Skip to content

Commit

Permalink
feat: sample gen should incorporate schema validation constraint (#7043)
Browse files Browse the repository at this point in the history
* feat(sample-gen): handle minProperties, maxProperties

* fix(sample-gen): lift required

* feat(sample-gen): handle minimum, maximum (+ exclusive)

* feat(sample-gen): handle minLength, maxLength
  • Loading branch information
mathis-m authored Mar 10, 2021
1 parent 902241c commit 3ead825
Show file tree
Hide file tree
Showing 2 changed files with 474 additions and 20 deletions.
167 changes: 147 additions & 20 deletions src/core/plugins/samples/fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
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
const setIfNotDefinedInTarget = (key) => {
if(target[key] === undefined && oldSchema[key] !== undefined) {
target[key] = oldSchema[key]
}
}
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) {
Expand Down Expand Up @@ -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]) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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++
}
}

Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -342,13 +430,21 @@ 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) {
res[displayName].push({additionalProp: "Anything can be here"})
} else {
res.additionalProp1 = {}
}
propertyAddedCounter++
} else if ( additionalProperties ) {
const additionalProps = objectify(additionalProperties)
const additionalPropSample = sampleFromSchemaGeneric(additionalProps, config, undefined, respectXML)
Expand All @@ -357,21 +453,24 @@ 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"]
res[displayName].push(temp)
} else {
res["additionalProp" + i] = additionalPropSample
}
propertyAddedCounter++
}
}
}
if (respectXML && _attr) {
res[displayName].push({_attr: _attr})
}

return res
}

Expand All @@ -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)) {
Expand All @@ -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
}
Expand Down
Loading

0 comments on commit 3ead825

Please sign in to comment.