refactor(samples): do the general code cleanup (#8911)

This change is specific to JSON Schema 2020-12
and OpenAPI 3.1.0.

Refs #8577
This commit is contained in:
Vladimír Gorej
2023-06-11 19:27:59 +02:00
committed by GitHub
parent 7bf0dd921b
commit fd18aaa09c
2 changed files with 62 additions and 123 deletions

View File

@@ -4,6 +4,7 @@
import { ALL_TYPES } from "./constants" import { ALL_TYPES } from "./constants"
import { isJSONSchemaObject } from "./predicates" import { isJSONSchemaObject } from "./predicates"
import { pick as randomPick } from "./random" import { pick as randomPick } from "./random"
import { hasExample, extractExample } from "./example"
const inferringKeywords = { const inferringKeywords = {
array: [ array: [
@@ -50,6 +51,15 @@ inferringKeywords.number = inferringKeywords.integer
const fallbackType = "string" const fallbackType = "string"
const inferTypeFromValue = (value) => {
if (typeof value === "undefined") return null
if (value === null) return "null"
if (Array.isArray(value)) return "array"
if (Number.isInteger(value)) return "integer"
return typeof value
}
export const foldType = (type) => { export const foldType = (type) => {
if (Array.isArray(type) && type.length >= 1) { if (Array.isArray(type) && type.length >= 1) {
if (type.includes("array")) { if (type.includes("array")) {
@@ -100,17 +110,8 @@ export const inferType = (schema, processedSchemas = new WeakSet()) => {
// inferring type from const keyword // inferring type from const keyword
if (typeof type !== "string" && typeof constant !== "undefined") { if (typeof type !== "string" && typeof constant !== "undefined") {
if (constant === null) { const constType = inferTypeFromValue(constant)
type = "null" type = typeof constType === "string" ? constType : type
} else if (typeof constant === "boolean") {
type = "boolean"
} else if (typeof constant === "number") {
type = Number.isInteger(constant) ? "integer" : "number"
} else if (typeof constant === "string") {
type = "string"
} else if (typeof constant === "object") {
type = "object"
}
} }
// inferring type from combining schemas // inferring type from combining schemas
@@ -135,6 +136,13 @@ export const inferType = (schema, processedSchemas = new WeakSet()) => {
} }
} }
// inferring type from example
if (typeof type !== "string" && hasExample(schema)) {
const example = extractExample(schema)
const exampleType = inferTypeFromValue(example)
type = typeof exampleType === "string" ? exampleType : type
}
processedSchemas.delete(schema) processedSchemas.delete(schema)
return type || fallbackType return type || fallbackType

View File

@@ -3,6 +3,7 @@
*/ */
import XML from "xml" import XML from "xml"
import isEmpty from "lodash/isEmpty" import isEmpty from "lodash/isEmpty"
import isPlainObject from "lodash/isPlainObject"
import { objectify, normalizeArray } from "core/utils" import { objectify, normalizeArray } from "core/utils"
import memoizeN from "../../../../../helpers/memoizeN" import memoizeN from "../../../../../helpers/memoizeN"
@@ -26,9 +27,9 @@ export const sampleFromSchemaGeneric = (
let usePlainValue = exampleOverride !== undefined || hasExample(schema) let usePlainValue = exampleOverride !== undefined || hasExample(schema)
// first check if there is the need of combining this schema with others required by allOf // first check if there is the need of combining this schema with others required by allOf
const hasOneOf = const hasOneOf =
!usePlainValue && schema && schema.oneOf && schema.oneOf.length > 0 !usePlainValue && Array.isArray(schema.oneOf) && schema.oneOf.length > 0
const hasAnyOf = const hasAnyOf =
!usePlainValue && schema && schema.anyOf && schema.anyOf.length > 0 !usePlainValue && Array.isArray(schema.anyOf) && schema.anyOf.length > 0
if (!usePlainValue && (hasOneOf || hasAnyOf)) { if (!usePlainValue && (hasOneOf || hasAnyOf)) {
const schemaToAdd = typeCast( const schemaToAdd = typeCast(
hasOneOf ? randomPick(schema.oneOf) : randomPick(schema.anyOf) hasOneOf ? randomPick(schema.oneOf) : randomPick(schema.anyOf)
@@ -39,47 +40,6 @@ export const sampleFromSchemaGeneric = (
} }
if (hasExample(schema) && hasExample(schemaToAdd)) { if (hasExample(schema) && hasExample(schemaToAdd)) {
usePlainValue = true usePlainValue = true
} else if (schemaToAdd.properties) {
if (!schema.properties) {
schema.properties = {}
}
let props = objectify(schemaToAdd.properties)
for (let propName in props) {
if (!Object.hasOwn(props, propName)) {
continue
}
if (props[propName] && props[propName].deprecated) {
continue
}
if (
props[propName] &&
props[propName].readOnly &&
!config.includeReadOnly
) {
continue
}
if (
props[propName] &&
props[propName].writeOnly &&
!config.includeWriteOnly
) {
continue
}
if (!schema.properties[propName]) {
schema.properties[propName] = props[propName]
if (
!schemaToAdd.required &&
Array.isArray(schemaToAdd.required) &&
schemaToAdd.required.indexOf(propName) !== -1
) {
if (!schema.required) {
schema.required = [propName]
} else {
schema.required.push(propName)
}
}
}
}
} }
} }
const _attr = {} const _attr = {}
@@ -99,10 +59,10 @@ export const sampleFromSchemaGeneric = (
if (respectXML) { if (respectXML) {
name = name || "notagname" name = name || "notagname"
// add prefix to name if exists // add prefix to name if exists
displayName = (prefix ? prefix + ":" : "") + name displayName = (prefix ? `${prefix}:` : "") + name
if (namespace) { if (namespace) {
//add prefix to namespace if exists //add prefix to namespace if exists
let namespacePrefix = prefix ? "xmlns:" + prefix : "xmlns" let namespacePrefix = prefix ? `xmlns:${prefix}` : "xmlns"
_attr[namespacePrefix] = namespace _attr[namespacePrefix] = namespace
} }
} }
@@ -118,13 +78,12 @@ export const sampleFromSchemaGeneric = (
let propertyAddedCounter = 0 let propertyAddedCounter = 0
const hasExceededMaxProperties = () => const hasExceededMaxProperties = () =>
schema &&
Number.isInteger(schema.maxProperties) && Number.isInteger(schema.maxProperties) &&
schema.maxProperties > 0 && schema.maxProperties > 0 &&
propertyAddedCounter >= schema.maxProperties propertyAddedCounter >= schema.maxProperties
const requiredPropertiesToAdd = () => { const requiredPropertiesToAdd = () => {
if (!schema || !schema.required) { if (!Array.isArray(schema.required) || schema.required.length === 0) {
return 0 return 0
} }
let addedCount = 0 let addedCount = 0
@@ -133,26 +92,25 @@ export const sampleFromSchemaGeneric = (
(key) => (addedCount += res[key] === undefined ? 0 : 1) (key) => (addedCount += res[key] === undefined ? 0 : 1)
) )
} else { } else {
schema.required.forEach( schema.required.forEach((key) => {
(key) => addedCount +=
(addedCount +=
res[displayName]?.find((x) => x[key] !== undefined) === undefined res[displayName]?.find((x) => x[key] !== undefined) === undefined
? 0 ? 0
: 1) : 1
) })
} }
return schema.required.length - addedCount return schema.required.length - addedCount
} }
const isOptionalProperty = (propName) => { const isOptionalProperty = (propName) => {
if (!schema || !schema.required || !schema.required.length) { if (!Array.isArray(schema.required)) return true
return true if (schema.required.length === 0) return true
}
return !schema.required.includes(propName) return !schema.required.includes(propName)
} }
const canAddProperty = (propName) => { const canAddProperty = (propName) => {
if (!schema || schema.maxProperties == null) { if (!(Number.isInteger(schema.maxProperties) && schema.maxProperties > 0)) {
return true return true
} }
if (hasExceededMaxProperties()) { if (hasExceededMaxProperties()) {
@@ -170,7 +128,7 @@ export const sampleFromSchemaGeneric = (
if (respectXML) { if (respectXML) {
addPropertyToResult = (propName, overrideE = undefined) => { addPropertyToResult = (propName, overrideE = undefined) => {
if (schema && props[propName]) { if (schema && props[propName]) {
// case it is an xml attribute // case it is a xml attribute
props[propName].xml = props[propName].xml || {} props[propName].xml = props[propName].xml || {}
if (props[propName].xml.attribute) { if (props[propName].xml.attribute) {
@@ -203,7 +161,7 @@ export const sampleFromSchemaGeneric = (
} }
let t = sampleFromSchemaGeneric( let t = sampleFromSchemaGeneric(
(schema && props[propName]) || undefined, props[propName],
config, config,
overrideE, overrideE,
respectXML respectXML
@@ -225,15 +183,11 @@ export const sampleFromSchemaGeneric = (
return return
} }
if ( if (
Object.hasOwn(schema, "discriminator") && isPlainObject(schema.discriminator?.mapping) &&
schema.discriminator && schema.discriminator.propertyName === propName &&
Object.hasOwn(schema.discriminator, "mapping") && typeof schema.$$ref === "string"
schema.discriminator.mapping &&
Object.hasOwn(schema, "$$ref") &&
schema.$$ref &&
schema.discriminator.propertyName === propName
) { ) {
for (let pair in schema.discriminator.mapping) { for (const pair in schema.discriminator.mapping) {
if (schema.$$ref.search(schema.discriminator.mapping[pair]) !== -1) { if (schema.$$ref.search(schema.discriminator.mapping[pair]) !== -1) {
res[propName] = pair res[propName] = pair
break break
@@ -273,17 +227,12 @@ export const sampleFromSchemaGeneric = (
// check if sample is parsable or just a plain string // check if sample is parsable or just a plain string
try { try {
return JSON.parse(sample) return JSON.parse(sample)
} catch (e) { } catch {
// sample is just plain string return it // sample is just plain string return it
return sample return sample
} }
} }
// recover missing type
if (!schema) {
type = Array.isArray(sample) ? "array" : typeof sample
}
// generate xml sample recursively for array case // generate xml sample recursively for array case
if (type === "array") { if (type === "array") {
if (!Array.isArray(sample)) { if (!Array.isArray(sample)) {
@@ -295,7 +244,7 @@ export const sampleFromSchemaGeneric = (
let itemSamples = [] let itemSamples = []
if (items != null && typeof items === "object") { if (isJSONSchemaObject(items)) {
items.xml = items.xml || xml || {} items.xml = items.xml || xml || {}
items.xml.name = items.xml.name || xml.name items.xml.name = items.xml.name || xml.name
itemSamples = sample.map((s) => itemSamples = sample.map((s) =>
@@ -303,7 +252,7 @@ export const sampleFromSchemaGeneric = (
) )
} }
if (contains != null && typeof contains === "object") { if (isJSONSchemaObject(contains)) {
contains.xml = contains.xml || xml || {} contains.xml = contains.xml || xml || {}
contains.xml.name = contains.xml.name || xml.name contains.xml.name = contains.xml.name || xml.name
itemSamples = [ itemSamples = [
@@ -330,32 +279,17 @@ export const sampleFromSchemaGeneric = (
if (typeof sample === "string") { if (typeof sample === "string") {
return sample return sample
} }
for (let propName in sample) { for (const propName in sample) {
if (!Object.hasOwn(sample, propName)) { if (!Object.hasOwn(sample, propName)) {
continue continue
} }
if ( if (props[propName]?.readOnly && !includeReadOnly) {
schema &&
props[propName] &&
props[propName].readOnly &&
!includeReadOnly
) {
continue continue
} }
if ( if (props[propName]?.writeOnly && !includeWriteOnly) {
schema &&
props[propName] &&
props[propName].writeOnly &&
!includeWriteOnly
) {
continue continue
} }
if ( if (props[propName]?.xml?.attribute) {
schema &&
props[propName] &&
props[propName].xml &&
props[propName].xml.attribute
) {
_attr[props[propName].xml.name || propName] = sample[propName] _attr[props[propName].xml.name || propName] = sample[propName]
continue continue
} }
@@ -376,9 +310,9 @@ export const sampleFromSchemaGeneric = (
if (type === "array") { if (type === "array") {
let sampleArray = [] let sampleArray = []
if (contains != null && typeof contains === "object") { if (isJSONSchemaObject(contains)) {
if (respectXML) { if (respectXML) {
contains.xml = contains.xml || schema?.xml || {} contains.xml = contains.xml || schema.xml || {}
contains.xml.name = contains.xml.name || xml.name contains.xml.name = contains.xml.name || xml.name
} }
@@ -413,9 +347,9 @@ export const sampleFromSchemaGeneric = (
} }
} }
if (items != null && typeof items === "object") { if (isJSONSchemaObject(items)) {
if (respectXML) { if (respectXML) {
items.xml = items.xml || schema?.xml || {} items.xml = items.xml || schema.xml || {}
items.xml.name = items.xml.name || xml.name items.xml.name = items.xml.name || xml.name
} }
@@ -467,13 +401,13 @@ export const sampleFromSchemaGeneric = (
if (!Object.hasOwn(props, propName)) { if (!Object.hasOwn(props, propName)) {
continue continue
} }
if (props[propName] && props[propName].deprecated) { if (props[propName]?.deprecated) {
continue continue
} }
if (props[propName] && props[propName].readOnly && !includeReadOnly) { if (props[propName]?.readOnly && !includeReadOnly) {
continue continue
} }
if (props[propName] && props[propName].writeOnly && !includeWriteOnly) { if (props[propName]?.writeOnly && !includeWriteOnly) {
continue continue
} }
addPropertyToResult(propName) addPropertyToResult(propName)
@@ -494,7 +428,7 @@ export const sampleFromSchemaGeneric = (
} }
propertyAddedCounter++ propertyAddedCounter++
} else if (isJSONSchemaObject(additionalProperties)) { } else if (isJSONSchemaObject(additionalProperties)) {
const additionalProps = typeCast(additionalProperties) const additionalProps = additionalProperties
const additionalPropSample = sampleFromSchemaGeneric( const additionalPropSample = sampleFromSchemaGeneric(
additionalProps, additionalProps,
config, config,
@@ -504,9 +438,8 @@ export const sampleFromSchemaGeneric = (
if ( if (
respectXML && respectXML &&
additionalProps.xml && typeof additionalProps?.xml?.name === "string" &&
additionalProps.xml.name && additionalProps?.xml?.name !== "notagname"
additionalProps.xml.name !== "notagname"
) { ) {
res[displayName].push(additionalPropSample) res[displayName].push(additionalPropSample)
} else { } else {
@@ -535,25 +468,23 @@ export const sampleFromSchemaGeneric = (
} }
let value let value
if (typeof schema?.const !== "undefined") { if (typeof schema.const !== "undefined") {
// display const value // display const value
value = schema.const value = schema.const
} else if (schema && Array.isArray(schema.enum)) { } else if (schema && Array.isArray(schema.enum)) {
//display enum first value //display enum first value
value = randomPick(normalizeArray(schema.enum)) value = randomPick(normalizeArray(schema.enum))
} else if (schema) { } else {
// display schema default // display schema default
const contentSample = Object.hasOwn(schema, "contentSchema") const contentSample = isJSONSchemaObject(schema.contentSchema)
? sampleFromSchemaGeneric( ? sampleFromSchemaGeneric(
typeCast(schema.contentSchema), schema.contentSchema,
config, config,
undefined, undefined,
respectXML respectXML
) )
: undefined : undefined
value = typeMap[type](schema, { sample: contentSample }) value = typeMap[type](schema, { sample: contentSample })
} else {
return
} }
if (respectXML) { if (respectXML) {