Skip to content

Commit 56b033c

Browse files
committed
Merge branch 'release'
2 parents 2d5145e + c6d8fd5 commit 56b033c

File tree

9 files changed

+555
-61
lines changed

9 files changed

+555
-61
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@netcracker/qubership-apihub-api-unifier",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"description": "Tools for JsonSchema/Openapi/GraphQL spec for unified processing",
55
"module": "./dist/index.es.js",
66
"main": "./dist/index.cjs.js",
@@ -46,7 +46,7 @@
4646
},
4747
"devDependencies": {
4848
"@netcracker/qubership-apihub-graphapi": "1.0.8",
49-
"@netcracker/qubership-apihub-npm-gitflow": "3.0.1",
49+
"@netcracker/qubership-apihub-npm-gitflow": "3.1.0",
5050
"@types/object-hash": "3.0.6",
5151
"@types/jest": "29.5.2",
5252
"@types/js-yaml": "4.0.5",

src/normalize.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { validate } from './validate'
1414
import { merge } from './merge'
1515
import { cleanUpSynthetic, deCleanUpSynthetic, deUnify, unify } from './unify'
1616
import { deHash, hash } from './hash'
17+
import { removeOasExtensions } from './remove-oas-extensions'
1718

1819
export const normalize = (value: unknown, options: NormalizeOptions = {}) => {
1920
const optionsWithDefaults = createOptionsWithDefaults(options)
@@ -23,6 +24,7 @@ export const normalize = (value: unknown, options: NormalizeOptions = {}) => {
2324
if (optionsWithDefaults.mergeAllOf) { spec = merge(spec, optionsWithDefaults) }
2425
if (optionsWithDefaults.unify) { spec = unify(spec, optionsWithDefaults) }
2526
if (optionsWithDefaults.mergeAllOf && !optionsWithDefaults.unify && !optionsWithDefaults.allowNotValidSyntheticChanges) { spec = cleanUpSynthetic(spec, optionsWithDefaults) }
27+
if (optionsWithDefaults.removeOasExtensions) { spec = removeOasExtensions(spec, optionsWithDefaults) }
2628
if (optionsWithDefaults.hashFlag) { spec = hash(spec, optionsWithDefaults) }
2729
return spec
2830
}

src/remove-oas-extensions.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
InternalRemoveOasExtensionsOptions,
3+
NormalizationRule,
4+
OpenApiExtensionKey,
5+
RemoveOasExtensionsOptions,
6+
} from './types'
7+
import { resolveSpec, SPEC_TYPE_GRAPH_API } from './spec-type'
8+
import { syncClone, SyncCloneHook } from '@netcracker/qubership-apihub-json-crawl'
9+
import { RULES } from './rules'
10+
import { createCycledJsoHandlerHook } from './cycle-jso'
11+
12+
const createRemoveOasExtensionsHook: (options: InternalRemoveOasExtensionsOptions) => RemoveOasExtensionsCrawlHook = (options) => {
13+
const removeOasExtensionsHook: RemoveOasExtensionsCrawlHook = ({ key, value, rules }) => {
14+
if (rules?.isExtension && options.shouldRemoveOasExtension?.(key as OpenApiExtensionKey)) {
15+
return { value: undefined }
16+
}
17+
18+
return { value }
19+
}
20+
21+
return removeOasExtensionsHook
22+
}
23+
24+
type RemoveOasExtensionsCrawlHook = SyncCloneHook<RemoveOasExtensionsCrawlState, NormalizationRule>
25+
26+
export interface RemoveOasExtensionsCrawlState {}
27+
28+
export const removeOasExtensions = (value: unknown, options?: RemoveOasExtensionsOptions) => {
29+
const internalOptions = {
30+
...options,
31+
}
32+
const spec = resolveSpec(value)
33+
if (spec.type === SPEC_TYPE_GRAPH_API) {
34+
return value
35+
}
36+
const cycledJsoHandlerHook = createCycledJsoHandlerHook<RemoveOasExtensionsCrawlState, NormalizationRule>()
37+
return syncClone<RemoveOasExtensionsCrawlState, NormalizationRule>(
38+
value,
39+
[
40+
cycledJsoHandlerHook,
41+
createRemoveOasExtensionsHook(internalOptions)
42+
],
43+
{ rules: RULES[spec.type] },
44+
)
45+
}

src/rules/openapi.ts

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import {
6666
calculateHeaderName,
6767
calculateHeaderPlace,
6868
calculateParameterName,
69-
nonEmptyString
69+
nonEmptyString,
7070
} from '../deprecated-item-description'
7171
import { OPEN_API_DEPRECATION_RESOLVER } from './openapi.deprecated'
7272

@@ -84,13 +84,13 @@ const TO_EMPTY_OBJECT_MAPPING: ReplaceMapping = {
8484
mapping: new Map([[EMPTY_MARKER, {
8585
value: () => ({}),
8686
reverseMatcher: deepEqualsMatcher({}),
87-
}]])
87+
}]]),
8888
}
8989
const TO_EMPTY_ARRAY_MAPPING: ReplaceMapping = {
9090
mapping: new Map([[EMPTY_MARKER, {
9191
value: () => ([]),
9292
reverseMatcher: deepEqualsMatcher([]),
93-
}]])
93+
}]]),
9494
}
9595

9696
const OPEN_API_30_JSON_SCHEMA_DEFAULTS: Record<string, JsonPrimitiveValue> = {
@@ -206,16 +206,18 @@ const OPEN_API_COMPONENTS_REPLACES: Record<string, ReplaceMapping> = {
206206
[OPEN_API_PROPERTY_EXAMPLES]: TO_EMPTY_OBJECT_MAPPING,
207207
}
208208

209-
const openApiExtensionRulesFunction: (elseRules: NormalizationRules) => NormalizationRules = (elseRules) => ({
210-
'/*': ({ key }) => (
211-
typeof key === 'string' && key.startsWith('x-')
209+
const openApiExtensionRulesFunction: (elseRules: NormalizationRules | (() => NormalizationRules)) => NormalizationRules = (elseRules) => ({
210+
'/*': (ctx) => {
211+
const { key } = ctx
212+
return typeof key === 'string' && key.startsWith('x-')
212213
? {
214+
isExtension: true,
213215
validate: checkType(...TYPE_JSON_ANY),
214216
merge: resolvers.last,
215217
'/**': { validate: checkType(...TYPE_JSON_ANY) },
216-
} as NormalizationRules
217-
: elseRules
218-
),
218+
}
219+
: typeof elseRules === 'function' ? elseRules() : elseRules
220+
},
219221
})
220222

221223
const openApiExtensionRules: NormalizationRules = openApiExtensionRulesFunction({ validate: () => false })
@@ -226,6 +228,7 @@ const openApiExternalDocsRules: NormalizationRules = {
226228
merge: resolvers.last,
227229
'/description': { validate: checkType(TYPE_STRING) },
228230
'/url': { validate: checkType(TYPE_STRING) },
231+
...openApiExtensionRules,
229232
},
230233
}
231234

@@ -240,6 +243,11 @@ const openApiExamplesRules: NormalizationRules = {
240243
'/examples': {
241244
validate: checkType(TYPE_OBJECT),
242245
merge: resolvers.last,
246+
'/*': {
247+
...openApiExtensionRulesFunction({
248+
validate: checkType(...TYPE_JSON_ANY),
249+
}),
250+
},
243251
'/**': { validate: checkType(...TYPE_JSON_ANY) },
244252
},
245253
}
@@ -278,12 +286,12 @@ const openApiServersRules: NormalizationRules = {
278286
const openApiSecurityRules: NormalizationRules = {
279287
'/security': {
280288
'/*': {
281-
...openApiExtensionRulesFunction({
289+
'/*': {
282290
'/*': {
283291
validate: checkType(TYPE_STRING),
284292
},
285293
validate: checkType(TYPE_ARRAY),
286-
}),
294+
},
287295
validate: checkType(TYPE_OBJECT),
288296
},
289297
validate: checkType(TYPE_ARRAY),
@@ -314,6 +322,7 @@ const openApiJsonSchemaExtensionRules = (): NormalizationRules => ({
314322
'/xml': {
315323
validate: checkType(...TYPE_JSON_ANY),
316324
merge: resolvers.mergeObjects,
325+
...openApiExtensionRulesFunction({ validate: checkType(...TYPE_JSON_ANY) }),
317326
'/**': { validate: checkType(...TYPE_JSON_ANY) },
318327
},
319328
'/discriminator': {
@@ -554,7 +563,7 @@ const openApiRequestRules = (version: OpenApiSpecVersion): NormalizationRules =>
554563
})
555564

556565
const openApiResponsesRules = (version: OpenApiSpecVersion): NormalizationRules => ({
557-
'/*': {
566+
...openApiExtensionRulesFunction({
558567
'/description': { validate: checkType(TYPE_STRING) },
559568
'/headers': openApiHeadersRules(version),
560569
'/content': openApiMediaTypesRules(version),
@@ -568,11 +577,55 @@ const openApiResponsesRules = (version: OpenApiSpecVersion): NormalizationRules
568577
deprecation: {
569578
inlineDescriptionSuffixCalculator: ctx => `${ctx.suffix} '${ctx.key.toString()}'`,
570579
},
571-
},
580+
}),
572581
deprecation: { inlineDescriptionSuffixCalculator: () => 'in response' },
573582
validate: checkType(TYPE_OBJECT),
574583
})
575584

585+
const openApiPathItemRules = (version: OpenApiSpecVersion): NormalizationRules => ({
586+
deprecation: { inlineDescriptionSuffixCalculator: ctx => `${ctx.key.toString()}` },
587+
'/summary': { validate: checkType(TYPE_STRING) },
588+
'/description': { validate: checkType(TYPE_STRING) },
589+
'/servers': {
590+
'/*': openApiServerRules,
591+
validate: checkType(TYPE_ARRAY),
592+
},
593+
...openApiExtensionRulesFunction({
594+
deprecation: {
595+
deprecationResolver: (ctx) => OPEN_API_DEPRECATION_RESOLVER(ctx),
596+
descriptionCalculator: ctx => `[Deprecated] operation ${ctx.key.toString().toUpperCase()} ${ctx.suffix}`,
597+
},
598+
'/tags': {
599+
'/*': { validate: checkType(TYPE_STRING) },
600+
validate: checkType(TYPE_ARRAY),
601+
},
602+
'/summary': { validate: checkType(TYPE_STRING) },
603+
'/description': { validate: checkType(TYPE_STRING) },
604+
...openApiExternalDocsRules,
605+
'/operationId': { validate: checkType(TYPE_STRING) },
606+
'/callbacks': {
607+
'/*': {
608+
...openApiExtensionRulesFunction(() => openApiPathItemRules(version)),
609+
},
610+
},
611+
'/deprecated': { validate: checkType(TYPE_BOOLEAN) },
612+
...openApiSecurityRules,
613+
...openApiServersRules,
614+
'/parameters': openApiParametersRules(version),
615+
'/requestBody': openApiRequestRules(version),
616+
'/responses': openApiResponsesRules(version),
617+
...openApiExtensionRules,
618+
unify: [
619+
valueDefaults(OPEN_API_OPERATION_DEFAULTS),
620+
valueReplaces(OPEN_API_OPERATION_REPLACES),
621+
],
622+
validate: checkType(TYPE_OBJECT),
623+
}),
624+
'/parameters': openApiParametersRules(version),
625+
validate: checkType(TYPE_OBJECT),
626+
unify: pathItemsUnification,
627+
})
628+
576629
//TODO no 3.1 specific. Add it when need
577630
export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules => ({
578631
'/openapi': { validate: checkType(TYPE_STRING) },
@@ -611,45 +664,7 @@ export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules =>
611664
validate: checkType(TYPE_ARRAY),
612665
},
613666
'/paths': {
614-
'/*': {
615-
deprecation: { inlineDescriptionSuffixCalculator: ctx => `${ctx.key.toString()}` },
616-
'/summary': { validate: checkType(TYPE_STRING) },
617-
'/description': { validate: checkType(TYPE_STRING) },
618-
'/servers': {
619-
'/*': openApiServerRules,
620-
validate: checkType(TYPE_ARRAY),
621-
},
622-
'/*': {
623-
deprecation: {
624-
deprecationResolver: (ctx) => OPEN_API_DEPRECATION_RESOLVER(ctx),
625-
descriptionCalculator: ctx => `[Deprecated] operation ${ctx.key.toString().toUpperCase()} ${ctx.suffix}`
626-
},
627-
'/tags': {
628-
'/*': { validate: checkType(TYPE_STRING) },
629-
validate: checkType(TYPE_ARRAY),
630-
},
631-
'/summary': { validate: checkType(TYPE_STRING) },
632-
'/description': { validate: checkType(TYPE_STRING) },
633-
...openApiExternalDocsRules,
634-
'/operationId': { validate: checkType(TYPE_STRING) },
635-
// '/callbacks': not supported
636-
'/deprecated': { validate: checkType(TYPE_BOOLEAN) },
637-
...openApiSecurityRules,
638-
...openApiServersRules,
639-
'/parameters': openApiParametersRules(version),
640-
'/requestBody': openApiRequestRules(version),
641-
'/responses': openApiResponsesRules(version),
642-
...openApiExtensionRules,
643-
unify: [
644-
valueDefaults(OPEN_API_OPERATION_DEFAULTS),
645-
valueReplaces(OPEN_API_OPERATION_REPLACES),
646-
],
647-
validate: checkType(TYPE_OBJECT),
648-
},
649-
'/parameters': openApiParametersRules(version),
650-
validate: checkType(TYPE_OBJECT),
651-
unify: pathItemsUnification,
652-
},
667+
...openApiExtensionRulesFunction(openApiPathItemRules(version)),
653668
validate: checkType(TYPE_OBJECT),
654669
},
655670
'/components': {
@@ -715,7 +730,11 @@ export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules =>
715730
validate: checkType(TYPE_OBJECT),
716731
},
717732
'/headers': openApiHeadersRules(version),
718-
// '/callbacks': not supported
733+
'/callbacks': {
734+
'/*': {
735+
...openApiExtensionRulesFunction(() => openApiPathItemRules(version)),
736+
},
737+
},
719738
...openApiExamplesRules,
720739
...openApiExtensionRules,
721740
validate: checkType(TYPE_OBJECT),
@@ -724,6 +743,7 @@ export const openApiRules = (version: OpenApiSpecVersion): NormalizationRules =>
724743
valueReplaces(OPEN_API_COMPONENTS_REPLACES),
725744
],
726745
},
746+
...openApiExtensionRules,
727747
unify: [
728748
valueDefaults(OPEN_API_ROOT_DEFAULTS),
729749
valueReplaces(OPEN_API_ROOT_REPLACES),

src/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ export interface HashOptions {
7878

7979
export interface InternalHashOptions extends HashOptions {}
8080

81+
const OPEN_API_EXTENSION_PREFIX = 'x-'
82+
83+
export type OpenApiExtensionKey = `${typeof OPEN_API_EXTENSION_PREFIX}${string}`
84+
85+
export interface RemoveOasExtensionsOptions {
86+
removeOasExtensions?: boolean
87+
shouldRemoveOasExtension?: (extensionKey: OpenApiExtensionKey) => boolean
88+
}
89+
90+
export interface InternalRemoveOasExtensionsOptions extends RemoveOasExtensionsOptions {}
91+
8192
export interface InternalLiftCombinersOptions extends Omit<LiftCombinersOptions, never>, InternalMergeOptions, HasInternalIgnoreSymbols {
8293
liftCombiners: boolean
8394
}
@@ -115,6 +126,7 @@ export type NormalizeOptions =
115126
& UnifyOptions
116127
& LiftCombinersOptions
117128
& HashOptions
129+
& RemoveOasExtensionsOptions
118130

119131
export type DenormalizeOptions =
120132
ResolveOptions
@@ -213,6 +225,7 @@ export interface NormalizationRule {
213225
readonly hashOwner?: boolean
214226
readonly newDataLayer?: boolean
215227
readonly deprecation?: DeprecationPolicy
228+
readonly isExtension?: boolean
216229
}
217230

218231
export type MergeAndLiftCombinersSyncCloneHook = SyncCloneHook<MergeAndLiftCombinersState, NormalizationRule>

0 commit comments

Comments
 (0)