Repository: Redocly/redocly-cli
Pull request #958 (2022-12-07 12:38:44)

feat: add vendor extensions to types
## What/Why/How?
Add vendor extensions to types to allow use with rules and decorators.

Docs:
https://redocly.com/docs/redoc/redoc-vendor-extensions
https://redocly.com/docs/api-reference-docs/spec-extensions/

## Reference
https://redoc-ly.slack.com/archives/C02UHE7EL3E/p1668877670978679

## Testing
Locally

## Screenshots (optional)
N/a

## Check yourself

- [x] Code is linted
- [ ] Tested with redoc/reference-docs/workflows
- [ ] All new/updated code is covered with tests

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines



Patch:
diff --git a/packages/core/src/types/oas2.ts b/packages/core/src/types/oas2.ts
index b726a52a..24095bbc 100644
--- a/packages/core/src/types/oas2.ts
+++ b/packages/core/src/types/oas2.ts
@@ -19,6 +19,9 @@ const Root: NodeType = {
     security: 'SecurityRequirementList',
     tags: 'TagList',
     externalDocs: 'ExternalDocs',
+    'x-servers': 'XServerList',
+    'x-tagGroups': 'TagGroups',
+    'x-ignoredHeaderParameters': { type: 'array', items: { type: 'string' } },
   },
   required: ['swagger', 'paths', 'info'],
 };
@@ -31,10 +34,20 @@ const Info: NodeType = {
     contact: 'Contact',
     license: 'License',
     version: { type: 'string' },
+    'x-logo': 'Logo',
   },
   required: ['title', 'version'],
 };
 
+const Logo: NodeType = {
+  properties: {
+    url: { type: 'string' },
+    altText: { type: 'string' },
+    backgroundColor: { type: 'string' },
+    href: { type: 'string' },
+  },
+};
+
 const Contact: NodeType = {
   properties: {
     name: { type: 'string' },
@@ -89,6 +102,7 @@ const Operation: NodeType = {
     'x-codeSamples': 'XCodeSampleList',
     'x-code-samples': 'XCodeSampleList', // deprecated
     'x-hideTryItPanel': { type: 'boolean' },
+    'x-meta': 'XMetaList',
   },
   required: ['responses'],
 };
@@ -101,6 +115,14 @@ const XCodeSample: NodeType = {
   },
 };
 
+const XServer: NodeType = {
+  properties: {
+    url: { type: 'string' },
+    description: { type: 'string' },
+  },
+  required: ['url'],
+};
+
 const ExternalDocs: NodeType = {
   properties: {
     description: { type: 'string' },
@@ -134,6 +156,8 @@ const Parameter: NodeType = {
     uniqueItems: { type: 'boolean' },
     enum: { type: 'array' },
     multipleOf: { type: 'number' },
+    'x-example': 'Example',
+    'x-examples': 'ExamplesMap',
   },
   required(value) {
     if (!value || !value.in) {
@@ -194,6 +218,7 @@ const Response: NodeType = {
     schema: 'Schema',
     headers: mapOf('Header'),
     examples: 'Examples',
+    'x-summary': { type: 'string' },
   },
   required: ['description'],
 };
@@ -238,10 +263,19 @@ const Tag: NodeType = {
     name: { type: 'string' },
     description: { type: 'string' },
     externalDocs: 'ExternalDocs',
+    'x-traitTag': { type: 'boolean' },
+    'x-displayName': { type: 'string' },
   },
   required: ['name'],
 };
 
+const TagGroup: NodeType = {
+  properties: {
+    name: { type: 'string' },
+    tags: { type: 'array', items: { type: 'string' } },
+  },
+};
+
 const Schema: NodeType = {
   properties: {
     format: { type: 'string' },
@@ -289,9 +323,19 @@ const Schema: NodeType = {
     externalDocs: 'ExternalDocs',
     example: { isExample: true },
     'x-tags': { type: 'array', items: { type: 'string' } },
+    'x-nullable': { type: 'boolean' },
+    'x-extendedDiscriminator': { type: 'string' },
+    'x-additionalPropertiesName': { type: 'string' },
+    'x-explicitMappingOnly': { type: 'boolean' },
+    'x-enumDescriptions': 'EnumDescriptions',
   },
 };
 
+const EnumDescriptions: NodeType = {
+  properties: {},
+  additionalProperties: { type: 'string' },
+};
+
 const SchemaProperties: NodeType = {
   properties: {},
   additionalProperties: 'Schema',
@@ -317,6 +361,7 @@ const SecurityScheme: NodeType = {
     authorizationUrl: { type: 'string' },
     tokenUrl: { type: 'string' },
     scopes: { type: 'object', additionalProperties: { type: 'string' } },
+    'x-defaultClientId': { type: 'string' },
   },
   required(value) {
     switch (value?.type) {
@@ -368,16 +413,40 @@ const SecurityRequirement: NodeType = {
   additionalProperties: { type: 'array', items: { type: 'string' } },
 };
 
+const Example: NodeType = {
+  properties: {
+    value: { isExample: true },
+    summary: { type: 'string' },
+    description: { type: 'string' },
+    externalValue: { type: 'string' },
+  },
+};
+
+const XMeta: NodeType = {
+  properties: {
+    title: { type: 'string' },
+    description: { type: 'string' },
+    keywords: { type: 'string' },
+    image: { type: 'string' },
+  },
+};
+
 export const Oas2Types: Record<string, NodeType> = {
   Root,
   Tag,
   TagList: listOf('Tag'),
+  TagGroups: listOf('TagGroup'),
+  TagGroup,
   ExternalDocs,
+  Example,
+  ExamplesMap: mapOf('Example'),
+  EnumDescriptions,
   SecurityRequirement,
   SecurityRequirementList: listOf('SecurityRequirement'),
   Info,
   Contact,
   License,
+  Logo,
   Paths,
   PathItem,
   Parameter,
@@ -398,4 +467,8 @@ export const Oas2Types: Record<string, NodeType> = {
   SecurityScheme,
   XCodeSample,
   XCodeSampleList: listOf('XCodeSample'),
+  XMeta,
+  XMetaList: listOf('XMeta'),
+  XServerList: listOf('XServer'),
+  XServer,
 };
diff --git a/packages/core/src/types/oas3.ts b/packages/core/src/types/oas3.ts
index 94e95435..086491fd 100755
--- a/packages/core/src/types/oas3.ts
+++ b/packages/core/src/types/oas3.ts
@@ -13,6 +13,8 @@ const Root: NodeType = {
     paths: 'Paths',
     components: 'Components',
     'x-webhooks': 'WebhooksMap',
+    'x-tagGroups': 'TagGroups',
+    'x-ignoredHeaderParameters': { type: 'array', items: { type: 'string' } },
   },
   required: ['openapi', 'paths', 'info'],
 };
@@ -22,10 +24,19 @@ const Tag: NodeType = {
     name: { type: 'string' },
     description: { type: 'string' },
     externalDocs: 'ExternalDocs',
+    'x-traitTag': { type: 'boolean' },
+    'x-displayName': { type: 'string' },
   },
   required: ['name'],
 };
 
+const TagGroup: NodeType = {
+  properties: {
+    name: { type: 'string' },
+    tags: { type: 'array', items: { type: 'string' } },
+  },
+};
+
 const ExternalDocs: NodeType = {
   properties: {
     description: { type: 'string' },
@@ -68,10 +79,20 @@ const Info: NodeType = {
     termsOfService: { type: 'string' },
     contact: 'Contact',
     license: 'License',
+    'x-logo': 'Logo',
   },
   required: ['title', 'version'],
 };
 
+const Logo: NodeType = {
+  properties: {
+    url: { type: 'string' },
+    altText: { type: 'string' },
+    backgroundColor: { type: 'string' },
+    href: { type: 'string' },
+  },
+};
+
 const Contact: NodeType = {
   properties: {
     name: { type: 'string' },
@@ -159,6 +180,7 @@ const Operation: NodeType = {
     'x-codeSamples': 'XCodeSampleList',
     'x-code-samples': 'XCodeSampleList', // deprecated
     'x-hideTryItPanel': { type: 'boolean' },
+    'x-meta': 'XMetaList',
   },
   required: ['responses'],
 };
@@ -215,6 +237,11 @@ const Encoding: NodeType = {
   },
 };
 
+const EnumDescriptions: NodeType = {
+  properties: {},
+  additionalProperties: { type: 'string' },
+};
+
 const Header: NodeType = {
   properties: {
     description: { type: 'string' },
@@ -246,6 +273,7 @@ const Response: NodeType = {
     headers: 'HeadersMap',
     content: 'MediaTypesMap',
     links: 'LinksMap',
+    'x-summary': { type: 'string' },
   },
   required: ['description'],
 };
@@ -320,6 +348,8 @@ const Schema: NodeType = {
     example: { isExample: true },
     deprecated: { type: 'boolean' },
     'x-tags': { type: 'array', items: { type: 'string' } },
+    'x-additionalPropertiesName': { type: 'string' },
+    'x-explicitMappingOnly': { type: 'boolean' },
   },
 };
 
@@ -404,6 +434,13 @@ const AuthorizationCode: NodeType = {
     authorizationUrl: { type: 'string' },
     scopes: { type: 'object', additionalProperties: { type: 'string' } }, // TODO: validate scopes
     tokenUrl: { type: 'string' },
+    'x-usePkce': (value: any) => {
+      if (typeof value === 'boolean') {
+        return { type: 'boolean' };
+      } else {
+        return 'XUsePkce';
+      }
+    },
   },
   required: ['authorizationUrl', 'tokenUrl', 'scopes'],
 };
@@ -427,6 +464,7 @@ const SecurityScheme: NodeType = {
     bearerFormat: { type: 'string' },
     flows: 'OAuth2Flows',
     openIdConnectUrl: { type: 'string' },
+    'x-defaultClientId': { type: 'string' },
   },
   required(value) {
     switch (value?.type) {
@@ -459,10 +497,28 @@ const SecurityScheme: NodeType = {
   extensionsPrefix: 'x-',
 };
 
+const XMeta: NodeType = {
+  properties: {
+    title: { type: 'string' },
+    description: { type: 'string' },
+    keywords: { type: 'string' },
+    image: { type: 'string' },
+  },
+};
+
+const XUsePkce: NodeType = {
+  properties: {
+    disableManualConfiguration: { type: 'boolean' },
+    hideClientSecretInput: { type: 'boolean' },
+  },
+};
+
 export const Oas3Types: Record<string, NodeType> = {
   Root,
   Tag,
   TagList: listOf('Tag'),
+  TagGroups: listOf('TagGroup'),
+  TagGroup,
   ExternalDocs,
   Server,
   ServerList: listOf('Server'),
@@ -487,11 +543,13 @@ export const Oas3Types: Record<string, NodeType> = {
   ExamplesMap: mapOf('Example'),
   Encoding,
   EncodingMap: mapOf('Encoding'),
+  EnumDescriptions,
   Header,
   HeadersMap: mapOf('Header'),
   Responses,
   Response,
   Link,
+  Logo,
   Schema,
   Xml,
   SchemaProperties,
@@ -516,5 +574,8 @@ export const Oas3Types: Record<string, NodeType> = {
   SecurityScheme,
   XCodeSample,
   XCodeSampleList: listOf('XCodeSample'),
+  XMeta,
+  XMetaList: listOf('XMeta'),
+  XUsePkce,
   WebhooksMap,
 };


Test patch:
diff --git a/__tests__/webpack-bundle/bundle-workflows/meta-snapshot.js b/__tests__/webpack-bundle/bundle-workflows/meta-snapshot.js
index 454865d5..a2b6d33e 100644
--- a/__tests__/webpack-bundle/bundle-workflows/meta-snapshot.js
+++ b/__tests__/webpack-bundle/bundle-workflows/meta-snapshot.js
@@ -1,3 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`webpack-bundle test bundle-workflows 1`] = `{"fileDependencies":{},"rootType":{"properties":{"openapi":null,"info":{"properties":{"title":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"},"termsOfService":{"type":"string"},"contact":{"properties":{"name":{"type":"string"},"url":{"type":"string"},"email":{"type":"string"}},"name":"Contact"},"license":{"properties":{"name":{"type":"string"},"url":{"type":"string"}},"required":["name"],"name":"License"}},"required":["title","version"],"name":"Info"},"servers":{"name":"ServerList","properties":{},"items":{"properties":{"url":{"type":"string"},"description":{"type":"string"},"variables":{"name":"ServerVariablesMap","properties":{}}},"required":["url"],"name":"Server"}},"security":{"name":"SecurityRequirementList","properties":{},"items":{"properties":{},"additionalProperties":{"type":"array","items":{"type":"string"}},"name":"SecurityRequirement"}},"tags":{"name":"TagList","properties":{},"items":{"properties":{"name":{"type":"string"},"description":{"type":"string"},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"}},"required":["name"],"name":"Tag"}},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"},"paths":{"properties":{},"name":"Paths"},"components":{"properties":{"parameters":{"name":"NamedParameters","properties":{}},"schemas":{"name":"NamedSchemas","properties":{}},"responses":{"name":"NamedResponses","properties":{}},"examples":{"name":"NamedExamples","properties":{}},"requestBodies":{"name":"NamedRequestBodies","properties":{}},"headers":{"name":"NamedHeaders","properties":{}},"securitySchemes":{"name":"NamedSecuritySchemes","properties":{}},"links":{"name":"NamedLinks","properties":{}},"callbacks":{"name":"NamedCallbacks","properties":{}}},"name":"Components"},"x-webhooks":{"properties":{},"name":"WebhooksMap"}},"required":["openapi","paths","info"],"name":"Root"},"refTypes":{},"visitorsData":{"registry-dependencies":{"links":[]}}}`;
+exports[`webpack-bundle test bundle-workflows 1`] = `{"fileDependencies":{},"rootType":{"properties":{"openapi":null,"info":{"properties":{"title":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"},"termsOfService":{"type":"string"},"contact":{"properties":{"name":{"type":"string"},"url":{"type":"string"},"email":{"type":"string"}},"name":"Contact"},"license":{"properties":{"name":{"type":"string"},"url":{"type":"string"}},"required":["name"],"name":"License"},"x-logo":{"properties":{"url":{"type":"string"},"altText":{"type":"string"},"backgroundColor":{"type":"string"},"href":{"type":"string"}},"name":"Logo"}},"required":["title","version"],"name":"Info"},"servers":{"name":"ServerList","properties":{},"items":{"properties":{"url":{"type":"string"},"description":{"type":"string"},"variables":{"name":"ServerVariablesMap","properties":{}}},"required":["url"],"name":"Server"}},"security":{"name":"SecurityRequirementList","properties":{},"items":{"properties":{},"additionalProperties":{"type":"array","items":{"type":"string"}},"name":"SecurityRequirement"}},"tags":{"name":"TagList","properties":{},"items":{"properties":{"name":{"type":"string"},"description":{"type":"string"},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"},"x-traitTag":{"type":"boolean"},"x-displayName":{"type":"string"}},"required":["name"],"name":"Tag"}},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"},"paths":{"properties":{},"name":"Paths"},"components":{"properties":{"parameters":{"name":"NamedParameters","properties":{}},"schemas":{"name":"NamedSchemas","properties":{}},"responses":{"name":"NamedResponses","properties":{}},"examples":{"name":"NamedExamples","properties":{}},"requestBodies":{"name":"NamedRequestBodies","properties":{}},"headers":{"name":"NamedHeaders","properties":{}},"securitySchemes":{"name":"NamedSecuritySchemes","properties":{}},"links":{"name":"NamedLinks","properties":{}},"callbacks":{"name":"NamedCallbacks","properties":{}}},"name":"Components"},"x-webhooks":{"properties":{},"name":"WebhooksMap"},"x-tagGroups":{"name":"TagGroups","properties":{},"items":{"properties":{"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}},"name":"TagGroup"}},"x-ignoredHeaderParameters":{"type":"array","items":{"type":"string"}}},"required":["openapi","paths","info"],"name":"Root"},"refTypes":{},"visitorsData":{"registry-dependencies":{"links":[]}}}`;
diff --git a/__tests__/webpack-bundle/bundle-workflows/metafile.json b/__tests__/webpack-bundle/bundle-workflows/metafile.json
index 79a3c2d2..1eb1cb67 100644
--- a/__tests__/webpack-bundle/bundle-workflows/metafile.json
+++ b/__tests__/webpack-bundle/bundle-workflows/metafile.json
@@ -1,1 +1,1 @@
-{"fileDependencies":{},"rootType":{"properties":{"openapi":null,"info":{"properties":{"title":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"},"termsOfService":{"type":"string"},"contact":{"properties":{"name":{"type":"string"},"url":{"type":"string"},"email":{"type":"string"}},"name":"Contact"},"license":{"properties":{"name":{"type":"string"},"url":{"type":"string"}},"required":["name"],"name":"License"}},"required":["title","version"],"name":"Info"},"servers":{"name":"ServerList","properties":{},"items":{"properties":{"url":{"type":"string"},"description":{"type":"string"},"variables":{"name":"ServerVariablesMap","properties":{}}},"required":["url"],"name":"Server"}},"security":{"name":"SecurityRequirementList","properties":{},"items":{"properties":{},"additionalProperties":{"type":"array","items":{"type":"string"}},"name":"SecurityRequirement"}},"tags":{"name":"TagList","properties":{},"items":{"properties":{"name":{"type":"string"},"description":{"type":"string"},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"}},"required":["name"],"name":"Tag"}},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"},"paths":{"properties":{},"name":"Paths"},"components":{"properties":{"parameters":{"name":"NamedParameters","properties":{}},"schemas":{"name":"NamedSchemas","properties":{}},"responses":{"name":"NamedResponses","properties":{}},"examples":{"name":"NamedExamples","properties":{}},"requestBodies":{"name":"NamedRequestBodies","properties":{}},"headers":{"name":"NamedHeaders","properties":{}},"securitySchemes":{"name":"NamedSecuritySchemes","properties":{}},"links":{"name":"NamedLinks","properties":{}},"callbacks":{"name":"NamedCallbacks","properties":{}}},"name":"Components"},"x-webhooks":{"properties":{},"name":"WebhooksMap"}},"required":["openapi","paths","info"],"name":"Root"},"refTypes":{},"visitorsData":{"registry-dependencies":{"links":[]}}}
\ No newline at end of file
+{"fileDependencies":{},"rootType":{"properties":{"openapi":null,"info":{"properties":{"title":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"},"termsOfService":{"type":"string"},"contact":{"properties":{"name":{"type":"string"},"url":{"type":"string"},"email":{"type":"string"}},"name":"Contact"},"license":{"properties":{"name":{"type":"string"},"url":{"type":"string"}},"required":["name"],"name":"License"},"x-logo":{"properties":{"url":{"type":"string"},"altText":{"type":"string"},"backgroundColor":{"type":"string"},"href":{"type":"string"}},"name":"Logo"}},"required":["title","version"],"name":"Info"},"servers":{"name":"ServerList","properties":{},"items":{"properties":{"url":{"type":"string"},"description":{"type":"string"},"variables":{"name":"ServerVariablesMap","properties":{}}},"required":["url"],"name":"Server"}},"security":{"name":"SecurityRequirementList","properties":{},"items":{"properties":{},"additionalProperties":{"type":"array","items":{"type":"string"}},"name":"SecurityRequirement"}},"tags":{"name":"TagList","properties":{},"items":{"properties":{"name":{"type":"string"},"description":{"type":"string"},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"},"x-traitTag":{"type":"boolean"},"x-displayName":{"type":"string"}},"required":["name"],"name":"Tag"}},"externalDocs":{"properties":{"description":{"type":"string"},"url":{"type":"string"}},"required":["url"],"name":"ExternalDocs"},"paths":{"properties":{},"name":"Paths"},"components":{"properties":{"parameters":{"name":"NamedParameters","properties":{}},"schemas":{"name":"NamedSchemas","properties":{}},"responses":{"name":"NamedResponses","properties":{}},"examples":{"name":"NamedExamples","properties":{}},"requestBodies":{"name":"NamedRequestBodies","properties":{}},"headers":{"name":"NamedHeaders","properties":{}},"securitySchemes":{"name":"NamedSecuritySchemes","properties":{}},"links":{"name":"NamedLinks","properties":{}},"callbacks":{"name":"NamedCallbacks","properties":{}}},"name":"Components"},"x-webhooks":{"properties":{},"name":"WebhooksMap"},"x-tagGroups":{"name":"TagGroups","properties":{},"items":{"properties":{"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}},"name":"TagGroup"}},"x-ignoredHeaderParameters":{"type":"array","items":{"type":"string"}}},"required":["openapi","paths","info"],"name":"Root"},"refTypes":{},"visitorsData":{"registry-dependencies":{"links":[]}}}
\ No newline at end of file
diff --git a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap
index c2fcbea4..b041c4a3 100644
--- a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap
@@ -33,6 +33,17 @@ rootType:
           required:
             - name
           name: License
+        x-logo:
+          properties:
+            url:
+              type: string
+            altText:
+              type: string
+            backgroundColor:
+              type: string
+            href:
+              type: string
+          name: Logo
       required:
         - title
         - version
@@ -80,6 +91,10 @@ rootType:
             required:
               - url
             name: ExternalDocs
+          x-traitTag:
+            type: boolean
+          x-displayName:
+            type: string
         required:
           - name
         name: Tag
@@ -128,6 +143,22 @@ rootType:
     x-webhooks:
       properties: {}
       name: WebhooksMap
+    x-tagGroups:
+      name: TagGroups
+      properties: {}
+      items:
+        properties:
+          name:
+            type: string
+          tags:
+            type: array
+            items:
+              type: string
+        name: TagGroup
+    x-ignoredHeaderParameters:
+      type: array
+      items:
+        type: string
   required:
     - openapi
     - paths