Repository: Redocly/openapi-cli
Pull request #320 (2021-07-02 07:43:30)

fix: ajv upgrade fixes
## What/Why/How?

Fix two issues caused by recent ajv upgrade:

- crash when AJV throws a schema error, reported here: https://github.com/Redocly/openapi-cli/issues/319
- invalid resolution of transitive $refs

## Reference

Fixes https://github.com/Redocly/openapi-cli/issues/319

## Testing

Added a few test

## Screenshots (optional)

n/a

## Check yourself

- [x] Code is linted
- [x] Tested with redoc/reference-docs/workflows
- [x] All new/updated code is covered with tests



Patch:
diff --git a/packages/core/src/rules/ajv.ts b/packages/core/src/rules/ajv.ts
index 105d9dea..ebc3cdfd 100644
--- a/packages/core/src/rules/ajv.ts
+++ b/packages/core/src/rules/ajv.ts
@@ -21,10 +21,10 @@ function getAjv(resolve: ResolveFn<any>, disallowAdditionalProperties: boolean)
       allowUnionTypes: true,
       validateFormats: false, // TODO: fix it
       defaultAdditionalProperties: !disallowAdditionalProperties,
-      loadSchemaSync(base: string, $ref: string, $id: string) {
+      loadSchemaSync(base: string, $ref: string) {
         const resolvedRef = resolve({ $ref }, base.split('#')[0]);
         if (!resolvedRef || !resolvedRef.location) return undefined;
-        return { $id, ...resolvedRef.node };
+        return { $id: resolvedRef.location.absolutePointer, ...resolvedRef.node };
       },
       logger: false,
     });
diff --git a/packages/core/src/rules/oas3/no-invalid-media-type-examples.ts b/packages/core/src/rules/oas3/no-invalid-media-type-examples.ts
index c913d1a4..341b01fe 100644
--- a/packages/core/src/rules/oas3/no-invalid-media-type-examples.ts
+++ b/packages/core/src/rules/oas3/no-invalid-media-type-examples.ts
@@ -29,26 +29,34 @@ export const ValidContentExamples: Oas3Rule = (opts) => {
         }
 
         function validateExample(example: any, dataLoc: Location) {
-          const { valid, errors } = validateJsonSchema(
-            example,
-            mediaType.schema!,
-            location.child('schema'),
-            dataLoc.pointer,
-            resolve,
-            disallowAdditionalProperties,
-          );
-          if (!valid) {
-            for (let error of errors) {
-              report({
-                message: `Example value must conform to the schema: ${error.message}.`,
-                location: {
-                  ...new Location(dataLoc.source, error.instancePath),
-                  reportOnKey: error.keyword === 'additionalProperties',
-                },
-                from: location,
-                suggest: error.suggest,
-              });
+          try {
+            const { valid, errors } = validateJsonSchema(
+              example,
+              mediaType.schema!,
+              location.child('schema'),
+              dataLoc.pointer,
+              resolve,
+              disallowAdditionalProperties,
+            );
+            if (!valid) {
+              for (let error of errors) {
+                report({
+                  message: `Example value must conform to the schema: ${error.message}.`,
+                  location: {
+                    ...new Location(dataLoc.source, error.instancePath),
+                    reportOnKey: error.keyword === 'additionalProperties',
+                  },
+                  from: location,
+                  suggest: error.suggest,
+                });
+              }
             }
+          } catch(e) {
+            report({
+              message: `Example validation errored: ${e.message}.`,
+              location: location.child('schema'),
+              from: location
+            });
           }
         }
       },


Test patch:
diff --git a/__tests__/no-invalid-media-type-examples-invalid-schema/.redocly.yaml b/__tests__/no-invalid-media-type-examples-invalid-schema/.redocly.yaml
new file mode 100644
index 00000000..c9167e38
--- /dev/null
+++ b/__tests__/no-invalid-media-type-examples-invalid-schema/.redocly.yaml
@@ -0,0 +1,6 @@
+apiDefinitions:
+  main: ./openapi.yaml
+
+lint:
+  extends:
+    - recommended
diff --git a/__tests__/no-invalid-media-type-examples-invalid-schema/openapi.yaml b/__tests__/no-invalid-media-type-examples-invalid-schema/openapi.yaml
new file mode 100644
index 00000000..86e3731a
--- /dev/null
+++ b/__tests__/no-invalid-media-type-examples-invalid-schema/openapi.yaml
@@ -0,0 +1,28 @@
+---
+openapi: 3.0.1
+info:
+  title: TEST
+  description: TEST
+  version: v1
+  license:
+    name: Apache 2.0
+    url: http://www.apache.org/licenses/LICENSE-2.0.html
+servers:
+- url: http://petstore.swagger.io/v1
+components:
+  schemas:
+    Test:
+      nullable: true
+paths:
+  "/test":
+    get:
+      summary: test
+      operationId: test
+      responses:
+        '202':
+          description: Test
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Test"
+              example: {}
\ No newline at end of file
diff --git a/__tests__/no-invalid-media-type-examples-invalid-schema/snapshot.js b/__tests__/no-invalid-media-type-examples-invalid-schema/snapshot.js
new file mode 100644
index 00000000..d4e45fa2
--- /dev/null
+++ b/__tests__/no-invalid-media-type-examples-invalid-schema/snapshot.js
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E no-invalid-media-type-examples-invalid-schema 1`] = `
+
+validating /openapi.yaml...
+[1] openapi.yaml:27:17 at #/paths/~1test/get/responses/202/content/application~1json/schema
+
+Example validation errored: "nullable" cannot be used without "type".
+
+25 | application/json:
+26 |   schema:
+27 |     $ref: "#/components/schemas/Test"
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+28 |   example: {}
+
+referenced from openapi.yaml:26:15
+
+Warning was generated by the no-invalid-media-type-examples rule.
+
+
+/openapi.yaml: validated in <test>ms
+
+Woohoo! Your OpenAPI definition is valid. 🎉
+You have 1 warning.
+
+
+`;
diff --git a/packages/core/src/rules/oas3/__tests__/fixtures/common.yaml b/packages/core/src/rules/oas3/__tests__/fixtures/common.yaml
new file mode 100644
index 00000000..3f36f79e
--- /dev/null
+++ b/packages/core/src/rules/oas3/__tests__/fixtures/common.yaml
@@ -0,0 +1,11 @@
+components:
+  schemas:
+    A:
+      type: object
+      properties:
+        a:
+          type: string
+        b:
+          $ref: '#/components/schemas/B'
+    B:
+      type: string
\ No newline at end of file
diff --git a/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts b/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts
index 26f22269..fcf0df4c 100644
--- a/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts
+++ b/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts
@@ -307,4 +307,87 @@ describe('no-invalid-media-type-examples', () => {
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
   });
+
+  it('should work with cross-file $ref', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+        openapi: 3.0.0
+        components:
+          schemas:
+            C:
+              $ref: './fixtures/common.yaml#/components/schemas/A'
+        paths:
+          /pet:
+            get:
+              responses:
+                200:
+                  content:
+                    application/json:
+                      example: {
+                        "a": "test",
+                        "b": "test"
+                      }
+                      schema:
+                        $ref: '#/components/schemas/C'
+
+      `,
+      __dirname + '/foobar.yaml',
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: makeConfig({ 'no-invalid-media-type-examples': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not throw for ajv throw', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+        openapi: 3.0.0
+        paths:
+          /pet:
+            get:
+              responses:
+                200:
+                  content:
+                    application/json:
+                      example: {}
+                      schema:
+                        nullable: true
+
+      `,
+      'foobar.yaml',
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: makeConfig({ 'no-invalid-media-type-examples': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "from": Object {
+            "pointer": "#/paths/~1pet/get/responses/200/content/application~1json",
+            "source": "foobar.yaml",
+          },
+          "location": Array [
+            Object {
+              "pointer": "#/paths/~1pet/get/responses/200/content/application~1json/schema",
+              "reportOnKey": false,
+              "source": "foobar.yaml",
+            },
+          ],
+          "message": "Example validation errored: \\"nullable\\" cannot be used without \\"type\\".",
+          "ruleId": "no-invalid-media-type-examples",
+          "severity": "error",
+          "suggest": Array [],
+        },
+      ]
+    `);
+  });
 });