Repository: Redocly/redocly-cli
Pull request #978 (2023-01-03 14:57:16)

fix: spec rule error on extension additional properties
## What/Why/How?
Add support for `x-logo` type to the 3.1 spec version. 
Allow additional properties for the SpecExtension type. 

## Reference
https://redoc-ly.slack.com/archives/C02UHE7EL3E/p1672741829392659

## Testing
Locally.

## Screenshots (optional)
N/a

## Check yourself

- [ ] Code is linted
- [ ] Tested with redoc/reference-docs/workflows
- [ ] All new/updated code is covered with tests

## Security

- [ ] Security impact of change has been considered
- [ ] Code follows company security practices and guidelines



Patch:
diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts
index 60df7a4d..1e4277f0 100755
--- a/packages/core/src/types/index.ts
+++ b/packages/core/src/types/index.ts
@@ -66,6 +66,8 @@ export function mapOf(typeName: string) {
 export const SpecExtension: NormalizedNodeType = {
   name: 'SpecExtension',
   properties: {},
+  // skip validation of additional properties for unknown extensions
+  additionalProperties: null,
 };
 
 export function normalizeTypes(
diff --git a/packages/core/src/types/oas3_1.ts b/packages/core/src/types/oas3_1.ts
index e6970460..598e2d2a 100755
--- a/packages/core/src/types/oas3_1.ts
+++ b/packages/core/src/types/oas3_1.ts
@@ -38,6 +38,7 @@ const Info: NodeType = {
     summary: { type: 'string' },
     contact: 'Contact',
     license: 'License',
+    'x-logo': 'Logo',
   },
   required: ['title', 'version'],
   extensionsPrefix: 'x-',


Test patch:
diff --git a/packages/core/src/rules/common/__tests__/spec.test.ts b/packages/core/src/rules/common/__tests__/spec.test.ts
index 122fe301..f921af9d 100644
--- a/packages/core/src/rules/common/__tests__/spec.test.ts
+++ b/packages/core/src/rules/common/__tests__/spec.test.ts
@@ -308,6 +308,56 @@ describe('Oas3 spec', () => {
       ]
     `);
   });
+
+  it('should not report on SpecExtension with additionalProperties', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+      openapi: 3.0.0
+      x-foo:
+        prop: bar
+      `,
+      'foobar.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ spec: 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "from": undefined,
+          "location": Array [
+            Object {
+              "pointer": "#/",
+              "reportOnKey": true,
+              "source": "foobar.yaml",
+            },
+          ],
+          "message": "The field \`paths\` must be present on this level.",
+          "ruleId": "spec",
+          "severity": "error",
+          "suggest": Array [],
+        },
+        Object {
+          "from": undefined,
+          "location": Array [
+            Object {
+              "pointer": "#/",
+              "reportOnKey": true,
+              "source": "foobar.yaml",
+            },
+          ],
+          "message": "The field \`info\` must be present on this level.",
+          "ruleId": "spec",
+          "severity": "error",
+          "suggest": Array [],
+        },
+      ]
+    `);
+  });
 });
 
 describe('Oas3.1 spec', () => {
@@ -433,4 +483,73 @@ describe('Oas3.1 spec', () => {
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
   });
+
+  it('should not report on SpecExtension with additionalProperties', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+      openapi: 3.1.0
+      info:
+        x-logo:
+          url: 'https://redocly.github.io/redoc/example-logo.svg'
+          backgroundColor: '#FFFFFF'
+          altText: 'Example logo'
+      x-foo:
+        prop: bar
+      `,
+      'foobar.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ spec: 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "from": undefined,
+          "location": Array [
+            Object {
+              "pointer": "#/",
+              "reportOnKey": true,
+              "source": "foobar.yaml",
+            },
+          ],
+          "message": "Must contain at least one of the following fields: paths, components, webhooks.",
+          "ruleId": "spec",
+          "severity": "error",
+          "suggest": Array [],
+        },
+        Object {
+          "from": undefined,
+          "location": Array [
+            Object {
+              "pointer": "#/info",
+              "reportOnKey": true,
+              "source": "foobar.yaml",
+            },
+          ],
+          "message": "The field \`title\` must be present on this level.",
+          "ruleId": "spec",
+          "severity": "error",
+          "suggest": Array [],
+        },
+        Object {
+          "from": undefined,
+          "location": Array [
+            Object {
+              "pointer": "#/info",
+              "reportOnKey": true,
+              "source": "foobar.yaml",
+            },
+          ],
+          "message": "The field \`version\` must be present on this level.",
+          "ruleId": "spec",
+          "severity": "error",
+          "suggest": Array [],
+        },
+      ]
+    `);
+  });
 });