From 634b9e147bd5a27e175a6517f25065e1b821eb70 Mon Sep 17 00:00:00 2001 From: Jeremy Fiel Date: Tue, 5 Nov 2024 12:51:45 -0500 Subject: [PATCH 1/6] feat(typings): update openapi 3 and 3.1 typing declarations fixes #1547 --- .changeset/selfish-pandas-speak.md | 6 +++ packages/core/src/typings/openapi.ts | 63 +++++++++++++++++++--------- 2 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 .changeset/selfish-pandas-speak.md diff --git a/.changeset/selfish-pandas-speak.md b/.changeset/selfish-pandas-speak.md new file mode 100644 index 0000000000..06e3644ecb --- /dev/null +++ b/.changeset/selfish-pandas-speak.md @@ -0,0 +1,6 @@ +--- +"@redocly/openapi-core": minor +"@redocly/cli": minor +--- + +Update typings for OAS 3.0 and OAS 3.1 Schemas diff --git a/packages/core/src/typings/openapi.ts b/packages/core/src/typings/openapi.ts index 8860358073..9497f91e71 100644 --- a/packages/core/src/typings/openapi.ts +++ b/packages/core/src/typings/openapi.ts @@ -91,13 +91,13 @@ export interface Oas3Parameter { explode?: boolean; allowReserved?: boolean; schema?: Referenced; - example?: any; + example?: unknown; examples?: { [media: string]: Referenced }; content?: { [media: string]: Oas3MediaType }; } export interface Oas3Example { - value: any; + value: unknown; summary?: string; description?: string; externalValue?: string; @@ -112,13 +112,10 @@ export interface Oas3Xml { } // common fields for OpenAPI Schema v3.x -interface Oas3XSchemaBase { +interface Oas3XSchemaBase { $ref?: string; - properties?: { [name: string]: T }; - additionalProperties?: boolean | T; description?: string; - default?: any; - items?: T; + default?: unknown; required?: string[]; readOnly?: boolean; writeOnly?: boolean; @@ -127,17 +124,11 @@ interface Oas3XSchemaBase { externalDocs?: Oas3ExternalDocs; discriminator?: Oas3Discriminator; nullable?: boolean; - oneOf?: T[]; - anyOf?: T[]; - allOf?: T[]; - not?: T; title?: string; multipleOf?: number; maximum?: number; - exclusiveMaximum?: boolean; minimum?: number; - exclusiveMinimum?: boolean; maxLength?: number; minLength?: number; pattern?: string; @@ -146,21 +137,55 @@ interface Oas3XSchemaBase { uniqueItems?: boolean; maxProperties?: number; minProperties?: number; - enum?: any[]; - example?: any; + enum?: unknown[]; + example?: unknown; xml?: Oas3Xml; 'x-tags'?: string[]; } -export interface Oas3Schema extends Oas3XSchemaBase { +export interface Oas3Schema extends Oas3XSchemaBase { type?: string; + properties?: { [name: string]: Referenced }; + additionalProperties?: boolean | Oas3Schema; + items?: Oas3Schema; + exclusiveMaximum?: boolean; + exclusiveMinimum?: boolean; + oneOf?: Oas3Schema[]; + anyOf?: Oas3Schema[]; + allOf?: Oas3Schema[]; + not?: Oas3Schema; } -export interface Oas3_1Schema extends Oas3XSchemaBase { +export interface Oas3_1Schema extends Oas3XSchemaBase { type?: string | string[]; - examples?: any[]; + properties?: { [name: string]: Referenced }; + additionalProperties?: boolean | Oas3_1Schema; + examples?: unknown[]; prefixItems?: Oas3_1Schema[]; + items?: Oas3_1Schema; + oneOf?: Oas3_1Schema[]; + anyOf?: Oas3_1Schema[]; + allOf?: Oas3_1Schema[]; + not?: Oas3_1Schema; + exclusiveMaximum?: number; + exclusiveMinimum?: number; + const?: unknown; + contains?: Oas3_1Schema; + minContains?: number; + maxContains?: number; + propertyNames?: Oas3_1Schema; + if?: Oas3_1Schema; + then?: Oas3_1Schema; + else?: Oas3_1Schema; + dependentRequired?: { [name: string]: string[] }; + dependentSchemas?: { [name: string]: Referenced }; + patternProperties?: { [name: string]: Referenced }; + unevaluatedItems?: Oas3_1Schema; + unevaluatedProperties?: Oas3_1Schema; + contentSchema?: Oas3_1Schema; + contentMediaType?: string; + contentEncoding?: string; } export interface Oas3_1Definition extends Oas3Definition { @@ -179,7 +204,7 @@ export interface Oas3Discriminator { export interface Oas3MediaType { schema?: Referenced; - example?: any; + example?: unknown; examples?: { [name: string]: Referenced }; encoding?: { [field: string]: Oas3Encoding }; } From 3e1f910fb2a968ee2b762ead5b46d8ce0464402f Mon Sep 17 00:00:00 2001 From: Jeremy Fiel <32110157+jeremyfiel@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:48:15 -0500 Subject: [PATCH 2/6] Update .changeset/selfish-pandas-speak.md Added missing keywords for OAS 3.1.x (JSON Schema 2020-12). Co-authored-by: Andrew Tatomyr --- .changeset/selfish-pandas-speak.md | 5 +- packages/core/src/__tests__/lint.test.ts | 95 +++++++++++ .../common/media-type-examples-override.ts | 8 +- .../common/operation-description-override.ts | 4 +- .../oas3/remove-unused-components.ts | 8 +- .../src/rules/common/no-ambiguous-paths.ts | 4 +- .../rules/common/no-http-verbs-in-paths.ts | 4 +- .../src/rules/common/no-identical-paths.ts | 4 +- .../common/no-invalid-parameter-examples.ts | 4 +- .../src/rules/common/operation-description.ts | 4 +- .../common/operation-operationId-unique.ts | 4 +- .../common/operation-operationId-url-safe.ts | 4 +- .../src/rules/common/operation-operationId.ts | 4 +- .../common/operation-parameters-unique.ts | 6 +- .../rules/common/operation-singular-tag.ts | 4 +- .../src/rules/common/operation-summary.ts | 4 +- .../src/rules/common/operation-tag-defined.ts | 6 +- .../src/rules/common/parameter-description.ts | 4 +- .../rules/common/path-excludes-patterns.ts | 4 +- .../src/rules/common/path-http-verbs-order.ts | 4 +- .../src/rules/common/path-params-defined.ts | 6 +- .../rules/common/response-contains-header.ts | 4 +- .../core/src/rules/common/security-defined.ts | 8 +- .../src/rules/common/tags-alphabetical.ts | 4 +- .../oas3/array-parameter-serialization.ts | 4 +- .../src/rules/oas3/component-name-unique.ts | 9 +- packages/core/src/rules/other/stats.ts | 4 +- packages/core/src/types/oas3_1.ts | 2 + packages/core/src/typings/openapi.ts | 153 ++++++++++-------- packages/core/src/visitors.ts | 42 ++--- 30 files changed, 266 insertions(+), 154 deletions(-) diff --git a/.changeset/selfish-pandas-speak.md b/.changeset/selfish-pandas-speak.md index 06e3644ecb..25ec9fa3ce 100644 --- a/.changeset/selfish-pandas-speak.md +++ b/.changeset/selfish-pandas-speak.md @@ -1,6 +1,5 @@ --- -"@redocly/openapi-core": minor -"@redocly/cli": minor +"@redocly/openapi-core": patch --- -Update typings for OAS 3.0 and OAS 3.1 Schemas +Updated typings for OAS 3.0 and OAS 3.1 Schemas. diff --git a/packages/core/src/__tests__/lint.test.ts b/packages/core/src/__tests__/lint.test.ts index 91b71437d6..8e6eef519d 100644 --- a/packages/core/src/__tests__/lint.test.ts +++ b/packages/core/src/__tests__/lint.test.ts @@ -1674,4 +1674,99 @@ describe('lint', () => { expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`); }); + + it('should throw an error for $schema not expected here - OAS 3.0.x', async () => { + const document = parseYamlToDocument( + outdent` + openapi: 3.0.4 + info: + title: test json schema validation keyword - allOf should use an OAS Schema, not JSON Schema + version: 1.0.0 + paths: + '/thing': + get: + summary: a sample api + responses: + '200': + description: OK + content: + 'application/json': + schema: + $schema: http://json-schema.org/draft-04/schema# + type: object + properties: {} + `, + '' + ); + + const configFilePath = path.join(__dirname, '..', '..', '..', 'redocly.yaml'); + + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await makeConfig({ + rules: { spec: 'error' }, + decorators: undefined, + configPath: configFilePath, + }), + }); + + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(` + [ + { + "from": undefined, + "location": [ + { + "pointer": "#/paths/~1thing/get/responses/200/content/application~1json/schema/$schema", + "reportOnKey": true, + "source": "", + }, + ], + "message": "Property \`$schema\` is not expected here.", + "ruleId": "spec", + "severity": "error", + "suggest": [], + }, + ] + `); + }); + + it('should allow for $schema to be defined - OAS 3.1.x', async () => { + const document = parseYamlToDocument( + outdent` + openapi: 3.1.1 + info: + title: test json schema validation keyword - allOf should use an OAS Schema, not JSON Schema + version: 1.0.0 + paths: + '/thing': + get: + summary: a sample api + responses: + '200': + description: OK + content: + 'application/json': + schema: + $schema: http://json-schema.org/draft-04/schema# + type: object + properties: {} + `, + '' + ); + + const configFilePath = path.join(__dirname, '..', '..', '..', 'redocly.yaml'); + + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await makeConfig({ + rules: { spec: 'error' }, + decorators: undefined, + configPath: configFilePath, + }), + }); + + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`); + }); }); diff --git a/packages/core/src/decorators/common/media-type-examples-override.ts b/packages/core/src/decorators/common/media-type-examples-override.ts index 8b8037fe37..b2a064943f 100644 --- a/packages/core/src/decorators/common/media-type-examples-override.ts +++ b/packages/core/src/decorators/common/media-type-examples-override.ts @@ -2,13 +2,13 @@ import { yamlAndJsonSyncReader } from '../../utils'; import { isRef } from '../../ref-utils'; import type { Oas3Decorator } from '../../visitors'; -import type { Oas3Operation, Oas3RequestBody, Oas3Response } from '../../typings/openapi'; +import type { Oas3_1Schema, Oas3Operation, Oas3RequestBody, Oas3Response, Oas3Schema } from '../../typings/openapi'; import type { NonUndefined, ResolveFn, UserContext } from '../../walk'; export const MediaTypeExamplesOverride: Oas3Decorator = ({ operationIds }) => { return { Operation: { - enter(operation: Oas3Operation, ctx: UserContext) { + enter(operation: Oas3Operation, ctx: UserContext) { const operationId = operation.operationId; if (!operationId) { @@ -23,7 +23,7 @@ export const MediaTypeExamplesOverride: Oas3Decorator = ({ operationIds }) => { if (properties.responses && operation.responses) { for (const responseCode of Object.keys(properties.responses)) { - const resolvedResponse = checkAndResolveRef( + const resolvedResponse = checkAndResolveRef>( operation.responses[responseCode], ctx.resolve ); @@ -46,7 +46,7 @@ export const MediaTypeExamplesOverride: Oas3Decorator = ({ operationIds }) => { } if (properties.request && operation.requestBody) { - const resolvedRequest = checkAndResolveRef( + const resolvedRequest = checkAndResolveRef>( operation.requestBody, ctx.resolve ); diff --git a/packages/core/src/decorators/common/operation-description-override.ts b/packages/core/src/decorators/common/operation-description-override.ts index c583fb4ff2..46df16160b 100644 --- a/packages/core/src/decorators/common/operation-description-override.ts +++ b/packages/core/src/decorators/common/operation-description-override.ts @@ -2,13 +2,13 @@ import { readFileAsStringSync } from '../../utils'; import type { Oas3Decorator, Oas2Decorator } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Operation } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationDescriptionOverride: Oas3Decorator | Oas2Decorator = ({ operationIds }) => { return { Operation: { - leave(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + leave(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (!operation.operationId) return; if (!operationIds) throw new Error( diff --git a/packages/core/src/decorators/oas3/remove-unused-components.ts b/packages/core/src/decorators/oas3/remove-unused-components.ts index 4780961b9c..f65ae6d999 100644 --- a/packages/core/src/decorators/oas3/remove-unused-components.ts +++ b/packages/core/src/decorators/oas3/remove-unused-components.ts @@ -2,17 +2,17 @@ import { isEmptyObject } from '../../utils'; import type { Location } from '../../ref-utils'; import type { Oas3Decorator } from '../../visitors'; -import type { Oas3Components, Oas3Definition } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Components, Oas3Definition } from '../../typings/openapi'; export const RemoveUnusedComponents: Oas3Decorator = () => { const components = new Map< string, - { usedIn: Location[]; componentType?: keyof Oas3Components; name: string } + { usedIn: Location[]; componentType?: keyof Oas3Components; name: string } >(); function registerComponent( location: Location, - componentType: keyof Oas3Components, + componentType: keyof Oas3Components, name: string ): void { components.set(location.absolutePointer, { @@ -22,7 +22,7 @@ export const RemoveUnusedComponents: Oas3Decorator = () => { }); } - function removeUnusedComponents(root: Oas3Definition, removedPaths: string[]): number { + function removeUnusedComponents(root: Oas3Definition, removedPaths: string[]): number { const removedLengthStart = removedPaths.length; for (const [path, { usedIn, name, componentType }] of components) { diff --git a/packages/core/src/rules/common/no-ambiguous-paths.ts b/packages/core/src/rules/common/no-ambiguous-paths.ts index a8d2af5b27..3fcf2c1300 100644 --- a/packages/core/src/rules/common/no-ambiguous-paths.ts +++ b/packages/core/src/rules/common/no-ambiguous-paths.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; -import type { Oas3Paths } from '../../typings/openapi'; +import type { Oas3_1Schema, Oas3Paths, Oas3Schema } from '../../typings/openapi'; import type { Oas2Paths } from '../../typings/swagger'; export const NoAmbiguousPaths: Oas3Rule | Oas2Rule = () => { return { - Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { + Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { const seenPaths: string[] = []; for (const currentPath of Object.keys(pathMap)) { diff --git a/packages/core/src/rules/common/no-http-verbs-in-paths.ts b/packages/core/src/rules/common/no-http-verbs-in-paths.ts index 59560154fd..dce002b902 100644 --- a/packages/core/src/rules/common/no-http-verbs-in-paths.ts +++ b/packages/core/src/rules/common/no-http-verbs-in-paths.ts @@ -2,14 +2,14 @@ import { isPathParameter, splitCamelCaseIntoWords } from '../../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2PathItem } from '../../typings/swagger'; -import type { Oas3PathItem } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3PathItem } from '../../typings/openapi'; import type { UserContext } from '../../walk'; const httpMethods = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace']; export const NoHttpVerbsInPaths: Oas3Rule | Oas2Rule = ({ splitIntoWords }) => { return { - PathItem(_path: Oas2PathItem | Oas3PathItem, { key, report, location }: UserContext) { + PathItem(_path: Oas2PathItem | Oas3PathItem, { key, report, location }: UserContext) { const pathKey = key.toString(); if (!pathKey.startsWith('/')) return; const pathSegments = pathKey.split('/'); diff --git a/packages/core/src/rules/common/no-identical-paths.ts b/packages/core/src/rules/common/no-identical-paths.ts index 988dec3e56..d5da26d802 100644 --- a/packages/core/src/rules/common/no-identical-paths.ts +++ b/packages/core/src/rules/common/no-identical-paths.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; -import type { Oas3Paths } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Paths } from '../../typings/openapi'; import type { Oas2Paths } from '../../typings/swagger'; export const NoIdenticalPaths: Oas3Rule | Oas2Rule = () => { return { - Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { + Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { const Paths = new Map(); for (const pathName of Object.keys(pathMap)) { const id = pathName.replace(/{.+?}/g, '{VARIABLE}'); diff --git a/packages/core/src/rules/common/no-invalid-parameter-examples.ts b/packages/core/src/rules/common/no-invalid-parameter-examples.ts index 740335dfeb..a20094a655 100644 --- a/packages/core/src/rules/common/no-invalid-parameter-examples.ts +++ b/packages/core/src/rules/common/no-invalid-parameter-examples.ts @@ -1,13 +1,13 @@ import { getAdditionalPropertiesOption, validateExample } from '../utils'; import type { UserContext } from '../../walk'; -import type { Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; export const NoInvalidParameterExamples: any = (opts: any) => { const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false; return { Parameter: { - leave(parameter: Oas3Parameter, ctx: UserContext) { + leave(parameter: Oas3Parameter, ctx: UserContext) { if (parameter.example !== undefined) { validateExample( parameter.example, diff --git a/packages/core/src/rules/common/operation-description.ts b/packages/core/src/rules/common/operation-description.ts index 6f2bb45c04..b5cd7999b3 100644 --- a/packages/core/src/rules/common/operation-description.ts +++ b/packages/core/src/rules/common/operation-description.ts @@ -3,11 +3,11 @@ import { validateDefinedAndNonEmpty } from '../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation } from '../../typings/openapi'; +import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; export const OperationDescription: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { validateDefinedAndNonEmpty('description', operation, ctx); }, }; diff --git a/packages/core/src/rules/common/operation-operationId-unique.ts b/packages/core/src/rules/common/operation-operationId-unique.ts index 54aa597abc..7819ca2ebd 100644 --- a/packages/core/src/rules/common/operation-operationId-unique.ts +++ b/packages/core/src/rules/common/operation-operationId-unique.ts @@ -1,13 +1,13 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation } from '../../typings/openapi'; +import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationIdUnique: Oas3Rule | Oas2Rule = () => { const seenOperations = new Set(); return { - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (!operation.operationId) return; if (seenOperations.has(operation.operationId)) { report({ diff --git a/packages/core/src/rules/common/operation-operationId-url-safe.ts b/packages/core/src/rules/common/operation-operationId-url-safe.ts index 5f1c410809..7a3ffb920d 100644 --- a/packages/core/src/rules/common/operation-operationId-url-safe.ts +++ b/packages/core/src/rules/common/operation-operationId-url-safe.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation } from '../../typings/openapi'; +import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; import type { UserContext } from '../../walk'; // eslint-disable-next-line no-useless-escape @@ -8,7 +8,7 @@ const validUrlSymbols = /^[A-Za-z0-9-._~:/?#\[\]@!\$&'()*+,;=]*$/; export const OperationIdUrlSafe: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (operation.operationId && !validUrlSymbols.test(operation.operationId)) { report({ message: 'Operation `operationId` should not have URL invalid characters.', diff --git a/packages/core/src/rules/common/operation-operationId.ts b/packages/core/src/rules/common/operation-operationId.ts index b759a2ab01..c4323802ff 100644 --- a/packages/core/src/rules/common/operation-operationId.ts +++ b/packages/core/src/rules/common/operation-operationId.ts @@ -3,13 +3,13 @@ import { validateDefinedAndNonEmpty } from '../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation } from '../../typings/openapi'; +import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; export const OperationOperationId: Oas3Rule | Oas2Rule = () => { return { Root: { PathItem: { - Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { validateDefinedAndNonEmpty('operationId', operation, ctx); }, }, diff --git a/packages/core/src/rules/common/operation-parameters-unique.ts b/packages/core/src/rules/common/operation-parameters-unique.ts index 901dfb26d6..dc4f30a6c4 100644 --- a/packages/core/src/rules/common/operation-parameters-unique.ts +++ b/packages/core/src/rules/common/operation-parameters-unique.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Parameter } from '../../typings/swagger'; -import type { Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationParametersUnique: Oas3Rule | Oas2Rule = () => { @@ -13,7 +13,7 @@ export const OperationParametersUnique: Oas3Rule | Oas2Rule = () => { seenPathParams = new Set(); }, Parameter( - parameter: Oas2Parameter | Oas3Parameter, + parameter: Oas2Parameter | Oas3Parameter, { report, key, parentLocations }: UserContext ) { const paramId = `${parameter.in}___${parameter.name}`; @@ -30,7 +30,7 @@ export const OperationParametersUnique: Oas3Rule | Oas2Rule = () => { seenOperationParams = new Set(); }, Parameter( - parameter: Oas2Parameter | Oas3Parameter, + parameter: Oas2Parameter | Oas3Parameter, { report, key, parentLocations }: UserContext ) { const paramId = `${parameter.in}___${parameter.name}`; diff --git a/packages/core/src/rules/common/operation-singular-tag.ts b/packages/core/src/rules/common/operation-singular-tag.ts index 6e149f9ca9..df3e914e5c 100644 --- a/packages/core/src/rules/common/operation-singular-tag.ts +++ b/packages/core/src/rules/common/operation-singular-tag.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation } from '../../typings/openapi'; +import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationSingularTag: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (operation.tags && operation.tags.length > 1) { report({ message: 'Operation `tags` object should have only one tag.', diff --git a/packages/core/src/rules/common/operation-summary.ts b/packages/core/src/rules/common/operation-summary.ts index 18743ad9ed..171b1b747e 100644 --- a/packages/core/src/rules/common/operation-summary.ts +++ b/packages/core/src/rules/common/operation-summary.ts @@ -3,11 +3,11 @@ import { validateDefinedAndNonEmpty } from '../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation } from '../../typings/openapi'; +import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; export const OperationSummary: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { validateDefinedAndNonEmpty('summary', operation, ctx); }, }; diff --git a/packages/core/src/rules/common/operation-tag-defined.ts b/packages/core/src/rules/common/operation-tag-defined.ts index e492dba7e2..ea70225e14 100644 --- a/packages/core/src/rules/common/operation-tag-defined.ts +++ b/packages/core/src/rules/common/operation-tag-defined.ts @@ -1,16 +1,16 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Definition, Oas2Operation } from '../../typings/swagger'; -import type { Oas3Definition, Oas3Operation } from '../../typings/openapi'; +import type { Oas3Definition, Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationTagDefined: Oas3Rule | Oas2Rule = () => { let definedTags: Set; return { - Root(root: Oas2Definition | Oas3Definition) { + Root(root: Oas2Definition | Oas3Definition) { definedTags = new Set((root.tags ?? []).map((t) => t.name)); }, - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (operation.tags) { for (let i = 0; i < operation.tags.length; i++) { if (!definedTags.has(operation.tags[i])) { diff --git a/packages/core/src/rules/common/parameter-description.ts b/packages/core/src/rules/common/parameter-description.ts index f1495b29e4..2580307b7b 100644 --- a/packages/core/src/rules/common/parameter-description.ts +++ b/packages/core/src/rules/common/parameter-description.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Parameter } from '../../typings/swagger'; -import type { Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const ParameterDescription: Oas3Rule | Oas2Rule = () => { return { - Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { + Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { if (parameter.description === undefined) { report({ message: 'Parameter object description must be present.', diff --git a/packages/core/src/rules/common/path-excludes-patterns.ts b/packages/core/src/rules/common/path-excludes-patterns.ts index 14988cbffd..3304c5314f 100644 --- a/packages/core/src/rules/common/path-excludes-patterns.ts +++ b/packages/core/src/rules/common/path-excludes-patterns.ts @@ -1,11 +1,11 @@ import type { Oas2Rule, Oas3Rule } from '../../visitors'; import type { Oas2PathItem } from '../../typings/swagger'; -import type { Oas3PathItem } from '../../typings/openapi'; +import type { Oas3PathItem, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const PathExcludesPatterns: Oas3Rule | Oas2Rule = ({ patterns }) => { return { - PathItem(_path: Oas2PathItem | Oas3PathItem, { report, key, location }: UserContext) { + PathItem(_path: Oas2PathItem | Oas3PathItem, { report, key, location }: UserContext) { if (!patterns) throw new Error(`Parameter "patterns" is not provided for "path-excludes-patterns" rule`); const pathKey = key.toString(); diff --git a/packages/core/src/rules/common/path-http-verbs-order.ts b/packages/core/src/rules/common/path-http-verbs-order.ts index 3bfe0bcddd..4f1f2615b6 100644 --- a/packages/core/src/rules/common/path-http-verbs-order.ts +++ b/packages/core/src/rules/common/path-http-verbs-order.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2PathItem } from '../../typings/swagger'; -import type { Oas3PathItem } from '../../typings/openapi'; +import type { Oas3PathItem, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; import type { UserContext } from '../../walk'; const defaultOrder = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace']; @@ -12,7 +12,7 @@ export const PathHttpVerbsOrder: Oas3Rule | Oas2Rule = (opts: any) => { } return { - PathItem(path: Oas2PathItem | Oas3PathItem, { report, location }: UserContext) { + PathItem(path: Oas2PathItem | Oas3PathItem, { report, location }: UserContext) { const httpVerbs = Object.keys(path).filter((k) => order.includes(k)); for (let i = 0; i < httpVerbs.length - 1; i++) { diff --git a/packages/core/src/rules/common/path-params-defined.ts b/packages/core/src/rules/common/path-params-defined.ts index bbb696b860..88c5ed50b5 100644 --- a/packages/core/src/rules/common/path-params-defined.ts +++ b/packages/core/src/rules/common/path-params-defined.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Parameter } from '../../typings/swagger'; -import type { Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; import type { UserContext } from '../../walk'; const pathRegex = /\{([a-zA-Z0-9_.-]+)\}+/g; @@ -20,7 +20,7 @@ export const PathParamsDefined: Oas3Rule | Oas2Rule = () => { Array.from(key!.toString().matchAll(pathRegex)).map((m) => m[1]) ); }, - Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { + Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { if (parameter.in === 'path' && parameter.name) { definedPathParams.add(parameter.name); if (!pathTemplateParams.has(parameter.name)) { @@ -48,7 +48,7 @@ export const PathParamsDefined: Oas3Rule | Oas2Rule = () => { } } }, - Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { + Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { if (parameter.in === 'path' && parameter.name) { definedOperationParams.add(parameter.name); if (!pathTemplateParams.has(parameter.name)) { diff --git a/packages/core/src/rules/common/response-contains-header.ts b/packages/core/src/rules/common/response-contains-header.ts index 38ad9d6f75..ba875329cc 100644 --- a/packages/core/src/rules/common/response-contains-header.ts +++ b/packages/core/src/rules/common/response-contains-header.ts @@ -2,7 +2,7 @@ import { getMatchingStatusCodeRange } from '../../utils'; import type { Oas2Rule, Oas3Rule } from '../../visitors'; import type { UserContext } from '../../walk'; -import type { Oas3Response } from '../../typings/openapi'; +import type { Oas3Response, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; import type { Oas2Response } from '../../typings/swagger'; export const ResponseContainsHeader: Oas3Rule | Oas2Rule = (options) => { @@ -10,7 +10,7 @@ export const ResponseContainsHeader: Oas3Rule | Oas2Rule = (options) => { return { Operation: { Response: { - enter: (response: Oas2Response | Oas3Response, { report, location, key }: UserContext) => { + enter: (response: Oas2Response | Oas3Response, { report, location, key }: UserContext) => { const expectedHeaders = names[key] || names[getMatchingStatusCodeRange(key)] || diff --git a/packages/core/src/rules/common/security-defined.ts b/packages/core/src/rules/common/security-defined.ts index 1e1ea0b98e..b27dfed6f6 100644 --- a/packages/core/src/rules/common/security-defined.ts +++ b/packages/core/src/rules/common/security-defined.ts @@ -8,6 +8,8 @@ import type { Oas2SecurityScheme, } from '../../typings/swagger'; import type { + Oas3Schema, + Oas3_1Schema, Oas3Definition, Oas3Operation, Oas3PathItem, @@ -31,7 +33,7 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = (opts: { return { Root: { - leave(root: Oas2Definition | Oas3Definition, { report }: UserContext) { + leave(root: Oas2Definition | Oas3Definition, { report }: UserContext) { for (const [name, scheme] of referencedSchemes.entries()) { if (scheme.defined) continue; for (const reportedFromLocation of scheme.from) { @@ -69,10 +71,10 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = (opts: { } }, PathItem: { - enter(pathItem: Oas2PathItem | Oas3PathItem, { key }: UserContext) { + enter(pathItem: Oas2PathItem | Oas3PathItem, { key }: UserContext) { path = key as string; }, - Operation(operation: Oas2Operation | Oas3Operation, { location, key }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { location, key }: UserContext) { const isException = opts.exceptions?.some( (item) => item.path === path && diff --git a/packages/core/src/rules/common/tags-alphabetical.ts b/packages/core/src/rules/common/tags-alphabetical.ts index 63f85f2f52..0411406c4e 100644 --- a/packages/core/src/rules/common/tags-alphabetical.ts +++ b/packages/core/src/rules/common/tags-alphabetical.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Definition, Oas2Tag } from '../../typings/swagger'; -import type { Oas3Definition, Oas3Tag } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Definition, Oas3Tag } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const TagsAlphabetical: Oas3Rule | Oas2Rule = ({ ignoreCase = false }) => { return { - Root(root: Oas2Definition | Oas3Definition, { report, location }: UserContext) { + Root(root: Oas2Definition | Oas3Definition, { report, location }: UserContext) { if (!root.tags) return; for (let i = 0; i < root.tags.length - 1; i++) { if (getTagName(root.tags[i], ignoreCase) > getTagName(root.tags[i + 1], ignoreCase)) { diff --git a/packages/core/src/rules/oas3/array-parameter-serialization.ts b/packages/core/src/rules/oas3/array-parameter-serialization.ts index 2ca6e206e5..8f48a28c28 100644 --- a/packages/core/src/rules/oas3/array-parameter-serialization.ts +++ b/packages/core/src/rules/oas3/array-parameter-serialization.ts @@ -12,7 +12,7 @@ export const ArrayParameterSerialization: Oas3Rule = ( ): Oas3Visitor => { return { Parameter: { - leave(node: Oas3Parameter, ctx) { + leave(node: Oas3Parameter, ctx) { if (!node.schema) { return; } @@ -32,7 +32,7 @@ export const ArrayParameterSerialization: Oas3Rule = ( }; function shouldReportMissingStyleAndExplode( - node: Oas3Parameter, + node: Oas3Parameter, schema: Oas3_1Schema, options: ArrayParameterSerializationOptions ) { diff --git a/packages/core/src/rules/oas3/component-name-unique.ts b/packages/core/src/rules/oas3/component-name-unique.ts index cbf21fd555..8a83365e88 100644 --- a/packages/core/src/rules/oas3/component-name-unique.ts +++ b/packages/core/src/rules/oas3/component-name-unique.ts @@ -7,6 +7,7 @@ import type { Oas3RequestBody, Oas3Response, Oas3Schema, + Oas3_1Schema, OasRef, } from '../../typings/openapi'; @@ -54,7 +55,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { }, }, Root: { - leave(root: Oas3Definition, ctx: UserContext) { + leave(root: Oas3Definition, ctx: UserContext) { components.forEach((value, key, _) => { if (value.absolutePointers.size > 1) { const component = getComponentFromKey(key); @@ -90,7 +91,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { if (options.responses != 'off') { rule.NamedResponses = { - Response(_: Oas3Response, { location }: UserContext) { + Response(_: Oas3Response, { location }: UserContext) { addComponentFromAbsoluteLocation(TYPE_NAME_RESPONSE, location); }, }; @@ -98,7 +99,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { if (options.parameters != 'off') { rule.NamedParameters = { - Parameter(_: Oas3Parameter, { location }: UserContext) { + Parameter(_: Oas3Parameter, { location }: UserContext) { addComponentFromAbsoluteLocation(TYPE_NAME_PARAMETER, location); }, }; @@ -106,7 +107,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { if (options.requestBodies != 'off') { rule.NamedRequestBodies = { - RequestBody(_: Oas3RequestBody, { location }: UserContext) { + RequestBody(_: Oas3RequestBody, { location }: UserContext) { addComponentFromAbsoluteLocation(TYPE_NAME_REQUEST_BODY, location); }, }; diff --git a/packages/core/src/rules/other/stats.ts b/packages/core/src/rules/other/stats.ts index 3ba9a152cc..faf1808195 100644 --- a/packages/core/src/rules/other/stats.ts +++ b/packages/core/src/rules/other/stats.ts @@ -1,4 +1,4 @@ -import type { Oas3Parameter, OasRef, Oas3Tag } from '../../typings/openapi'; +import type { Oas3Schema, Oas3_1Schema, Oas3Parameter, OasRef, Oas3Tag } from '../../typings/openapi'; import type { Oas2Parameter } from '../../typings/swagger'; import type { StatsAccumulator } from '../../typings/common'; @@ -58,7 +58,7 @@ export const Stats = (statsAccumulator: StatsAccumulator) => { }, }, Parameter: { - leave(parameter: Oas2Parameter | Oas3Parameter) { + leave(parameter: Oas2Parameter | Oas3Parameter) { statsAccumulator.parameters.items!.add(parameter.name); }, }, diff --git a/packages/core/src/types/oas3_1.ts b/packages/core/src/types/oas3_1.ts index c9c5de778d..3f3d74e58a 100755 --- a/packages/core/src/types/oas3_1.ts +++ b/packages/core/src/types/oas3_1.ts @@ -182,6 +182,8 @@ export const Schema: NodeType = { const: null, $comment: { type: 'string' }, 'x-tags': { type: 'array', items: { type: 'string' } }, + $dynamicAnchor: { type: 'string' }, + $dynamicRef: { type: 'string' }, }, extensionsPrefix: 'x-', }; diff --git a/packages/core/src/typings/openapi.ts b/packages/core/src/typings/openapi.ts index 9497f91e71..0a70230525 100644 --- a/packages/core/src/typings/openapi.ts +++ b/packages/core/src/typings/openapi.ts @@ -1,13 +1,13 @@ -export interface Oas3Definition { +export interface Oas3Definition { openapi: string; info?: Oas3Info; servers?: Oas3Server[]; - paths?: Oas3Paths; - components?: Oas3Components; + paths?: Oas3Paths; + components?: Oas3Components; security?: Oas3SecurityRequirement[]; tags?: Oas3Tag[]; externalDocs?: Oas3ExternalDocs; - 'x-webhooks'?: Oas3_1Webhooks; + 'x-webhooks'?: Oas3_1Webhooks; } export interface Oas3Info { @@ -32,8 +32,8 @@ export interface Oas3ServerVariable { description?: string; } -export interface Oas3Paths { - [path: string]: Referenced; +export interface Oas3Paths { + [path: string]: Referenced>; } export interface OasRef { $ref: string; @@ -41,19 +41,20 @@ export interface OasRef { export type Referenced = OasRef | T; -export interface Oas3PathItem { +export interface Oas3PathItem { summary?: string; description?: string; - get?: Oas3Operation; - put?: Oas3Operation; - post?: Oas3Operation; - delete?: Oas3Operation; - options?: Oas3Operation; - head?: Oas3Operation; - patch?: Oas3Operation; - trace?: Oas3Operation; + get?: Oas3Operation; + put?: Oas3Operation; + post?: Oas3Operation; + delete?: Oas3Operation; + options?: Oas3Operation; + head?: Oas3Operation; + patch?: Oas3Operation; + trace?: Oas3Operation; servers?: Oas3Server[]; - parameters?: Array>; + parameters?: Array>>; + $ref?: Referenced; } export interface Oas3XCodeSample { @@ -62,16 +63,16 @@ export interface Oas3XCodeSample { source: string; } -export interface Oas3Operation { +export interface Oas3Operation { tags?: string[]; summary?: string; description?: string; externalDocs?: Oas3ExternalDocs; operationId?: string; - parameters?: Array>; - requestBody?: Referenced; - responses: Oas3Responses; - callbacks?: { [name: string]: Referenced }; + parameters?: Array>>; + requestBody?: Referenced>; + responses: Oas3Responses; + callbacks?: { [name: string]: Referenced> }; deprecated?: boolean; security?: Oas3SecurityRequirement[]; servers?: Oas3Server[]; @@ -80,7 +81,7 @@ export interface Oas3Operation { 'x-hideTryItPanel'?: boolean; } -export interface Oas3Parameter { +export interface Oas3Parameter { name: string; in?: Oas3ParameterLocation; description?: string; @@ -90,10 +91,10 @@ export interface Oas3Parameter { style?: Oas3ParameterStyle; explode?: boolean; allowReserved?: boolean; - schema?: Referenced; + schema?: Referenced; example?: unknown; examples?: { [media: string]: Referenced }; - content?: { [media: string]: Oas3MediaType }; + content?: { [media: string]: Oas3MediaType }; } export interface Oas3Example { @@ -112,7 +113,7 @@ export interface Oas3Xml { } // common fields for OpenAPI Schema v3.x -interface Oas3XSchemaBase { +interface Oas3XSchemaBase { $ref?: string; description?: string; default?: unknown; @@ -123,8 +124,10 @@ interface Oas3XSchemaBase { format?: string; externalDocs?: Oas3ExternalDocs; discriminator?: Oas3Discriminator; - nullable?: boolean; - + oneOf?: T[]; + anyOf?: T[]; + allOf?: T[]; + not?: T; title?: string; multipleOf?: number; maximum?: number; @@ -132,9 +135,11 @@ interface Oas3XSchemaBase { maxLength?: number; minLength?: number; pattern?: string; + items?: boolean | T; maxItems?: number; minItems?: number; uniqueItems?: boolean; + additionalProperties?: boolean | T; maxProperties?: number; minProperties?: number; enum?: unknown[]; @@ -144,30 +149,27 @@ interface Oas3XSchemaBase { 'x-tags'?: string[]; } -export interface Oas3Schema extends Oas3XSchemaBase { +export interface Oas3Schema extends Oas3XSchemaBase { type?: string; properties?: { [name: string]: Referenced }; - additionalProperties?: boolean | Oas3Schema; - items?: Oas3Schema; exclusiveMaximum?: boolean; exclusiveMinimum?: boolean; - oneOf?: Oas3Schema[]; - anyOf?: Oas3Schema[]; - allOf?: Oas3Schema[]; - not?: Oas3Schema; + nullable?: boolean; } -export interface Oas3_1Schema extends Oas3XSchemaBase { +export interface Oas3_1Schema extends Oas3XSchemaBase { + $id?: string; + $schema?: string; + $anchor?: string; + $dynamicAnchor?: string; + $dynamicRef?: string; + $defs?: { [name: string]: Referenced }; + $vocabulary?: { [uri: string]: boolean }; + $comment?: string; type?: string | string[]; properties?: { [name: string]: Referenced }; - additionalProperties?: boolean | Oas3_1Schema; examples?: unknown[]; prefixItems?: Oas3_1Schema[]; - items?: Oas3_1Schema; - oneOf?: Oas3_1Schema[]; - anyOf?: Oas3_1Schema[]; - allOf?: Oas3_1Schema[]; - not?: Oas3_1Schema; exclusiveMaximum?: number; exclusiveMinimum?: number; const?: unknown; @@ -181,19 +183,19 @@ export interface Oas3_1Schema extends Oas3XSchemaBase { dependentRequired?: { [name: string]: string[] }; dependentSchemas?: { [name: string]: Referenced }; patternProperties?: { [name: string]: Referenced }; - unevaluatedItems?: Oas3_1Schema; - unevaluatedProperties?: Oas3_1Schema; + unevaluatedItems?: boolean | Oas3_1Schema; + unevaluatedProperties?: boolean | Oas3_1Schema; contentSchema?: Oas3_1Schema; contentMediaType?: string; contentEncoding?: string; } -export interface Oas3_1Definition extends Oas3Definition { - webhooks?: Oas3_1Webhooks; +export interface Oas3_1Definition extends Oas3Definition { + webhooks?: Oas3_1Webhooks; } -export interface Oas3_1Webhooks { - [webhook: string]: Referenced; +export interface Oas3_1Webhooks { + [webhook: string]: Referenced>; } export interface Oas3Discriminator { @@ -202,16 +204,16 @@ export interface Oas3Discriminator { 'x-explicitMappingOnly'?: boolean; } -export interface Oas3MediaType { - schema?: Referenced; +export interface Oas3MediaType { + schema?: Referenced; example?: unknown; examples?: { [name: string]: Referenced }; - encoding?: { [field: string]: Oas3Encoding }; + encoding?: { [field: string]: Oas3Encoding }; } -export interface Oas3Encoding { +export interface Oas3Encoding { contentType: string; - headers?: { [name: string]: Referenced }; + headers?: { [name: string]: Referenced> }; style: Oas3ParameterStyle; explode: boolean; allowReserved: boolean; @@ -227,46 +229,55 @@ export type Oas3ParameterStyle = | 'pipeDelimited' | 'deepObject'; -export interface Oas3RequestBody { +export interface Oas3RequestBody { description?: string; required?: boolean; - content: { [mime: string]: Oas3MediaType }; + content: { [mime: string]: Oas3MediaType }; } -export interface Oas3Responses { - [code: string]: Oas3Response; +export interface Oas3Responses { + [code: string]: Oas3Response; } -export interface Oas3Response { +export interface Oas3Response { description?: string; - headers?: { [name: string]: Referenced }; - content?: { [mime: string]: Oas3MediaType }; + headers?: { [name: string]: Referenced> }; + content?: { [mime: string]: Oas3MediaType }; links?: { [name: string]: Referenced }; } export interface Oas3Link { - $ref?: string; + operationRef?: string; + operationId?: string; + parameters?: { [ name: string]: unknown }; + requestBody?: unknown; + description?: string; + server?: Oas3Server; } -export type Oas3Header = Omit; +export type Oas3Header = Omit, 'in' | 'name'>; -export interface Oas3Callback { - [name: string]: Oas3PathItem; +export interface Oas3Callback { + [name: string]: Oas3PathItem; } -export interface Oas3Components { - schemas?: { [name: string]: Referenced }; - responses?: { [name: string]: Referenced }; - parameters?: { [name: string]: Referenced }; +export interface Oas3Components { + schemas?: { [name: string]: Referenced }; + responses?: { [name: string]: Referenced> }; + parameters?: { [name: string]: Referenced> }; examples?: { [name: string]: Referenced }; - requestBodies?: { [name: string]: Referenced }; - headers?: { [name: string]: Referenced }; + requestBodies?: { [name: string]: Referenced> }; + headers?: { [name: string]: Referenced> }; securitySchemes?: { [name: string]: Referenced }; links?: { [name: string]: Referenced }; - callbacks?: { [name: string]: Referenced }; + callbacks?: { [name: string]: Referenced> }; +} + +export interface Oas3_1Components>{ + pathItems?: { [name: string]: Referenced> }; } -export type Oas3ComponentName = keyof Oas3Components; +export type Oas3ComponentName = keyof Oas3Components; export interface Oas3SecurityRequirement { [name: string]: string[]; diff --git a/packages/core/src/visitors.ts b/packages/core/src/visitors.ts index 61b7282ec2..fe262276b9 100644 --- a/packages/core/src/visitors.ts +++ b/packages/core/src/visitors.ts @@ -10,8 +10,10 @@ import type { Oas3Info, Oas3Contact, Oas3Components, + Oas3_1Components, Oas3License, Oas3Schema, + Oas3_1Schema, Oas3Header, Oas3Parameter, Oas3Operation, @@ -152,7 +154,7 @@ export type BaseVisitor = { }; type Oas3FlatVisitor = { - Root?: VisitFunctionOrObject; + Root?: VisitFunctionOrObject>; Tag?: VisitFunctionOrObject; ExternalDocs?: VisitFunctionOrObject; Server?: VisitFunctionOrObject; @@ -161,36 +163,36 @@ type Oas3FlatVisitor = { Info?: VisitFunctionOrObject; Contact?: VisitFunctionOrObject; License?: VisitFunctionOrObject; - Paths?: VisitFunctionOrObject>; - PathItem?: VisitFunctionOrObject; - Callback?: VisitFunctionOrObject; - CallbacksMap?: VisitFunctionOrObject>; - Parameter?: VisitFunctionOrObject; - Operation?: VisitFunctionOrObject; - RequestBody?: VisitFunctionOrObject; - MediaTypesMap?: VisitFunctionOrObject>; - MediaType?: VisitFunctionOrObject; + Paths?: VisitFunctionOrObject>>; + PathItem?: VisitFunctionOrObject>; + Callback?: VisitFunctionOrObject>; + CallbacksMap?: VisitFunctionOrObject>>; + Parameter?: VisitFunctionOrObject>; + Operation?: VisitFunctionOrObject>; + RequestBody?: VisitFunctionOrObject>; + MediaTypesMap?: VisitFunctionOrObject>>; + MediaType?: VisitFunctionOrObject>; Example?: VisitFunctionOrObject; - Encoding?: VisitFunctionOrObject; - Header?: VisitFunctionOrObject; - Responses?: VisitFunctionOrObject>; - Response?: VisitFunctionOrObject; + Encoding?: VisitFunctionOrObject>; + Header?: VisitFunctionOrObject>; + Responses?: VisitFunctionOrObject>>; + Response?: VisitFunctionOrObject>; Link?: VisitFunctionOrObject; Schema?: VisitFunctionOrObject; Xml?: VisitFunctionOrObject; SchemaProperties?: VisitFunctionOrObject>; DiscriminatorMapping?: VisitFunctionOrObject>; Discriminator?: VisitFunctionOrObject; - Components?: VisitFunctionOrObject; + Components?: VisitFunctionOrObject | Oas3_1Components>; NamedSchemas?: VisitFunctionOrObject>; - NamedResponses?: VisitFunctionOrObject>; - NamedParameters?: VisitFunctionOrObject>; + NamedResponses?: VisitFunctionOrObject>>; + NamedParameters?: VisitFunctionOrObject>>; NamedExamples?: VisitFunctionOrObject>; - NamedRequestBodies?: VisitFunctionOrObject>; - NamedHeaders?: VisitFunctionOrObject>; + NamedRequestBodies?: VisitFunctionOrObject>>; + NamedHeaders?: VisitFunctionOrObject>>; NamedSecuritySchemes?: VisitFunctionOrObject>; NamedLinks?: VisitFunctionOrObject>; - NamedCallbacks?: VisitFunctionOrObject>; + NamedCallbacks?: VisitFunctionOrObject>>; ImplicitFlow?: VisitFunctionOrObject; PasswordFlow?: VisitFunctionOrObject; ClientCredentials?: VisitFunctionOrObject; From 6a0547fa5f803cbda4c6646656b9b22c07c60a87 Mon Sep 17 00:00:00 2001 From: Jeremy Fiel <32110157+jeremyfiel@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:21:46 +0000 Subject: [PATCH 3/6] apply suggestions, still unable to run e2e tests --- packages/cli/src/commands/join.ts | 18 ++++-- packages/cli/src/commands/split/index.ts | 32 +++++----- packages/cli/src/commands/split/types.ts | 29 +-------- .../common/media-type-examples-override.ts | 8 +-- .../common/operation-description-override.ts | 4 +- .../oas3/remove-unused-components.ts | 22 +++++-- packages/core/src/index.ts | 4 +- .../src/rules/common/no-ambiguous-paths.ts | 4 +- .../rules/common/no-http-verbs-in-paths.ts | 4 +- .../src/rules/common/no-identical-paths.ts | 4 +- .../common/no-invalid-parameter-examples.ts | 4 +- .../src/rules/common/operation-description.ts | 4 +- .../common/operation-operationId-unique.ts | 4 +- .../common/operation-operationId-url-safe.ts | 4 +- .../src/rules/common/operation-operationId.ts | 4 +- .../common/operation-parameters-unique.ts | 6 +- .../rules/common/operation-singular-tag.ts | 4 +- .../src/rules/common/operation-summary.ts | 4 +- .../src/rules/common/operation-tag-defined.ts | 6 +- .../src/rules/common/parameter-description.ts | 4 +- .../rules/common/path-excludes-patterns.ts | 4 +- .../src/rules/common/path-http-verbs-order.ts | 4 +- .../src/rules/common/path-params-defined.ts | 6 +- .../rules/common/response-contains-header.ts | 4 +- .../core/src/rules/common/security-defined.ts | 9 ++- .../src/rules/common/tags-alphabetical.ts | 7 ++- .../oas3/array-parameter-serialization.ts | 13 ++-- .../src/rules/oas3/component-name-unique.ts | 11 ++-- packages/core/src/rules/other/stats.ts | 4 +- packages/core/src/typings/openapi.ts | 61 ++++++++++--------- packages/core/src/utils.ts | 2 + packages/core/src/visitors.ts | 15 +++-- 32 files changed, 165 insertions(+), 148 deletions(-) diff --git a/packages/cli/src/commands/join.ts b/packages/cli/src/commands/join.ts index 8671e0a35d..916d8e70c0 100644 --- a/packages/cli/src/commands/join.ts +++ b/packages/cli/src/commands/join.ts @@ -23,14 +23,17 @@ import { isObject, isString, keysOf } from '../utils/js-utils'; import { COMPONENTS, OPENAPI3_METHOD } from './split/types'; import { crawl, startsWithComponents } from './split'; -import type { Oas3Definition, Document, Oas3Tag, Referenced } from '@redocly/openapi-core'; +import type { Document, Referenced } from '@redocly/openapi-core'; import type { BundleResult } from '@redocly/openapi-core/lib/bundle'; import type { + Oas3Definition, + Oas3_1Definition, Oas3Parameter, Oas3PathItem, Oas3Server, - Oas3_1Definition, + Oas3Tag, } from '@redocly/openapi-core/lib/typings/openapi'; +import type { StrictObject } from '@redocly/openapi-core/lib/utils'; import type { CommandArgs } from '../wrapper'; import type { VerifyConfigOptions } from '../types'; @@ -311,7 +314,7 @@ export async function handleJoin({ } } - function collectServers(openapi: Oas3Definition) { + function collectServers(openapi: Oas3Definition | Oas3_1Definition) { const { servers } = openapi; if (servers) { if (!joinedDef.hasOwnProperty('servers')) { @@ -325,7 +328,10 @@ export async function handleJoin({ } } - function collectExternalDocs(openapi: Oas3Definition, { api }: JoinDocumentContext) { + function collectExternalDocs( + openapi: Oas3Definition | Oas3_1Definition, + { api }: JoinDocumentContext + ) { const { externalDocs } = openapi; if (externalDocs) { if (joinedDef.hasOwnProperty('externalDocs')) { @@ -339,7 +345,7 @@ export async function handleJoin({ } function collectPaths( - openapi: Oas3Definition, + openapi: Oas3Definition | Oas3_1Definition, { apiFilename, apiTitle, @@ -567,7 +573,7 @@ export async function handleJoin({ function collectWebhooks( oasVersion: SpecVersion, - openapi: Oas3_1Definition, + openapi: StrictObject, { apiFilename, apiTitle, diff --git a/packages/cli/src/commands/split/index.ts b/packages/cli/src/commands/split/index.ts index 0c5881610d..0ebea40e8b 100644 --- a/packages/cli/src/commands/split/index.ts +++ b/packages/cli/src/commands/split/index.ts @@ -22,20 +22,18 @@ import { OPENAPI3_COMPONENT_NAMES, } from './types'; -import type { OasRef } from '@redocly/openapi-core'; +import type { Oas3Definition, Oas3_1Definition, Oas2Definition } from '@redocly/openapi-core'; import type { - Definition, - Oas2Definition, Oas3Schema, - Oas3Definition, - Oas3_1Definition, - Oas3Components, + Oas3_1Schema, + Oas3ComponentsBase, + Oas3_1Components, Oas3ComponentName, - ComponentsFiles, - RefObject, Oas3PathItem, + OasRef, Referenced, -} from './types'; +} from '@redocly/openapi-core/lib/typings/openapi'; +import type { ComponentsFiles, Definition, RefObject } from './types'; import type { CommandArgs } from '../../wrapper'; import type { VerifyConfigOptions } from '../../types'; @@ -239,7 +237,7 @@ function doesFileDiffer(filename: string, componentData: any) { function removeEmptyComponents( openapi: Oas3Definition | Oas3_1Definition, - componentType: Oas3ComponentName + componentType: Oas3ComponentName ) { if (openapi.components && isEmptyObject(openapi.components[componentType])) { delete openapi.components[componentType]; @@ -264,15 +262,17 @@ function getFileNamePath(componentDirPath: string, componentName: string, ext: s } function gatherComponentsFiles( - components: Oas3Components, + components: Oas3ComponentsBase | Oas3_1Components, componentsFiles: ComponentsFiles, - componentType: Oas3ComponentName, + componentType: Oas3ComponentName, componentName: string, filename: string ) { let inherits: string[] = []; if (componentType === OPENAPI3_COMPONENT.Schemas) { - inherits = ((components?.[componentType]?.[componentName] as Oas3Schema)?.allOf || []) + inherits = ( + (components?.[componentType]?.[componentName] as Oas3Schema | Oas3_1Schema)?.allOf || [] + ) .map(({ $ref }) => $ref) .filter(isTruthy); } @@ -347,7 +347,9 @@ function iterateComponents( componentTypes.forEach(iterateComponentTypes); // eslint-disable-next-line no-inner-declarations - function iterateAndGatherComponentsFiles(componentType: Oas3ComponentName) { + function iterateAndGatherComponentsFiles( + componentType: Oas3ComponentName + ) { const componentDirPath = path.join(componentsDir, componentType); for (const componentName of Object.keys(components?.[componentType] || {})) { const filename = getFileNamePath(componentDirPath, componentName, ext); @@ -356,7 +358,7 @@ function iterateComponents( } // eslint-disable-next-line no-inner-declarations - function iterateComponentTypes(componentType: Oas3ComponentName) { + function iterateComponentTypes(componentType: Oas3ComponentName) { const componentDirPath = path.join(componentsDir, componentType); createComponentDir(componentDirPath, componentType); for (const componentName of Object.keys(components?.[componentType] || {})) { diff --git a/packages/cli/src/commands/split/types.ts b/packages/cli/src/commands/split/types.ts index 2b56b24ca5..bf46fa30e5 100644 --- a/packages/cli/src/commands/split/types.ts +++ b/packages/cli/src/commands/split/types.ts @@ -1,29 +1,6 @@ -import { - Oas3Schema, - Oas3_1Schema, - Oas3Definition, - Oas3_1Definition, - Oas3Components, - Oas3PathItem, - Oas3Paths, - Oas3ComponentName, - Oas3_1Webhooks, - Oas2Definition, - Referenced, -} from '@redocly/openapi-core'; -export { - Oas3_1Definition, - Oas3Definition, - Oas2Definition, - Oas3Components, - Oas3Paths, - Oas3PathItem, - Oas3ComponentName, - Oas3_1Schema, - Oas3Schema, - Oas3_1Webhooks, - Referenced, -}; +import type { Oas2Definition } from '@redocly/openapi-core'; +import type { Oas3_1Definition, Oas3Definition } from 'core/src/typings/openapi'; + export type Definition = Oas3_1Definition | Oas3Definition | Oas2Definition; export interface ComponentsFiles { [schemas: string]: any; diff --git a/packages/core/src/decorators/common/media-type-examples-override.ts b/packages/core/src/decorators/common/media-type-examples-override.ts index b2a064943f..8b8037fe37 100644 --- a/packages/core/src/decorators/common/media-type-examples-override.ts +++ b/packages/core/src/decorators/common/media-type-examples-override.ts @@ -2,13 +2,13 @@ import { yamlAndJsonSyncReader } from '../../utils'; import { isRef } from '../../ref-utils'; import type { Oas3Decorator } from '../../visitors'; -import type { Oas3_1Schema, Oas3Operation, Oas3RequestBody, Oas3Response, Oas3Schema } from '../../typings/openapi'; +import type { Oas3Operation, Oas3RequestBody, Oas3Response } from '../../typings/openapi'; import type { NonUndefined, ResolveFn, UserContext } from '../../walk'; export const MediaTypeExamplesOverride: Oas3Decorator = ({ operationIds }) => { return { Operation: { - enter(operation: Oas3Operation, ctx: UserContext) { + enter(operation: Oas3Operation, ctx: UserContext) { const operationId = operation.operationId; if (!operationId) { @@ -23,7 +23,7 @@ export const MediaTypeExamplesOverride: Oas3Decorator = ({ operationIds }) => { if (properties.responses && operation.responses) { for (const responseCode of Object.keys(properties.responses)) { - const resolvedResponse = checkAndResolveRef>( + const resolvedResponse = checkAndResolveRef( operation.responses[responseCode], ctx.resolve ); @@ -46,7 +46,7 @@ export const MediaTypeExamplesOverride: Oas3Decorator = ({ operationIds }) => { } if (properties.request && operation.requestBody) { - const resolvedRequest = checkAndResolveRef>( + const resolvedRequest = checkAndResolveRef( operation.requestBody, ctx.resolve ); diff --git a/packages/core/src/decorators/common/operation-description-override.ts b/packages/core/src/decorators/common/operation-description-override.ts index 46df16160b..c583fb4ff2 100644 --- a/packages/core/src/decorators/common/operation-description-override.ts +++ b/packages/core/src/decorators/common/operation-description-override.ts @@ -2,13 +2,13 @@ import { readFileAsStringSync } from '../../utils'; import type { Oas3Decorator, Oas2Decorator } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Schema, Oas3_1Schema, Oas3Operation } from '../../typings/openapi'; +import type { Oas3Operation } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationDescriptionOverride: Oas3Decorator | Oas2Decorator = ({ operationIds }) => { return { Operation: { - leave(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + leave(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (!operation.operationId) return; if (!operationIds) throw new Error( diff --git a/packages/core/src/decorators/oas3/remove-unused-components.ts b/packages/core/src/decorators/oas3/remove-unused-components.ts index f65ae6d999..a291607a7f 100644 --- a/packages/core/src/decorators/oas3/remove-unused-components.ts +++ b/packages/core/src/decorators/oas3/remove-unused-components.ts @@ -2,17 +2,28 @@ import { isEmptyObject } from '../../utils'; import type { Location } from '../../ref-utils'; import type { Oas3Decorator } from '../../visitors'; -import type { Oas3Schema, Oas3_1Schema, Oas3Components, Oas3Definition } from '../../typings/openapi'; +import type { + Oas3Definition, + Oas3_1Definition, + Oas3ComponentsBase, + Oas3_1Components, + Oas3Schema, + Oas3_1Schema, +} from '../../typings/openapi'; export const RemoveUnusedComponents: Oas3Decorator = () => { const components = new Map< string, - { usedIn: Location[]; componentType?: keyof Oas3Components; name: string } + { + usedIn: Location[]; + componentType?: keyof (Oas3ComponentsBase | Oas3_1Components); + name: string; + } >(); function registerComponent( location: Location, - componentType: keyof Oas3Components, + componentType: keyof (Oas3ComponentsBase | Oas3_1Components), name: string ): void { components.set(location.absolutePointer, { @@ -22,7 +33,10 @@ export const RemoveUnusedComponents: Oas3Decorator = () => { }); } - function removeUnusedComponents(root: Oas3Definition, removedPaths: string[]): number { + function removeUnusedComponents( + root: Oas3Definition | Oas3_1Definition, + removedPaths: string[] + ): number { const removedLengthStart = removedPaths.length; for (const [path, { usedIn, name, componentType }] of components) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d96598fca2..c7758b9397 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -17,14 +17,14 @@ export { ConfigTypes } from './types/redocly-yaml'; export type { Oas3Definition, Oas3_1Definition, - Oas3Components, + Oas3ComponentsBase, + Oas3_1Components, Oas3PathItem, Oas3Paths, Oas3ComponentName, Oas3Schema, Oas3_1Schema, Oas3Tag, - Oas3_1Webhooks, Referenced, OasRef, } from './typings/openapi'; diff --git a/packages/core/src/rules/common/no-ambiguous-paths.ts b/packages/core/src/rules/common/no-ambiguous-paths.ts index 3fcf2c1300..a8d2af5b27 100644 --- a/packages/core/src/rules/common/no-ambiguous-paths.ts +++ b/packages/core/src/rules/common/no-ambiguous-paths.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; -import type { Oas3_1Schema, Oas3Paths, Oas3Schema } from '../../typings/openapi'; +import type { Oas3Paths } from '../../typings/openapi'; import type { Oas2Paths } from '../../typings/swagger'; export const NoAmbiguousPaths: Oas3Rule | Oas2Rule = () => { return { - Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { + Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { const seenPaths: string[] = []; for (const currentPath of Object.keys(pathMap)) { diff --git a/packages/core/src/rules/common/no-http-verbs-in-paths.ts b/packages/core/src/rules/common/no-http-verbs-in-paths.ts index dce002b902..59560154fd 100644 --- a/packages/core/src/rules/common/no-http-verbs-in-paths.ts +++ b/packages/core/src/rules/common/no-http-verbs-in-paths.ts @@ -2,14 +2,14 @@ import { isPathParameter, splitCamelCaseIntoWords } from '../../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2PathItem } from '../../typings/swagger'; -import type { Oas3Schema, Oas3_1Schema, Oas3PathItem } from '../../typings/openapi'; +import type { Oas3PathItem } from '../../typings/openapi'; import type { UserContext } from '../../walk'; const httpMethods = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace']; export const NoHttpVerbsInPaths: Oas3Rule | Oas2Rule = ({ splitIntoWords }) => { return { - PathItem(_path: Oas2PathItem | Oas3PathItem, { key, report, location }: UserContext) { + PathItem(_path: Oas2PathItem | Oas3PathItem, { key, report, location }: UserContext) { const pathKey = key.toString(); if (!pathKey.startsWith('/')) return; const pathSegments = pathKey.split('/'); diff --git a/packages/core/src/rules/common/no-identical-paths.ts b/packages/core/src/rules/common/no-identical-paths.ts index d5da26d802..988dec3e56 100644 --- a/packages/core/src/rules/common/no-identical-paths.ts +++ b/packages/core/src/rules/common/no-identical-paths.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; -import type { Oas3Schema, Oas3_1Schema, Oas3Paths } from '../../typings/openapi'; +import type { Oas3Paths } from '../../typings/openapi'; import type { Oas2Paths } from '../../typings/swagger'; export const NoIdenticalPaths: Oas3Rule | Oas2Rule = () => { return { - Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { + Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) { const Paths = new Map(); for (const pathName of Object.keys(pathMap)) { const id = pathName.replace(/{.+?}/g, '{VARIABLE}'); diff --git a/packages/core/src/rules/common/no-invalid-parameter-examples.ts b/packages/core/src/rules/common/no-invalid-parameter-examples.ts index a20094a655..740335dfeb 100644 --- a/packages/core/src/rules/common/no-invalid-parameter-examples.ts +++ b/packages/core/src/rules/common/no-invalid-parameter-examples.ts @@ -1,13 +1,13 @@ import { getAdditionalPropertiesOption, validateExample } from '../utils'; import type { UserContext } from '../../walk'; -import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Parameter } from '../../typings/openapi'; export const NoInvalidParameterExamples: any = (opts: any) => { const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false; return { Parameter: { - leave(parameter: Oas3Parameter, ctx: UserContext) { + leave(parameter: Oas3Parameter, ctx: UserContext) { if (parameter.example !== undefined) { validateExample( parameter.example, diff --git a/packages/core/src/rules/common/operation-description.ts b/packages/core/src/rules/common/operation-description.ts index b5cd7999b3..6f2bb45c04 100644 --- a/packages/core/src/rules/common/operation-description.ts +++ b/packages/core/src/rules/common/operation-description.ts @@ -3,11 +3,11 @@ import { validateDefinedAndNonEmpty } from '../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Operation } from '../../typings/openapi'; export const OperationDescription: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { validateDefinedAndNonEmpty('description', operation, ctx); }, }; diff --git a/packages/core/src/rules/common/operation-operationId-unique.ts b/packages/core/src/rules/common/operation-operationId-unique.ts index 7819ca2ebd..54aa597abc 100644 --- a/packages/core/src/rules/common/operation-operationId-unique.ts +++ b/packages/core/src/rules/common/operation-operationId-unique.ts @@ -1,13 +1,13 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Operation } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationIdUnique: Oas3Rule | Oas2Rule = () => { const seenOperations = new Set(); return { - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (!operation.operationId) return; if (seenOperations.has(operation.operationId)) { report({ diff --git a/packages/core/src/rules/common/operation-operationId-url-safe.ts b/packages/core/src/rules/common/operation-operationId-url-safe.ts index 7a3ffb920d..5f1c410809 100644 --- a/packages/core/src/rules/common/operation-operationId-url-safe.ts +++ b/packages/core/src/rules/common/operation-operationId-url-safe.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Operation } from '../../typings/openapi'; import type { UserContext } from '../../walk'; // eslint-disable-next-line no-useless-escape @@ -8,7 +8,7 @@ const validUrlSymbols = /^[A-Za-z0-9-._~:/?#\[\]@!\$&'()*+,;=]*$/; export const OperationIdUrlSafe: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (operation.operationId && !validUrlSymbols.test(operation.operationId)) { report({ message: 'Operation `operationId` should not have URL invalid characters.', diff --git a/packages/core/src/rules/common/operation-operationId.ts b/packages/core/src/rules/common/operation-operationId.ts index c4323802ff..b759a2ab01 100644 --- a/packages/core/src/rules/common/operation-operationId.ts +++ b/packages/core/src/rules/common/operation-operationId.ts @@ -3,13 +3,13 @@ import { validateDefinedAndNonEmpty } from '../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Operation } from '../../typings/openapi'; export const OperationOperationId: Oas3Rule | Oas2Rule = () => { return { Root: { PathItem: { - Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { validateDefinedAndNonEmpty('operationId', operation, ctx); }, }, diff --git a/packages/core/src/rules/common/operation-parameters-unique.ts b/packages/core/src/rules/common/operation-parameters-unique.ts index dc4f30a6c4..901dfb26d6 100644 --- a/packages/core/src/rules/common/operation-parameters-unique.ts +++ b/packages/core/src/rules/common/operation-parameters-unique.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Parameter } from '../../typings/swagger'; -import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Parameter } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationParametersUnique: Oas3Rule | Oas2Rule = () => { @@ -13,7 +13,7 @@ export const OperationParametersUnique: Oas3Rule | Oas2Rule = () => { seenPathParams = new Set(); }, Parameter( - parameter: Oas2Parameter | Oas3Parameter, + parameter: Oas2Parameter | Oas3Parameter, { report, key, parentLocations }: UserContext ) { const paramId = `${parameter.in}___${parameter.name}`; @@ -30,7 +30,7 @@ export const OperationParametersUnique: Oas3Rule | Oas2Rule = () => { seenOperationParams = new Set(); }, Parameter( - parameter: Oas2Parameter | Oas3Parameter, + parameter: Oas2Parameter | Oas3Parameter, { report, key, parentLocations }: UserContext ) { const paramId = `${parameter.in}___${parameter.name}`; diff --git a/packages/core/src/rules/common/operation-singular-tag.ts b/packages/core/src/rules/common/operation-singular-tag.ts index df3e914e5c..6e149f9ca9 100644 --- a/packages/core/src/rules/common/operation-singular-tag.ts +++ b/packages/core/src/rules/common/operation-singular-tag.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Operation } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationSingularTag: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (operation.tags && operation.tags.length > 1) { report({ message: 'Operation `tags` object should have only one tag.', diff --git a/packages/core/src/rules/common/operation-summary.ts b/packages/core/src/rules/common/operation-summary.ts index 171b1b747e..18743ad9ed 100644 --- a/packages/core/src/rules/common/operation-summary.ts +++ b/packages/core/src/rules/common/operation-summary.ts @@ -3,11 +3,11 @@ import { validateDefinedAndNonEmpty } from '../utils'; import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { UserContext } from '../../walk'; import type { Oas2Operation } from '../../typings/swagger'; -import type { Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Operation } from '../../typings/openapi'; export const OperationSummary: Oas3Rule | Oas2Rule = () => { return { - Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, ctx: UserContext) { validateDefinedAndNonEmpty('summary', operation, ctx); }, }; diff --git a/packages/core/src/rules/common/operation-tag-defined.ts b/packages/core/src/rules/common/operation-tag-defined.ts index ea70225e14..804381aea0 100644 --- a/packages/core/src/rules/common/operation-tag-defined.ts +++ b/packages/core/src/rules/common/operation-tag-defined.ts @@ -1,16 +1,16 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Definition, Oas2Operation } from '../../typings/swagger'; -import type { Oas3Definition, Oas3Operation, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Definition, Oas3_1Definition, Oas3Operation } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const OperationTagDefined: Oas3Rule | Oas2Rule = () => { let definedTags: Set; return { - Root(root: Oas2Definition | Oas3Definition) { + Root(root: Oas2Definition | Oas3Definition | Oas3_1Definition) { definedTags = new Set((root.tags ?? []).map((t) => t.name)); }, - Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { if (operation.tags) { for (let i = 0; i < operation.tags.length; i++) { if (!definedTags.has(operation.tags[i])) { diff --git a/packages/core/src/rules/common/parameter-description.ts b/packages/core/src/rules/common/parameter-description.ts index 2580307b7b..f1495b29e4 100644 --- a/packages/core/src/rules/common/parameter-description.ts +++ b/packages/core/src/rules/common/parameter-description.ts @@ -1,11 +1,11 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Parameter } from '../../typings/swagger'; -import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Parameter } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const ParameterDescription: Oas3Rule | Oas2Rule = () => { return { - Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { + Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { if (parameter.description === undefined) { report({ message: 'Parameter object description must be present.', diff --git a/packages/core/src/rules/common/path-excludes-patterns.ts b/packages/core/src/rules/common/path-excludes-patterns.ts index 3304c5314f..14988cbffd 100644 --- a/packages/core/src/rules/common/path-excludes-patterns.ts +++ b/packages/core/src/rules/common/path-excludes-patterns.ts @@ -1,11 +1,11 @@ import type { Oas2Rule, Oas3Rule } from '../../visitors'; import type { Oas2PathItem } from '../../typings/swagger'; -import type { Oas3PathItem, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3PathItem } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const PathExcludesPatterns: Oas3Rule | Oas2Rule = ({ patterns }) => { return { - PathItem(_path: Oas2PathItem | Oas3PathItem, { report, key, location }: UserContext) { + PathItem(_path: Oas2PathItem | Oas3PathItem, { report, key, location }: UserContext) { if (!patterns) throw new Error(`Parameter "patterns" is not provided for "path-excludes-patterns" rule`); const pathKey = key.toString(); diff --git a/packages/core/src/rules/common/path-http-verbs-order.ts b/packages/core/src/rules/common/path-http-verbs-order.ts index 4f1f2615b6..3bfe0bcddd 100644 --- a/packages/core/src/rules/common/path-http-verbs-order.ts +++ b/packages/core/src/rules/common/path-http-verbs-order.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2PathItem } from '../../typings/swagger'; -import type { Oas3PathItem, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3PathItem } from '../../typings/openapi'; import type { UserContext } from '../../walk'; const defaultOrder = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace']; @@ -12,7 +12,7 @@ export const PathHttpVerbsOrder: Oas3Rule | Oas2Rule = (opts: any) => { } return { - PathItem(path: Oas2PathItem | Oas3PathItem, { report, location }: UserContext) { + PathItem(path: Oas2PathItem | Oas3PathItem, { report, location }: UserContext) { const httpVerbs = Object.keys(path).filter((k) => order.includes(k)); for (let i = 0; i < httpVerbs.length - 1; i++) { diff --git a/packages/core/src/rules/common/path-params-defined.ts b/packages/core/src/rules/common/path-params-defined.ts index 88c5ed50b5..bbb696b860 100644 --- a/packages/core/src/rules/common/path-params-defined.ts +++ b/packages/core/src/rules/common/path-params-defined.ts @@ -1,6 +1,6 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Parameter } from '../../typings/swagger'; -import type { Oas3Schema, Oas3_1Schema, Oas3Parameter } from '../../typings/openapi'; +import type { Oas3Parameter } from '../../typings/openapi'; import type { UserContext } from '../../walk'; const pathRegex = /\{([a-zA-Z0-9_.-]+)\}+/g; @@ -20,7 +20,7 @@ export const PathParamsDefined: Oas3Rule | Oas2Rule = () => { Array.from(key!.toString().matchAll(pathRegex)).map((m) => m[1]) ); }, - Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { + Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { if (parameter.in === 'path' && parameter.name) { definedPathParams.add(parameter.name); if (!pathTemplateParams.has(parameter.name)) { @@ -48,7 +48,7 @@ export const PathParamsDefined: Oas3Rule | Oas2Rule = () => { } } }, - Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { + Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { if (parameter.in === 'path' && parameter.name) { definedOperationParams.add(parameter.name); if (!pathTemplateParams.has(parameter.name)) { diff --git a/packages/core/src/rules/common/response-contains-header.ts b/packages/core/src/rules/common/response-contains-header.ts index ba875329cc..38ad9d6f75 100644 --- a/packages/core/src/rules/common/response-contains-header.ts +++ b/packages/core/src/rules/common/response-contains-header.ts @@ -2,7 +2,7 @@ import { getMatchingStatusCodeRange } from '../../utils'; import type { Oas2Rule, Oas3Rule } from '../../visitors'; import type { UserContext } from '../../walk'; -import type { Oas3Response, Oas3Schema, Oas3_1Schema } from '../../typings/openapi'; +import type { Oas3Response } from '../../typings/openapi'; import type { Oas2Response } from '../../typings/swagger'; export const ResponseContainsHeader: Oas3Rule | Oas2Rule = (options) => { @@ -10,7 +10,7 @@ export const ResponseContainsHeader: Oas3Rule | Oas2Rule = (options) => { return { Operation: { Response: { - enter: (response: Oas2Response | Oas3Response, { report, location, key }: UserContext) => { + enter: (response: Oas2Response | Oas3Response, { report, location, key }: UserContext) => { const expectedHeaders = names[key] || names[getMatchingStatusCodeRange(key)] || diff --git a/packages/core/src/rules/common/security-defined.ts b/packages/core/src/rules/common/security-defined.ts index b27dfed6f6..9ebe205786 100644 --- a/packages/core/src/rules/common/security-defined.ts +++ b/packages/core/src/rules/common/security-defined.ts @@ -8,9 +8,8 @@ import type { Oas2SecurityScheme, } from '../../typings/swagger'; import type { - Oas3Schema, - Oas3_1Schema, Oas3Definition, + Oas3_1Definition, Oas3Operation, Oas3PathItem, Oas3SecurityScheme, @@ -33,7 +32,7 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = (opts: { return { Root: { - leave(root: Oas2Definition | Oas3Definition, { report }: UserContext) { + leave(root: Oas2Definition | Oas3Definition | Oas3_1Definition, { report }: UserContext) { for (const [name, scheme] of referencedSchemes.entries()) { if (scheme.defined) continue; for (const reportedFromLocation of scheme.from) { @@ -71,10 +70,10 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = (opts: { } }, PathItem: { - enter(pathItem: Oas2PathItem | Oas3PathItem, { key }: UserContext) { + enter(pathItem: Oas2PathItem | Oas3PathItem, { key }: UserContext) { path = key as string; }, - Operation(operation: Oas2Operation | Oas3Operation, { location, key }: UserContext) { + Operation(operation: Oas2Operation | Oas3Operation, { location, key }: UserContext) { const isException = opts.exceptions?.some( (item) => item.path === path && diff --git a/packages/core/src/rules/common/tags-alphabetical.ts b/packages/core/src/rules/common/tags-alphabetical.ts index 0411406c4e..2257d51a51 100644 --- a/packages/core/src/rules/common/tags-alphabetical.ts +++ b/packages/core/src/rules/common/tags-alphabetical.ts @@ -1,11 +1,14 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors'; import type { Oas2Definition, Oas2Tag } from '../../typings/swagger'; -import type { Oas3Schema, Oas3_1Schema, Oas3Definition, Oas3Tag } from '../../typings/openapi'; +import type { Oas3Definition, Oas3Tag, Oas3_1Definition } from '../../typings/openapi'; import type { UserContext } from '../../walk'; export const TagsAlphabetical: Oas3Rule | Oas2Rule = ({ ignoreCase = false }) => { return { - Root(root: Oas2Definition | Oas3Definition, { report, location }: UserContext) { + Root( + root: Oas2Definition | Oas3Definition | Oas3_1Definition, + { report, location }: UserContext + ) { if (!root.tags) return; for (let i = 0; i < root.tags.length - 1; i++) { if (getTagName(root.tags[i], ignoreCase) > getTagName(root.tags[i + 1], ignoreCase)) { diff --git a/packages/core/src/rules/oas3/array-parameter-serialization.ts b/packages/core/src/rules/oas3/array-parameter-serialization.ts index 8f48a28c28..fc897c1f35 100644 --- a/packages/core/src/rules/oas3/array-parameter-serialization.ts +++ b/packages/core/src/rules/oas3/array-parameter-serialization.ts @@ -12,15 +12,18 @@ export const ArrayParameterSerialization: Oas3Rule = ( ): Oas3Visitor => { return { Parameter: { - leave(node: Oas3Parameter, ctx) { + leave(node, ctx) { if (!node.schema) { return; } - const schema = isRef(node.schema) - ? ctx.resolve(node.schema).node - : (node.schema as Oas3_1Schema); + const schema = ( + isRef(node.schema) ? ctx.resolve(node.schema).node : node.schema + ) as Oas3_1Schema; - if (schema && shouldReportMissingStyleAndExplode(node, schema, options)) { + if ( + schema && + shouldReportMissingStyleAndExplode(node as Oas3Parameter, schema, options) + ) { ctx.report({ message: `Parameter \`${node.name}\` should have \`style\` and \`explode \` fields`, location: ctx.location, diff --git a/packages/core/src/rules/oas3/component-name-unique.ts b/packages/core/src/rules/oas3/component-name-unique.ts index 8a83365e88..b6475345bd 100644 --- a/packages/core/src/rules/oas3/component-name-unique.ts +++ b/packages/core/src/rules/oas3/component-name-unique.ts @@ -3,6 +3,7 @@ import type { Problem, UserContext } from '../../walk'; import type { Oas2Rule, Oas3Rule, Oas3Visitor } from '../../visitors'; import type { Oas3Definition, + Oas3_1Definition, Oas3Parameter, Oas3RequestBody, Oas3Response, @@ -55,7 +56,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { }, }, Root: { - leave(root: Oas3Definition, ctx: UserContext) { + leave(root: Oas3Definition | Oas3_1Definition, ctx: UserContext) { components.forEach((value, key, _) => { if (value.absolutePointers.size > 1) { const component = getComponentFromKey(key); @@ -83,7 +84,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { if (options.schemas != 'off') { rule.NamedSchemas = { - Schema(_: Oas3Schema, { location }: UserContext) { + Schema(_: Oas3Schema | Oas3_1Schema, { location }: UserContext) { addComponentFromAbsoluteLocation(TYPE_NAME_SCHEMA, location); }, }; @@ -91,7 +92,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { if (options.responses != 'off') { rule.NamedResponses = { - Response(_: Oas3Response, { location }: UserContext) { + Response(_: Oas3Response, { location }: UserContext) { addComponentFromAbsoluteLocation(TYPE_NAME_RESPONSE, location); }, }; @@ -99,7 +100,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { if (options.parameters != 'off') { rule.NamedParameters = { - Parameter(_: Oas3Parameter, { location }: UserContext) { + Parameter(_: Oas3Parameter, { location }: UserContext) { addComponentFromAbsoluteLocation(TYPE_NAME_PARAMETER, location); }, }; @@ -107,7 +108,7 @@ export const ComponentNameUnique: Oas3Rule | Oas2Rule = (options) => { if (options.requestBodies != 'off') { rule.NamedRequestBodies = { - RequestBody(_: Oas3RequestBody, { location }: UserContext) { + RequestBody(_: Oas3RequestBody, { location }: UserContext) { addComponentFromAbsoluteLocation(TYPE_NAME_REQUEST_BODY, location); }, }; diff --git a/packages/core/src/rules/other/stats.ts b/packages/core/src/rules/other/stats.ts index faf1808195..3ba9a152cc 100644 --- a/packages/core/src/rules/other/stats.ts +++ b/packages/core/src/rules/other/stats.ts @@ -1,4 +1,4 @@ -import type { Oas3Schema, Oas3_1Schema, Oas3Parameter, OasRef, Oas3Tag } from '../../typings/openapi'; +import type { Oas3Parameter, OasRef, Oas3Tag } from '../../typings/openapi'; import type { Oas2Parameter } from '../../typings/swagger'; import type { StatsAccumulator } from '../../typings/common'; @@ -58,7 +58,7 @@ export const Stats = (statsAccumulator: StatsAccumulator) => { }, }, Parameter: { - leave(parameter: Oas2Parameter | Oas3Parameter) { + leave(parameter: Oas2Parameter | Oas3Parameter) { statsAccumulator.parameters.items!.add(parameter.name); }, }, diff --git a/packages/core/src/typings/openapi.ts b/packages/core/src/typings/openapi.ts index 0a70230525..03b8ff9fea 100644 --- a/packages/core/src/typings/openapi.ts +++ b/packages/core/src/typings/openapi.ts @@ -1,19 +1,26 @@ -export interface Oas3Definition { +// common fields for OAS descriptions v3.x +interface Oas3DefinitionBase { openapi: string; info?: Oas3Info; servers?: Oas3Server[]; paths?: Oas3Paths; - components?: Oas3Components; + components?: T extends Oas3_1Schema ? Oas3_1Components : Oas3ComponentsBase; security?: Oas3SecurityRequirement[]; tags?: Oas3Tag[]; externalDocs?: Oas3ExternalDocs; - 'x-webhooks'?: Oas3_1Webhooks; +} + +export interface Oas3Definition extends Oas3DefinitionBase { + 'x-webhooks'?: Oas3Webhooks; +} + +export interface Oas3_1Definition extends Oas3DefinitionBase { + webhooks?: Oas3Webhooks; } export interface Oas3Info { title: string; version: string; - description?: string; termsOfService?: string; contact?: Oas3Contact; @@ -32,7 +39,7 @@ export interface Oas3ServerVariable { description?: string; } -export interface Oas3Paths { +export interface Oas3Paths { [path: string]: Referenced>; } export interface OasRef { @@ -41,7 +48,7 @@ export interface OasRef { export type Referenced = OasRef | T; -export interface Oas3PathItem { +export interface Oas3PathItem { summary?: string; description?: string; get?: Oas3Operation; @@ -54,7 +61,6 @@ export interface Oas3PathItem { trace?: Oas3Operation; servers?: Oas3Server[]; parameters?: Array>>; - $ref?: Referenced; } export interface Oas3XCodeSample { @@ -63,7 +69,7 @@ export interface Oas3XCodeSample { source: string; } -export interface Oas3Operation { +export interface Oas3Operation { tags?: string[]; summary?: string; description?: string; @@ -81,7 +87,7 @@ export interface Oas3Operation { 'x-hideTryItPanel'?: boolean; } -export interface Oas3Parameter { +export interface Oas3Parameter { name: string; in?: Oas3ParameterLocation; description?: string; @@ -115,6 +121,8 @@ export interface Oas3Xml { // common fields for OpenAPI Schema v3.x interface Oas3XSchemaBase { $ref?: string; + properties?: { [name: string]: Referenced }; + additionalProperties?: boolean | { [name: string]: Referenced }; description?: string; default?: unknown; required?: string[]; @@ -139,7 +147,6 @@ interface Oas3XSchemaBase { maxItems?: number; minItems?: number; uniqueItems?: boolean; - additionalProperties?: boolean | T; maxProperties?: number; minProperties?: number; enum?: unknown[]; @@ -151,7 +158,6 @@ interface Oas3XSchemaBase { export interface Oas3Schema extends Oas3XSchemaBase { type?: string; - properties?: { [name: string]: Referenced }; exclusiveMaximum?: boolean; exclusiveMinimum?: boolean; nullable?: boolean; @@ -167,7 +173,6 @@ export interface Oas3_1Schema extends Oas3XSchemaBase { $vocabulary?: { [uri: string]: boolean }; $comment?: string; type?: string | string[]; - properties?: { [name: string]: Referenced }; examples?: unknown[]; prefixItems?: Oas3_1Schema[]; exclusiveMaximum?: number; @@ -190,11 +195,7 @@ export interface Oas3_1Schema extends Oas3XSchemaBase { contentEncoding?: string; } -export interface Oas3_1Definition extends Oas3Definition { - webhooks?: Oas3_1Webhooks; -} - -export interface Oas3_1Webhooks { +export interface Oas3Webhooks { [webhook: string]: Referenced>; } @@ -204,14 +205,14 @@ export interface Oas3Discriminator { 'x-explicitMappingOnly'?: boolean; } -export interface Oas3MediaType { +export interface Oas3MediaType { schema?: Referenced; example?: unknown; examples?: { [name: string]: Referenced }; encoding?: { [field: string]: Oas3Encoding }; } -export interface Oas3Encoding { +export interface Oas3Encoding { contentType: string; headers?: { [name: string]: Referenced> }; style: Oas3ParameterStyle; @@ -229,7 +230,7 @@ export type Oas3ParameterStyle = | 'pipeDelimited' | 'deepObject'; -export interface Oas3RequestBody { +export interface Oas3RequestBody { description?: string; required?: boolean; content: { [mime: string]: Oas3MediaType }; @@ -239,7 +240,7 @@ export interface Oas3Responses { [code: string]: Oas3Response; } -export interface Oas3Response { +export interface Oas3Response { description?: string; headers?: { [name: string]: Referenced> }; content?: { [mime: string]: Oas3MediaType }; @@ -249,19 +250,23 @@ export interface Oas3Response { export interface Oas3Link { operationRef?: string; operationId?: string; - parameters?: { [ name: string]: unknown }; + parameters?: { [name: string]: unknown }; requestBody?: unknown; description?: string; server?: Oas3Server; } -export type Oas3Header = Omit, 'in' | 'name'>; +export type Oas3Header = Omit< + Oas3Parameter, + 'in' | 'name' +>; -export interface Oas3Callback { +export interface Oas3Callback { [name: string]: Oas3PathItem; } -export interface Oas3Components { +// common fields for OAS components v3.x +export interface Oas3ComponentsBase { schemas?: { [name: string]: Referenced }; responses?: { [name: string]: Referenced> }; parameters?: { [name: string]: Referenced> }; @@ -273,11 +278,11 @@ export interface Oas3Components { callbacks?: { [name: string]: Referenced> }; } -export interface Oas3_1Components>{ - pathItems?: { [name: string]: Referenced> }; +export interface Oas3_1Components extends Oas3ComponentsBase { + pathItems?: { [name: string]: Referenced> }; } -export type Oas3ComponentName = keyof Oas3Components; +export type Oas3ComponentName = keyof Oas3ComponentsBase; export interface Oas3SecurityRequirement { [name: string]: string[]; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 6b6d0476e3..16d5940bb2 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -354,3 +354,5 @@ export function dequal(foo: any, bar: any): boolean { } export type CollectFn = (value: unknown) => void; + +export type StrictObject = T & { [key: string]: undefined }; diff --git a/packages/core/src/visitors.ts b/packages/core/src/visitors.ts index fe262276b9..cb7321e553 100644 --- a/packages/core/src/visitors.ts +++ b/packages/core/src/visitors.ts @@ -6,10 +6,11 @@ import type { UserContext, ResolveResult, ProblemSeverity } from './walk'; import type { Location } from './ref-utils'; import type { Oas3Definition, + Oas3_1Definition, Oas3ExternalDocs, Oas3Info, Oas3Contact, - Oas3Components, + Oas3ComponentsBase, Oas3_1Components, Oas3License, Oas3Schema, @@ -154,7 +155,7 @@ export type BaseVisitor = { }; type Oas3FlatVisitor = { - Root?: VisitFunctionOrObject>; + Root?: VisitFunctionOrObject; Tag?: VisitFunctionOrObject; ExternalDocs?: VisitFunctionOrObject; Server?: VisitFunctionOrObject; @@ -178,17 +179,21 @@ type Oas3FlatVisitor = { Responses?: VisitFunctionOrObject>>; Response?: VisitFunctionOrObject>; Link?: VisitFunctionOrObject; - Schema?: VisitFunctionOrObject; + Schema?: VisitFunctionOrObject; Xml?: VisitFunctionOrObject; SchemaProperties?: VisitFunctionOrObject>; DiscriminatorMapping?: VisitFunctionOrObject>; Discriminator?: VisitFunctionOrObject; - Components?: VisitFunctionOrObject | Oas3_1Components>; + Components?: VisitFunctionOrObject< + Oas3ComponentsBase | Oas3_1Components + >; NamedSchemas?: VisitFunctionOrObject>; NamedResponses?: VisitFunctionOrObject>>; NamedParameters?: VisitFunctionOrObject>>; NamedExamples?: VisitFunctionOrObject>; - NamedRequestBodies?: VisitFunctionOrObject>>; + NamedRequestBodies?: VisitFunctionOrObject< + Record> + >; NamedHeaders?: VisitFunctionOrObject>>; NamedSecuritySchemes?: VisitFunctionOrObject>; NamedLinks?: VisitFunctionOrObject>; From ac145cfb78f8d01a54349346fe6cd569c8642c91 Mon Sep 17 00:00:00 2001 From: Jeremy Fiel <32110157+jeremyfiel@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:32:39 +0000 Subject: [PATCH 4/6] fixed tests, updated snapshots --- packages/cli/src/commands/split/types.ts | 2 +- packages/core/src/__tests__/lint.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/split/types.ts b/packages/cli/src/commands/split/types.ts index bf46fa30e5..c77757c032 100644 --- a/packages/cli/src/commands/split/types.ts +++ b/packages/cli/src/commands/split/types.ts @@ -1,5 +1,5 @@ import type { Oas2Definition } from '@redocly/openapi-core'; -import type { Oas3_1Definition, Oas3Definition } from 'core/src/typings/openapi'; +import type { Oas3_1Definition, Oas3Definition } from '@redocly/openapi-core/lib/typings/openapi'; export type Definition = Oas3_1Definition | Oas3Definition | Oas2Definition; export interface ComponentsFiles { diff --git a/packages/core/src/__tests__/lint.test.ts b/packages/core/src/__tests__/lint.test.ts index 8e6eef519d..195f7d5aec 100644 --- a/packages/core/src/__tests__/lint.test.ts +++ b/packages/core/src/__tests__/lint.test.ts @@ -1680,7 +1680,7 @@ describe('lint', () => { outdent` openapi: 3.0.4 info: - title: test json schema validation keyword - allOf should use an OAS Schema, not JSON Schema + title: test json schema validation keyword $schema - should use an OAS Schema, not JSON Schema version: 1.0.0 paths: '/thing': @@ -1694,7 +1694,7 @@ describe('lint', () => { schema: $schema: http://json-schema.org/draft-04/schema# type: object - properties: {} + properties: {} `, '' ); @@ -1723,7 +1723,7 @@ describe('lint', () => { }, ], "message": "Property \`$schema\` is not expected here.", - "ruleId": "spec", + "ruleId": "struct", "severity": "error", "suggest": [], }, @@ -1736,7 +1736,7 @@ describe('lint', () => { outdent` openapi: 3.1.1 info: - title: test json schema validation keyword - allOf should use an OAS Schema, not JSON Schema + title: test json schema validation keyword $schema - should allow a JSON Schema version: 1.0.0 paths: '/thing': @@ -1750,7 +1750,7 @@ describe('lint', () => { schema: $schema: http://json-schema.org/draft-04/schema# type: object - properties: {} + properties: {} `, '' ); From d754c7351cb02c411514aed73fdc1dfe9ddb21ac Mon Sep 17 00:00:00 2001 From: Jeremy Fiel <32110157+jeremyfiel@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:08:51 +0000 Subject: [PATCH 5/6] refactor(types): Oas3Components/Oas3_1Components --- packages/cli/src/commands/split/index.ts | 4 ++-- .../core/src/decorators/oas3/remove-unused-components.ts | 8 +++----- packages/core/src/typings/openapi.ts | 8 +++++--- packages/core/src/visitors.ts | 6 ++---- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/commands/split/index.ts b/packages/cli/src/commands/split/index.ts index 0ebea40e8b..2cde3c2195 100644 --- a/packages/cli/src/commands/split/index.ts +++ b/packages/cli/src/commands/split/index.ts @@ -26,7 +26,7 @@ import type { Oas3Definition, Oas3_1Definition, Oas2Definition } from '@redocly/ import type { Oas3Schema, Oas3_1Schema, - Oas3ComponentsBase, + Oas3Components, Oas3_1Components, Oas3ComponentName, Oas3PathItem, @@ -262,7 +262,7 @@ function getFileNamePath(componentDirPath: string, componentName: string, ext: s } function gatherComponentsFiles( - components: Oas3ComponentsBase | Oas3_1Components, + components: Oas3Components | Oas3_1Components, componentsFiles: ComponentsFiles, componentType: Oas3ComponentName, componentName: string, diff --git a/packages/core/src/decorators/oas3/remove-unused-components.ts b/packages/core/src/decorators/oas3/remove-unused-components.ts index a291607a7f..b7b7369935 100644 --- a/packages/core/src/decorators/oas3/remove-unused-components.ts +++ b/packages/core/src/decorators/oas3/remove-unused-components.ts @@ -5,10 +5,8 @@ import type { Oas3Decorator } from '../../visitors'; import type { Oas3Definition, Oas3_1Definition, - Oas3ComponentsBase, + Oas3Components, Oas3_1Components, - Oas3Schema, - Oas3_1Schema, } from '../../typings/openapi'; export const RemoveUnusedComponents: Oas3Decorator = () => { @@ -16,14 +14,14 @@ export const RemoveUnusedComponents: Oas3Decorator = () => { string, { usedIn: Location[]; - componentType?: keyof (Oas3ComponentsBase | Oas3_1Components); + componentType?: keyof (Oas3Components | Oas3_1Components); name: string; } >(); function registerComponent( location: Location, - componentType: keyof (Oas3ComponentsBase | Oas3_1Components), + componentType: keyof (Oas3Components | Oas3_1Components), name: string ): void { components.set(location.absolutePointer, { diff --git a/packages/core/src/typings/openapi.ts b/packages/core/src/typings/openapi.ts index 03b8ff9fea..18beccca8e 100644 --- a/packages/core/src/typings/openapi.ts +++ b/packages/core/src/typings/openapi.ts @@ -4,7 +4,7 @@ interface Oas3DefinitionBase { info?: Oas3Info; servers?: Oas3Server[]; paths?: Oas3Paths; - components?: T extends Oas3_1Schema ? Oas3_1Components : Oas3ComponentsBase; + components?: T extends Oas3_1Schema ? Oas3_1Components : Oas3Components; security?: Oas3SecurityRequirement[]; tags?: Oas3Tag[]; externalDocs?: Oas3ExternalDocs; @@ -278,10 +278,12 @@ export interface Oas3ComponentsBase { callbacks?: { [name: string]: Referenced> }; } -export interface Oas3_1Components extends Oas3ComponentsBase { - pathItems?: { [name: string]: Referenced> }; +export interface Oas3_1Components extends Oas3ComponentsBase { + pathItems?: { [name: string]: Referenced> }; } +export interface Oas3Components extends Oas3ComponentsBase {} + export type Oas3ComponentName = keyof Oas3ComponentsBase; export interface Oas3SecurityRequirement { diff --git a/packages/core/src/visitors.ts b/packages/core/src/visitors.ts index cb7321e553..4dece6af4d 100644 --- a/packages/core/src/visitors.ts +++ b/packages/core/src/visitors.ts @@ -10,7 +10,7 @@ import type { Oas3ExternalDocs, Oas3Info, Oas3Contact, - Oas3ComponentsBase, + Oas3Components, Oas3_1Components, Oas3License, Oas3Schema, @@ -184,9 +184,7 @@ type Oas3FlatVisitor = { SchemaProperties?: VisitFunctionOrObject>; DiscriminatorMapping?: VisitFunctionOrObject>; Discriminator?: VisitFunctionOrObject; - Components?: VisitFunctionOrObject< - Oas3ComponentsBase | Oas3_1Components - >; + Components?: VisitFunctionOrObject; NamedSchemas?: VisitFunctionOrObject>; NamedResponses?: VisitFunctionOrObject>>; NamedParameters?: VisitFunctionOrObject>>; From b5361c32c37a9b9eac0105922191b42d7b854b97 Mon Sep 17 00:00:00 2001 From: Jeremy Fiel <32110157+jeremyfiel@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:16:04 +0000 Subject: [PATCH 6/6] apply suggestions --- packages/core/src/index.ts | 2 +- packages/core/src/typings/openapi.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c7758b9397..449ab05fbe 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -17,7 +17,7 @@ export { ConfigTypes } from './types/redocly-yaml'; export type { Oas3Definition, Oas3_1Definition, - Oas3ComponentsBase, + Oas3Components, Oas3_1Components, Oas3PathItem, Oas3Paths, diff --git a/packages/core/src/typings/openapi.ts b/packages/core/src/typings/openapi.ts index 18beccca8e..c35452b6cb 100644 --- a/packages/core/src/typings/openapi.ts +++ b/packages/core/src/typings/openapi.ts @@ -122,7 +122,7 @@ export interface Oas3Xml { interface Oas3XSchemaBase { $ref?: string; properties?: { [name: string]: Referenced }; - additionalProperties?: boolean | { [name: string]: Referenced }; + additionalProperties?: boolean | T; description?: string; default?: unknown; required?: string[]; @@ -236,7 +236,7 @@ export interface Oas3RequestBody }; } -export interface Oas3Responses { +export interface Oas3Responses { [code: string]: Oas3Response; } @@ -284,7 +284,8 @@ export interface Oas3_1Components extends Oas3ComponentsBase { export interface Oas3Components extends Oas3ComponentsBase {} -export type Oas3ComponentName = keyof Oas3ComponentsBase; +export type Oas3ComponentName = + keyof Oas3ComponentsBase; export interface Oas3SecurityRequirement { [name: string]: string[];