From 6a493fb4f3f407c1c47f870b910953dcce0974a8 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Fri, 29 Mar 2024 12:56:20 +0100 Subject: [PATCH] feat(plugins): expose JSON Schema merging mechanism from samples plugins (#9766) Refs #9765 --- .../json-schema-2020-12-samples/fn/index.js | 1 + .../json-schema-2020-12-samples/index.js | 2 + .../plugins/json-schema-5-samples/fn/index.js | 56 ++++++++--------- .../plugins/json-schema-5-samples/index.js | 3 + src/core/plugins/oas31/after-load.js | 1 + .../plugins/json-schema-2020-12-samples/fn.js | 53 ++++++++++++++++ .../plugins/json-schema-5-samples/fn/index.js | 60 ++++++++++++++++++- 7 files changed, 148 insertions(+), 28 deletions(-) diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/index.js b/src/core/plugins/json-schema-2020-12-samples/fn/index.js index b6a34c0f..5f0dbeca 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/index.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/index.js @@ -11,3 +11,4 @@ export { export { default as encoderAPI } from "./api/encoderAPI" export { default as formatAPI } from "./api/formatAPI" export { default as mediaTypeAPI } from "./api/mediaTypeAPI" +export { default as mergeJsonSchema } from "./core/merge" diff --git a/src/core/plugins/json-schema-2020-12-samples/index.js b/src/core/plugins/json-schema-2020-12-samples/index.js index eb60b4ac..1561a965 100644 --- a/src/core/plugins/json-schema-2020-12-samples/index.js +++ b/src/core/plugins/json-schema-2020-12-samples/index.js @@ -10,6 +10,7 @@ import { encoderAPI, mediaTypeAPI, formatAPI, + mergeJsonSchema, } from "./fn/index" import makeGetJsonSampleSchema from "./fn/get-json-sample-schema" import makeGetYamlSampleSchema from "./fn/get-yaml-sample-schema" @@ -37,6 +38,7 @@ const JSONSchema202012SamplesPlugin = ({ getSystem }) => { getYamlSampleSchema, getXmlSampleSchema, getSampleSchema, + mergeJsonSchema, }, }, } diff --git a/src/core/plugins/json-schema-5-samples/fn/index.js b/src/core/plugins/json-schema-5-samples/fn/index.js index 0e5b1305..6434c506 100644 --- a/src/core/plugins/json-schema-5-samples/fn/index.js +++ b/src/core/plugins/json-schema-5-samples/fn/index.js @@ -56,10 +56,12 @@ const numberContracts = [ ] const stringContracts = ["minLength", "maxLength"] -const liftSampleHelper = (oldSchema, target, config = {}) => { +export const mergeJsonSchema = (target, source, config = {}) => { + const merged = { ...target } + const setIfNotDefinedInTarget = (key) => { - if(target[key] === undefined && oldSchema[key] !== undefined) { - target[key] = oldSchema[key] + if(merged[key] === undefined && source[key] !== undefined) { + merged[key] = source[key] } } @@ -75,22 +77,22 @@ const liftSampleHelper = (oldSchema, target, config = {}) => { ...stringContracts, ].forEach(key => setIfNotDefinedInTarget(key)) - if(oldSchema.required !== undefined && Array.isArray(oldSchema.required)) { - if(target.required === undefined || !target.required.length) { - target.required = [] + if(source.required !== undefined && Array.isArray(source.required)) { + if(merged.required === undefined || !merged.required.length) { + merged.required = [] } - oldSchema.required.forEach(key => { - if(target.required.includes(key)) { + source.required.forEach(key => { + if(merged.required.includes(key)) { return } - target.required.push(key) + merged.required.push(key) }) } - if(oldSchema.properties) { - if(!target.properties) { - target.properties = {} + if(source.properties) { + if(!merged.properties) { + merged.properties = {} } - let props = objectify(oldSchema.properties) + let props = objectify(source.properties) for (let propName in props) { if (!Object.prototype.hasOwnProperty.call(props, propName)) { continue @@ -104,26 +106,26 @@ const liftSampleHelper = (oldSchema, target, config = {}) => { if ( props[propName] && props[propName].writeOnly && !config.includeWriteOnly ) { continue } - if(!target.properties[propName]) { - target.properties[propName] = props[propName] - if(!oldSchema.required && Array.isArray(oldSchema.required) && oldSchema.required.indexOf(propName) !== -1) { - if(!target.required) { - target.required = [propName] + if(!merged.properties[propName]) { + merged.properties[propName] = props[propName] + if(!source.required && Array.isArray(source.required) && source.required.indexOf(propName) !== -1) { + if(!merged.required) { + merged.required = [propName] } else { - target.required.push(propName) + merged.required.push(propName) } } } } } - if(oldSchema.items) { - if(!target.items) { - target.items = {} + if(source.items) { + if(!merged.items) { + merged.items = {} } - target.items = liftSampleHelper(oldSchema.items, target.items, config) + merged.items = mergeJsonSchema(merged.items, source.items, config) } - return target + return merged } export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = undefined, respectXML = false) => { @@ -138,7 +140,7 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und ? schema.oneOf[0] : schema.anyOf[0] ) - liftSampleHelper(schemaToAdd, schema, config) + schema = mergeJsonSchema(schema, schemaToAdd, config) if(!schema.xml && schemaToAdd.xml) { schema.xml = schemaToAdd.xml } @@ -537,9 +539,9 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } if(Array.isArray(items.anyOf)) { - sampleArray = items.anyOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i, config), config, undefined, respectXML)) + sampleArray = items.anyOf.map(i => sampleFromSchemaGeneric(mergeJsonSchema(i, items, config), config, undefined, respectXML)) } else if(Array.isArray(items.oneOf)) { - sampleArray = items.oneOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i, config), config, undefined, respectXML)) + sampleArray = items.oneOf.map(i => sampleFromSchemaGeneric(mergeJsonSchema(i, items, config), config, undefined, respectXML)) } else if(!respectXML || respectXML && xml.wrapped) { sampleArray = [sampleFromSchemaGeneric(items, config, undefined, respectXML)] } else { diff --git a/src/core/plugins/json-schema-5-samples/index.js b/src/core/plugins/json-schema-5-samples/index.js index aaabf231..ca905c64 100644 --- a/src/core/plugins/json-schema-5-samples/index.js +++ b/src/core/plugins/json-schema-5-samples/index.js @@ -8,6 +8,7 @@ import { createXMLExample, memoizedCreateXMLExample, memoizedSampleFromSchema, + mergeJsonSchema, } from "./fn/index" import makeGetJsonSampleSchema from "./fn/get-json-sample-schema" import makeGetYamlSampleSchema from "./fn/get-yaml-sample-schema" @@ -33,6 +34,7 @@ const JSONSchema5SamplesPlugin = ({ getSystem }) => { getYamlSampleSchema, getXmlSampleSchema, getSampleSchema, + mergeJsonSchema, }, inferSchema, sampleFromSchema, @@ -44,6 +46,7 @@ const JSONSchema5SamplesPlugin = ({ getSystem }) => { getYamlSampleSchema, getXmlSampleSchema, getSampleSchema, + mergeJsonSchema, }, } } diff --git a/src/core/plugins/oas31/after-load.js b/src/core/plugins/oas31/after-load.js index 7c34220c..68db555d 100644 --- a/src/core/plugins/oas31/after-load.js +++ b/src/core/plugins/oas31/after-load.js @@ -31,6 +31,7 @@ function afterLoad({ fn, getSystem }) { getYamlSampleSchema: fn.jsonSchema202012.getYamlSampleSchema, getXmlSampleSchema: fn.jsonSchema202012.getXmlSampleSchema, getSampleSchema: fn.jsonSchema202012.getSampleSchema, + mergeJsonSchema: fn.jsonSchema202012.mergeJsonSchema, }, getSystem() ) diff --git a/test/unit/core/plugins/json-schema-2020-12-samples/fn.js b/test/unit/core/plugins/json-schema-2020-12-samples/fn.js index 8225a180..b9a51d6d 100644 --- a/test/unit/core/plugins/json-schema-2020-12-samples/fn.js +++ b/test/unit/core/plugins/json-schema-2020-12-samples/fn.js @@ -8,6 +8,7 @@ import { sampleFromSchema, memoizedCreateXMLExample, memoizedSampleFromSchema, + mergeJsonSchema, } from "core/plugins/json-schema-2020-12-samples/fn" describe("sampleFromSchema", () => { @@ -2983,3 +2984,55 @@ describe("memoizedCreateXMLExample", () => { ).toEqual(updatedExpected) }) }) + +describe("merge", function () { + it("should merge two schemas", function () { + const schema = { + properties: { + name: { + type: "string", + }, + id: { + type: "integer", + }, + }, + example: { + name: "test", + id: 1, + }, + required: ["name"], + } + + const target = { + type: "object", + properties: { + username: { + type: "string", + }, + }, + required: ["username"], + } + + const result = mergeJsonSchema(target, schema) + + expect(result).toStrictEqual({ + type: "object", + properties: { + username: { + type: "string", + }, + name: { + type: "string", + }, + id: { + type: "integer", + }, + }, + example: { + name: "test", + id: 1, + }, + required: ["username", "name"], + }) + }) +}) diff --git a/test/unit/core/plugins/json-schema-5-samples/fn/index.js b/test/unit/core/plugins/json-schema-5-samples/fn/index.js index 1dfdb3d3..d3b6ea15 100644 --- a/test/unit/core/plugins/json-schema-5-samples/fn/index.js +++ b/test/unit/core/plugins/json-schema-5-samples/fn/index.js @@ -1,5 +1,11 @@ import { fromJS } from "immutable" -import { createXMLExample, sampleFromSchema, memoizedCreateXMLExample, memoizedSampleFromSchema } from "core/plugins/json-schema-5-samples/fn/index" +import { + createXMLExample, + sampleFromSchema, + memoizedCreateXMLExample, + memoizedSampleFromSchema, + mergeJsonSchema, +} from "core/plugins/json-schema-5-samples/fn/index" describe("sampleFromSchema", () => { it("handles Immutable.js objects for nested schemas", function () { @@ -2438,3 +2444,55 @@ describe("memoizedCreateXMLExample", () => { expect(memoizedCreateXMLExample(definition, {}, updatedOverrideExample)).toEqual(updatedExpected) }) }) + +describe("mergeJsonSchema", function () { + it("should merge two schemas", function () { + const schema = { + properties: { + name: { + type: "string", + }, + id: { + type: "integer", + }, + }, + example: { + name: "test", + id: 1, + }, + required: ["name"], + } + + const target = { + type: "object", + properties: { + username: { + type: "string", + }, + }, + required: ["username"], + } + + const result = mergeJsonSchema(target, schema) + + expect(result).toStrictEqual({ + type: "object", + properties: { + username: { + type: "string", + }, + name: { + type: "string", + }, + id: { + type: "integer", + }, + }, + example: { + name: "test", + id: 1, + }, + required: ["username", "name"], + }) + }) +})