Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"simplify"
],
"dependencies": {
"@netcracker/qubership-apihub-json-crawl": "1.0.4",
"@netcracker/qubership-apihub-json-crawl": "feature-support-oas-extension-changes-classification",
"object-hash": "3.0.0",
"fast-equals": "4.0.3"
},
Expand Down
103 changes: 52 additions & 51 deletions src/rules/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,29 +244,25 @@ const OPEN_API_COMPONENTS_REPLACES: Record<string, ReplaceMapping> = {
[OPEN_API_PROPERTY_EXAMPLES]: TO_EMPTY_OBJECT_MAPPING,
}

const openApiExtensionRulesFunction: (elseRules: NormalizationRules | (() => NormalizationRules)) => NormalizationRules = (elseRules) => ({
'/*': (ctx) => {
const { key } = ctx
return typeof key === 'string' && key.startsWith('x-')
? {
isExtension: true,
validate: checkType(...TYPE_JSON_ANY),
merge: resolvers.last,
'/**': { validate: checkType(...TYPE_JSON_ANY) },
}
: typeof elseRules === 'function' ? elseRules() : elseRules
},
})

const openApiExtensionRules: NormalizationRules = openApiExtensionRulesFunction({ validate: () => false })
const openApiSpecificationExtensionRules = {
'/^': {
'x-': {
isExtension: true,
validate: checkType(...TYPE_JSON_ANY),
merge: resolvers.last,
'/*': { validate: checkType(...TYPE_JSON_ANY) },
'/**': { validate: checkType(...TYPE_JSON_ANY) },
},
},
} as NormalizationRules

const openApiExternalDocsRules: NormalizationRules = {
'/externalDocs': {
validate: checkType(TYPE_OBJECT),
merge: resolvers.last,
'/description': { validate: checkType(TYPE_STRING) },
'/url': { validate: checkType(TYPE_STRING) },
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
},
}

Expand All @@ -282,9 +278,8 @@ const openApiExamplesRules: NormalizationRules = {
validate: checkType(TYPE_OBJECT),
merge: resolvers.last,
'/*': {
...openApiExtensionRulesFunction({
validate: checkType(...TYPE_JSON_ANY),
}),
'/*': { validate: checkType(...TYPE_JSON_ANY) },
...openApiSpecificationExtensionRules,
},
'/**': { validate: checkType(...TYPE_JSON_ANY) },
},
Expand All @@ -306,12 +301,12 @@ const openApiServerRules: NormalizationRules = {
},
'/default': { validate: checkType(TYPE_STRING) },
'/description': { validate: checkType(TYPE_STRING) },
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
validate: checkType(TYPE_OBJECT),
},
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
}

Expand Down Expand Up @@ -350,7 +345,7 @@ const openApiLinksRules: NormalizationRules = {
},
'/description': { validate: checkType(TYPE_STRING) },
'/server': openApiServerRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
validate: checkType(TYPE_OBJECT),
Expand All @@ -363,8 +358,9 @@ const openApiJsonSchemaExtensionRules = (): NormalizationRules => ({
unify: [
valueDefaults(OPEN_API_XML_DEFAULTS),
],
...openApiExtensionRulesFunction({ validate: checkType(...TYPE_JSON_ANY) }),
'/*': { validate: checkType(...TYPE_JSON_ANY) },
'/**': { validate: checkType(...TYPE_JSON_ANY) },
...openApiSpecificationExtensionRules,
},
'/discriminator': {
validate: checkType(TYPE_OBJECT),
Expand All @@ -383,7 +379,7 @@ const openApiJsonSchemaExtensionRules = (): NormalizationRules => ({
},
},
...openApiExternalDocsRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
})

const customFor30JsonSchemaRulesFactory = (): NormalizationRules => {
Expand Down Expand Up @@ -488,11 +484,11 @@ const openApiMediaTypesRules = (version: OpenApiSpecVersion): NormalizationRules
valueReplaces(OPEN_API_ENCODING_REPLACES),
],
validate: checkType(TYPE_OBJECT),
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
},
validate: checkType(TYPE_OBJECT),
},
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
unify: [
valueDefaults(OPEN_API_MEDIA_TYPE_DEFAULTS),
valueReplaces(OPEN_API_MEDIA_TYPE_REPLACES),
Expand Down Expand Up @@ -520,7 +516,7 @@ const openApiHeadersRules = (version: OpenApiSpecVersion): NormalizationRules =>
...openApiExampleRules,
...openApiExamplesRules,
'/schema': openApiJsonSchemaRules(version),
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
unify: [
valueDefaults(OPEN_API_HEADER_DEFAULTS),
Expand Down Expand Up @@ -580,7 +576,7 @@ const openApiParametersRules = (version: OpenApiSpecVersion): NormalizationRules
...openApiJsonSchemaRules(version),
newDataLayer: true,
}),
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
unify: [
valueDefaults(OPEN_API_PARAMETER_DEFAULTS),
Expand All @@ -596,7 +592,7 @@ const openApiRequestRules = (version: OpenApiSpecVersion): NormalizationRules =>
'/description': { validate: checkType(TYPE_STRING) },
'/required': { validate: checkType(TYPE_BOOLEAN) },
'/content': openApiMediaTypesRules(version),
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
unify: [
valueDefaults(OPEN_API_REQUEST_BODY_DEFAULTS),
],
Expand All @@ -607,12 +603,12 @@ const openApiRequestRules = (version: OpenApiSpecVersion): NormalizationRules =>
})

const openApiResponsesRules = (version: OpenApiSpecVersion): NormalizationRules => ({
...openApiExtensionRulesFunction({
'/*': {
'/description': { validate: checkType(TYPE_STRING) },
'/headers': openApiHeadersRules(version),
'/content': openApiMediaTypesRules(version),
'/links': openApiLinksRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
unify: [
valueDefaults(OPEN_API_RESPONSE_DEFAULTS),
valueReplaces(OPEN_API_RESPONSE_REPLACES),
Expand All @@ -621,7 +617,8 @@ const openApiResponsesRules = (version: OpenApiSpecVersion): NormalizationRules
deprecation: {
inlineDescriptionSuffixCalculator: ctx => `${ctx.suffix} '${ctx.key.toString()}'`,
},
}),
},
...openApiSpecificationExtensionRules,
deprecation: { inlineDescriptionSuffixCalculator: () => 'in response' },
validate: checkType(TYPE_OBJECT),
})
Expand All @@ -634,7 +631,7 @@ const openApiPathItemRules = (version: OpenApiSpecVersion): NormalizationRules =
'/*': openApiServerRules,
validate: checkType(TYPE_ARRAY),
},
...openApiExtensionRulesFunction({
'/*': {
deprecation: {
deprecationResolver: (ctx) => OPEN_API_DEPRECATION_RESOLVER(ctx),
descriptionCalculator: ctx => `[Deprecated] operation ${ctx.key.toString().toUpperCase()} ${ctx.suffix}`,
Expand All @@ -648,8 +645,9 @@ const openApiPathItemRules = (version: OpenApiSpecVersion): NormalizationRules =
...openApiExternalDocsRules,
'/operationId': { validate: checkType(TYPE_STRING) },
'/callbacks': {
'/*': {
...openApiExtensionRulesFunction(() => openApiPathItemRules(version)),
'/*': {
'/*': () => openApiPathItemRules(version),
...openApiSpecificationExtensionRules,
},
},
'/deprecated': { validate: checkType(TYPE_BOOLEAN) },
Expand All @@ -658,13 +656,14 @@ const openApiPathItemRules = (version: OpenApiSpecVersion): NormalizationRules =
'/parameters': openApiParametersRules(version),
'/requestBody': openApiRequestRules(version),
'/responses': openApiResponsesRules(version),
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
unify: [
valueDefaults(OPEN_API_OPERATION_DEFAULTS),
valueReplaces(OPEN_API_OPERATION_REPLACES),
],
validate: checkType(TYPE_OBJECT),
}),
},
...openApiSpecificationExtensionRules,
'/parameters': openApiParametersRules(version),
validate: checkType(TYPE_OBJECT),
unify: pathItemsUnification,
Expand All @@ -681,17 +680,17 @@ export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules =>
'/name': { validate: checkType(TYPE_STRING) },
'/url': { validate: checkType(TYPE_STRING) },
'/email': { validate: checkType(TYPE_STRING) },
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
'/license': {
'/name': { validate: checkType(TYPE_STRING) },
'/url': { validate: checkType(TYPE_STRING) },
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
'/version': { validate: checkType(TYPE_STRING) },
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
...openApiExternalDocsRules,
Expand All @@ -702,13 +701,14 @@ export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules =>
'/name': { validate: checkType(TYPE_STRING) },
'/description': { validate: checkType(TYPE_STRING) },
...openApiExternalDocsRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
validate: checkType(TYPE_ARRAY),
},
'/paths': {
...openApiExtensionRulesFunction(openApiPathItemRules(version)),
'/*': openApiPathItemRules(version),
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
'/components': {
Expand All @@ -725,37 +725,37 @@ export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules =>
'/authorizationUrl': { validate: checkType(TYPE_STRING) },
'/refreshUrl': { validate: checkType(TYPE_STRING) },
'/scopes': openApiOAuthScopesRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
'/password': {
'/tokenUrl': { validate: checkType(TYPE_STRING) },
'/refreshUrl': { validate: checkType(TYPE_STRING) },
'/scopes': openApiOAuthScopesRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
'/clientCredentials': {
'/tokenUrl': { validate: checkType(TYPE_STRING) },
'/refreshUrl': { validate: checkType(TYPE_STRING) },
'/scopes': openApiOAuthScopesRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
'/authorizationCode': {
'/authorizationUrl': { validate: checkType(TYPE_STRING) },
'/tokenUrl': { validate: checkType(TYPE_STRING) },
'/refreshUrl': { validate: checkType(TYPE_STRING) },
'/scopes': openApiOAuthScopesRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
},
'/openIdConnectUrl': { validate: checkType(TYPE_STRING) },
validate: checkType(TYPE_OBJECT),
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
},
validate: checkType(TYPE_OBJECT),
},
Expand All @@ -776,18 +776,19 @@ export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules =>
'/headers': openApiHeadersRules(version),
'/callbacks': {
'/*': {
...openApiExtensionRulesFunction(() => openApiPathItemRules(version)),
'/*': () => openApiPathItemRules(version),
...openApiSpecificationExtensionRules,
},
},
...openApiExamplesRules,
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
validate: checkType(TYPE_OBJECT),
unify: [
valueDefaults(OPEN_API_COMPONENTS_DEFAULTS),
valueReplaces(OPEN_API_COMPONENTS_REPLACES),
],
},
...openApiExtensionRules,
...openApiSpecificationExtensionRules,
unify: [
valueDefaults(OPEN_API_ROOT_DEFAULTS),
valueReplaces(OPEN_API_ROOT_REPLACES),
Expand Down
19 changes: 19 additions & 0 deletions test/oas/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,4 +650,23 @@ describe('OAS 3.1 Type validations: array of types', () => {

expect(result).toHaveProperty([...schemaPath, 'type'], 'string')
})

it('validates OAS extensions of Paths Object with complex values', () => {
const spec = {
openapi: "3.0.4",
info: {
title: "Test API",
version: "1.0.0"
},
paths: {
"x-custom-extension": {
key: "value"
}
}
}

const result = validate(spec, { validate: true, })

expect(result).toMatchObject(spec)
})
})