diff --git a/src/core/plugins/samples/fn.js b/src/core/plugins/samples/fn.js index b8d7da62..24a37953 100644 --- a/src/core/plugins/samples/fn.js +++ b/src/core/plugins/samples/fn.js @@ -36,20 +36,39 @@ const sanitizeRef = (value) => deeplyStripKey(value, "$$ref", (val) => typeof val === "string" && val.indexOf("#") > -1) const liftSampleHelper = (oldSchema, target, config = {}) => { - if(target.example === undefined && oldSchema.example !== undefined) { - target.example = oldSchema.example + const setIfNotDefinedInTarget = (key) => { + if(target[key] === undefined && oldSchema[key] !== undefined) { + target[key] = oldSchema[key] + } } - if(target.default === undefined && oldSchema.default !== undefined) { - target.default = oldSchema.defaultfn - } - if(target.enum === undefined && oldSchema.enum !== undefined) { - target.enum = oldSchema.enum - } - if(target.xml === undefined && oldSchema.xml !== undefined) { - target.xml = oldSchema.xml - } - if(target.type === undefined && oldSchema.type !== undefined) { - target.type = oldSchema.type + [ + "example", + "default", + "enum", + "xml", + "type", + "maxProperties", + "minProperties", + "minItems", + "maxItems", + "minimum", + "maximum", + "exclusiveMinimum", + "exclusiveMaximum", + "minLength", + "maxLength" + ].forEach(key => setIfNotDefinedInTarget(key)) + + if(oldSchema.required !== undefined && Array.isArray(oldSchema.required)) { + if(target.required === undefined || !target.required.length) { + target.required = [] + } + oldSchema.required.forEach(key => { + if(target.required.includes(key)) { + return + } + target.required.push(key) + }) } if(oldSchema.properties) { if(!target.properties) { @@ -175,9 +194,69 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } } + const handleMinMaxItems = (sampleArray) => { + if (schema.maxItems !== null && schema.maxItems !== undefined) { + sampleArray = sampleArray.slice(0, schema.maxItems) + } + if (schema.minItems !== null && schema.minItems !== undefined) { + let i = 0 + while (sampleArray.length < schema.minItems) { + sampleArray.push(sampleArray[i++ % sampleArray.length]) + } + } + return sampleArray + } + // add to result helper init for xml or json const props = objectify(properties) let addPropertyToResult + let propertyAddedCounter = 0 + + const hasExceededMaxProperties = () => schema + && schema.maxProperties !== null && schema.maxProperties !== undefined + && propertyAddedCounter >= schema.maxProperties + + const requiredPropertiesToAdd = () => { + if(!schema || !schema.required) { + return 0 + } + let addedCount = 0 + if(respectXML) { + schema.required.forEach(key => addedCount += + res[key] === undefined + ? 0 + : 1 + ) + } else { + schema.required.forEach(key => addedCount += + res[displayName]?.find(x => x[key] !== undefined) === undefined + ? 0 + : 1 + ) + } + return schema.required.length - addedCount + } + + const isOptionalProperty = (propName) => { + if(!schema || !schema.required || !schema.required.length) { + return true + } + return !schema.required.includes(propName) + } + + const canAddProperty = (propName) => { + if(!schema || schema.maxProperties === null || schema.maxProperties === undefined) { + return true + } + if(hasExceededMaxProperties()) { + return false + } + if(!isOptionalProperty(propName)) { + return true + } + return (schema.maxProperties - propertyAddedCounter - requiredPropertiesToAdd()) > 0 + } + if(respectXML) { addPropertyToResult = (propName, overrideE = undefined) => { if(schema && props[propName]) { @@ -214,6 +293,11 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } let t = sampleFromSchemaGeneric(schema && props[propName] || undefined, config, overrideE, respectXML) + if(!canAddProperty(propName)) { + return + } + + propertyAddedCounter++ if (Array.isArray(t)) { res[displayName] = res[displayName].concat(t) } else { @@ -222,8 +306,11 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } } else { addPropertyToResult = (propName, overrideE) => { - + if(!canAddProperty(propName)) { + return + } res[propName] = sampleFromSchemaGeneric(props[propName], config, overrideE, respectXML) + propertyAddedCounter++ } } @@ -277,8 +364,9 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und itemSchema.xml = itemSchema.xml || xml || {} itemSchema.xml.name = itemSchema.xml.name || xml.name } - const itemSamples = sample + let itemSamples = sample .map(s => sampleFromSchemaGeneric(itemSchema, config, s, respectXML)) + itemSamples = handleMinMaxItems(itemSamples) if(xml.wrapped) { res[displayName] = itemSamples if (!isEmpty(_attr)) { @@ -342,6 +430,13 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } addPropertyToResult(propName) } + if (respectXML && _attr) { + res[displayName].push({_attr: _attr}) + } + + if(hasExceededMaxProperties()) { + return res + } if ( additionalProperties === true ) { if(respectXML) { @@ -349,6 +444,7 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } else { res.additionalProp1 = {} } + propertyAddedCounter++ } else if ( additionalProperties ) { const additionalProps = objectify(additionalProperties) const additionalPropSample = sampleFromSchemaGeneric(additionalProps, config, undefined, respectXML) @@ -357,7 +453,13 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und { res[displayName].push(additionalPropSample) } else { - for (let i = 1; i < 4; i++) { + const toGenerateCount = schema.minProperties !== null && schema.minProperties !== undefined && propertyAddedCounter < schema.minProperties + ? schema.minProperties - propertyAddedCounter + : 4 + for (let i = 1; i < toGenerateCount; i++) { + if(hasExceededMaxProperties()) { + return res + } if(respectXML) { const temp = {} temp["additionalProp" + i] = additionalPropSample["notagname"] @@ -365,13 +467,10 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } else { res["additionalProp" + i] = additionalPropSample } + propertyAddedCounter++ } } } - if (respectXML && _attr) { - res[displayName].push({_attr: _attr}) - } - return res } @@ -391,6 +490,7 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } else { return sampleFromSchemaGeneric(items, config, undefined, respectXML) } + sampleArray = handleMinMaxItems(sampleArray) if(respectXML && xml.wrapped) { res[displayName] = sampleArray if (!isEmpty(_attr)) { @@ -408,6 +508,33 @@ export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = und } else if(schema) { // display schema default value = primitive(schema) + if(typeof value === "number") { + let min = schema.minimum + if(min !== undefined && min !== null) { + if(schema.exclusiveMinimum) { + min++ + } + value = min + } + let max = schema.maximum + if(max !== undefined && max !== null) { + if(schema.exclusiveMaximum) { + max-- + } + value = max + } + } + if(typeof value === "string") { + if (schema.maxLength !== null && schema.maxLength !== undefined) { + value = value.slice(0, schema.maxLength) + } + if (schema.minLength !== null && schema.minLength !== undefined) { + let i = 0 + while (value.length < schema.minLength) { + value += value[i++ % value.length] + } + } + } } else { return } diff --git a/test/unit/core/plugins/samples/fn.js b/test/unit/core/plugins/samples/fn.js index c95f555b..47e7722d 100644 --- a/test/unit/core/plugins/samples/fn.js +++ b/test/unit/core/plugins/samples/fn.js @@ -768,6 +768,306 @@ describe("sampleFromSchema", () => { true ] + expect(sampleFromSchema(definition)).toEqual(expected) + }) + it("should ignore minProperties if cannot extend object", () => { + const definition = { + type: "object", + minProperties: 2, + properties: { + foo: { + type: "string" + } + } + } + + const expected = { + foo: "string" + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minProperties in conjunction with additionalProperties", () => { + const definition = { + type: "object", + minProperties: 2, + additionalProperties: true, + properties: { + foo: { + type: "string" + } + } + } + + const expected = { + foo: "string", + additionalProp1: {} + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minProperties in conjunction with additionalProperties and anyOf", () => { + const definition = { + type: "object", + minProperties: 2, + additionalProperties: true, + anyOf: [ + { + type: "object", + properties: { + foo: { + type: "string" + } + } + } + ] + } + + const expected = { + foo: "string", + additionalProp1: {} + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxProperties", () => { + const definition = { + type: "object", + maxProperties: 1, + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string" + } + } + } + + const expected = { + foo: "string" + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxProperties in conjunction with anyOf", () => { + const definition = { + type: "object", + maxProperties: 1, + anyOf: [ + { + type: "object", + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string" + } + } + } + ] + } + + const expected = { + foo: "string" + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle handle maxProperties in conjunction with required", () => { + const definition = { + type: "object", + maxProperties: 1, + required: ["swaggerUi"], + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string", + example: "<3" + } + } + } + + const expected = { + swaggerUi: "<3", + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle handle maxProperties in conjunction with anyOf required", () => { + const definition = { + type: "object", + maxProperties: 1, + required: ["swaggerUi"], + anyOf: [ + { + type: "object", + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string", + example: "<3" + } + } + } + ], + } + + const expected = { + swaggerUi: "<3", + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minItems", () => { + const definition = { + type: "array", + minItems: 2, + items: { + type: "string" + } + } + + const expected = ["string", "string"] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minItems with example", () => { + const definition = { + type: "array", + minItems: 2, + items: { + type: "string", + example: "some" + }, + } + + const expected = ["some", "some"] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minItems in conjunction with oneOf", () => { + const definition = { + type: "array", + minItems: 4, + items: { + oneOf: [ + { + type: "string" + }, + { + type: "number" + } + ] + } + } + + const expected = ["string", 0, "string", 0] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxItems in conjunction with multiple oneOf", () => { + const definition = { + type: "array", + maxItems: 1, + items: { + oneOf: [ + { + type: "string" + }, + { + type: "number" + } + ] + } + } + + const expected = ["string"] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minimum", () => { + const definition = { + type: "number", + minimum: 5, + } + + const expected = 5 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minimum with exclusive", () => { + const definition = { + type: "number", + minimum: 5, + exclusiveMinimum: true, + } + + const expected = 6 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + + it("should handle maximum", () => { + const definition = { + type: "number", + maximum: -1, + } + + const expected = -1 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maximum with exclusive", () => { + const definition = { + type: "number", + maximum: -1, + exclusiveMaximum: true, + } + + const expected = -2 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minLength", () => { + const definition = { + type: "string", + minLength: 7 + } + + const expected = "strings" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxLength", () => { + const definition = { + type: "string", + maxLength: 3 + } + + const expected = "str" + expect(sampleFromSchema(definition)).toEqual(expected) }) }) @@ -1794,6 +2094,33 @@ describe("createXMLExample", function () { expect(sut(definition, {}, { test: "probe" })).toEqual(expected) }) }) + + it("should handle handle maxProperties in conjunction with required", function () { + const definition = { + type: "object", + maxProperties: 1, + required: ["swaggerUi"], + xml: { + name: "probe" + }, + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string", + example: "cool" + } + } + } + + const expected = ` + +\tcool +` + + expect(sut(definition)).toEqual(expected) + }) }) describe("memoizedSampleFromSchema", () => {