diff --git a/package-lock.json b/package-lock.json index d71c62dc1..09a2ff723 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,11 @@ "dependencies": { "@babel/runtime-corejs3": "^7.22.15", "@scarf/scarf": "=1.4.0", - "@swagger-api/apidom-core": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-error": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-reference": ">=1.0.0-beta.39 <1.0.0-rc.0", + "@swagger-api/apidom-core": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-error": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-reference": ">=1.0.0-beta.40 <1.0.0-rc.0", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", diff --git a/package.json b/package.json index 3d3dd359a..029dcd050 100644 --- a/package.json +++ b/package.json @@ -74,11 +74,11 @@ "dependencies": { "@babel/runtime-corejs3": "^7.22.15", "@scarf/scarf": "=1.4.0", - "@swagger-api/apidom-core": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-error": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.39 <1.0.0-rc.0", - "@swagger-api/apidom-reference": ">=1.0.0-beta.39 <1.0.0-rc.0", + "@swagger-api/apidom-core": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-error": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.40 <1.0.0-rc.0", + "@swagger-api/apidom-reference": ">=1.0.0-beta.40 <1.0.0-rc.0", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", diff --git a/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/all-of.js b/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/all-of.js index f7782aeb1..1c05ea101 100644 --- a/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/all-of.js +++ b/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/all-of.js @@ -1,4 +1,12 @@ -import { isArrayElement, deepmerge } from '@swagger-api/apidom-core'; +import { uniqWith } from 'ramda'; +import { + isArrayElement, + isObjectElement, + deepmerge, + toValue, + cloneShallow, + includesClasses, +} from '@swagger-api/apidom-core'; import { isSchemaElement } from '@swagger-api/apidom-ns-openapi-3-1'; import toPath from '../utils/to-path.js'; @@ -37,7 +45,39 @@ class AllOfVisitor { while (schemaElement.hasKey('allOf')) { const { allOf } = schemaElement; schemaElement.remove('allOf'); - const allOfMerged = deepmerge.all([...allOf.content, schemaElement]); + const allOfMerged = deepmerge.all([...allOf.content, schemaElement], { + customMerge: (keyElement) => { + if (toValue(keyElement) === 'enum') { + return (targetElement, sourceElement) => { + if ( + includesClasses(['json-schema-enum'], targetElement) && + includesClasses(['json-schema-enum'], sourceElement) + ) { + const areElementsEqual = (a, b) => { + if ( + isArrayElement(a) || + isArrayElement(b) || + isObjectElement(a) || + isObjectElement(b) + ) { + return false; + } + return a.equals(toValue(b)); + }; + const clone = cloneShallow(targetElement); + clone.content = uniqWith(areElementsEqual)([ + ...targetElement.content, + ...sourceElement.content, + ]); + + return clone; + } + return deepmerge(targetElement, sourceElement); + }; + } + return deepmerge; + }, + }); /** * If there was not an original $$ref value, make sure to remove diff --git a/src/resolver/specmap/lib/index.js b/src/resolver/specmap/lib/index.js index 159318864..35718f8cd 100644 --- a/src/resolver/specmap/lib/index.js +++ b/src/resolver/specmap/lib/index.js @@ -39,7 +39,19 @@ function applyPatch(obj, patch, opts) { jsonPatch.applyPatch(obj, [replace(patch.path, newValue)]); } else if (patch.op === 'mergeDeep') { const currentValue = getInByJsonPath(obj, patch.path); - const newValue = deepmerge(currentValue, patch.value); + const newValue = deepmerge(currentValue, patch.value, { + customMerge: (key) => { + if (key === 'enum') { + return (targetElement, sourceElement) => { + if (Array.isArray(targetElement) && Array.isArray(sourceElement)) { + return [...new Set([...targetElement, ...sourceElement])]; + } + return deepmerge(targetElement, sourceElement); + }; + } + return undefined; + }, + }); obj = jsonPatch.applyPatch(obj, [replace(patch.path, newValue)]).newDocument; } else if (patch.op === 'add' && patch.path === '' && isObject(patch.value)) { // { op: 'add', path: '', value: { a: 1, b: 2 }} diff --git a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/all-of.js b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/all-of.js index e4cedcb57..7b69d2154 100644 --- a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/all-of.js +++ b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/all-of.js @@ -1018,6 +1018,51 @@ describe('dereference', () => { }, }); }); + + test('should not duplicate primitive `enum` values nested in `allOf`', async () => { + const spec = OpenApi3_1Element.refract({ + openapi: '3.1.0', + components: { + schemas: { + one: { + allOf: [ + { + properties: { + foo: { enum: [1, 2, 3, 4] }, + bar: { enum: [{ enum: [1, 2, 3] }, { enum: [1, 2, 3, 4] }] }, + }, + }, + { + properties: { + foo: { enum: [1, 2, 3, 5, { enum: [1, 2, 3] }] }, + bar: { enum: [{ enum: [1, 2, 3, 4] }] }, + }, + }, + ], + }, + }, + }, + }); + const dereferenced = await dereferenceApiDOM(spec, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + + expect(toValue(dereferenced)).toEqual({ + openapi: '3.1.0', + components: { + schemas: { + one: { + properties: { + foo: { enum: [1, 2, 3, 4, 5, { enum: [1, 2, 3] }] }, + bar: { + enum: [{ enum: [1, 2, 3] }, { enum: [1, 2, 3, 4] }, { enum: [1, 2, 3, 4] }], + }, + }, + }, + }, + }, + }); + }); }); }); }); diff --git a/test/resolver/strategies/generic/index.js b/test/resolver/strategies/generic/index.js index 6ce0f1721..6e9c1563b 100644 --- a/test/resolver/strategies/generic/index.js +++ b/test/resolver/strategies/generic/index.js @@ -890,6 +890,40 @@ describe('resolver', () => { }); }); + test('should not duplicate primitive `enum` values nested in `allOf`', () => { + // Given + const spec = { + allOf: [ + { + properties: { + foo: { enum: [1, 2, 3, 4] }, + bar: { enum: [{ enum: [1, 2, 3] }, { enum: [1, 2, 3, 4] }] }, + }, + }, + { + properties: { + foo: { enum: [1, 2, 3, 5] }, + bar: { enum: [{ enum: [1, 2, 3, 4] }] }, + }, + }, + ], + }; + + // When + return Swagger.resolve({ spec }).then(handleResponse); + + // Then + function handleResponse(obj) { + expect(obj.errors).toEqual([]); + expect(obj.spec).toEqual({ + properties: { + foo: { enum: [1, 2, 3, 4, 5] }, + bar: { enum: [{ enum: [1, 2, 3] }, { enum: [1, 2, 3, 4] }, { enum: [1, 2, 3, 4] }] }, + }, + }); + } + }); + test('should not throw errors on resvered-keywords in freely-named-fields', () => { // Given const ReservedKeywordSpec = jsYaml.load(