feat(samples): add support for type keyword defined as list of types (#8883)
This change is specific to JSON Schema 2020-12 and OpenAPI 3.1.0. Refs #8577
This commit is contained in:
@@ -49,27 +49,34 @@ const primitives = {
|
|||||||
integer_int64: () => 2 ** 53 - 1,
|
integer_int64: () => 2 ** 53 - 1,
|
||||||
boolean: (schema) =>
|
boolean: (schema) =>
|
||||||
typeof schema.default === "boolean" ? schema.default : true,
|
typeof schema.default === "boolean" ? schema.default : true,
|
||||||
|
null: () => null,
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
const primitive = (schema) => {
|
const primitive = (schema) => {
|
||||||
schema = objectify(schema)
|
schema = objectify(schema)
|
||||||
const { type, format } = schema
|
const { type: typeList, format } = schema
|
||||||
|
const type = Array.isArray(typeList) ? typeList.at(0) : typeList
|
||||||
|
|
||||||
const fn = primitives[`${type}_${format}`] || primitives[type]
|
const fn = primitives[`${type}_${format}`] || primitives[type]
|
||||||
|
|
||||||
return typeof fn === "function" ? fn(schema) : `Unknown Type: ${schema.type}`
|
return typeof fn === "function" ? fn(schema) : `Unknown Type: ${schema.type}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isURI = (uri) => {
|
||||||
|
try {
|
||||||
|
return new URL(uri) && true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do a couple of quick sanity tests to ensure the value
|
* Do a couple of quick sanity tests to ensure the value
|
||||||
* looks like a $$ref that swagger-client generates.
|
* looks like a $$ref that swagger-client generates.
|
||||||
*/
|
*/
|
||||||
const sanitizeRef = (value) =>
|
const sanitizeRef = (value) =>
|
||||||
deeplyStripKey(
|
deeplyStripKey(value, "$$ref", (val) => typeof val === "string" && isURI(val))
|
||||||
value,
|
|
||||||
"$$ref",
|
|
||||||
(val) => typeof val === "string" && val.indexOf("#") > -1
|
|
||||||
)
|
|
||||||
|
|
||||||
const objectContracts = ["maxProperties", "minProperties"]
|
const objectContracts = ["maxProperties", "minProperties"]
|
||||||
const arrayContracts = ["minItems", "maxItems"]
|
const arrayContracts = ["minItems", "maxItems"]
|
||||||
@@ -117,7 +124,7 @@ const liftSampleHelper = (oldSchema, target, config = {}) => {
|
|||||||
}
|
}
|
||||||
let props = objectify(oldSchema.properties)
|
let props = objectify(oldSchema.properties)
|
||||||
for (let propName in props) {
|
for (let propName in props) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(props, propName)) {
|
if (!Object.hasOwn(props, propName)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (props[propName] && props[propName].deprecated) {
|
if (props[propName] && props[propName].deprecated) {
|
||||||
@@ -193,7 +200,7 @@ export const sampleFromSchemaGeneric = (
|
|||||||
}
|
}
|
||||||
let props = objectify(schemaToAdd.properties)
|
let props = objectify(schemaToAdd.properties)
|
||||||
for (let propName in props) {
|
for (let propName in props) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(props, propName)) {
|
if (!Object.hasOwn(props, propName)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (props[propName] && props[propName].deprecated) {
|
if (props[propName] && props[propName].deprecated) {
|
||||||
@@ -256,10 +263,9 @@ export const sampleFromSchemaGeneric = (
|
|||||||
res[displayName] = []
|
res[displayName] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaHasAny = (keys) =>
|
const schemaHasAny = (keys) => keys.some((key) => Object.hasOwn(schema, key))
|
||||||
keys.some((key) => Object.prototype.hasOwnProperty.call(schema, key))
|
|
||||||
// try recover missing type
|
// try recover missing type
|
||||||
if (schema && !type) {
|
if (schema && typeof type !== "string" && !Array.isArray(type)) {
|
||||||
if (properties || additionalProperties || schemaHasAny(objectContracts)) {
|
if (properties || additionalProperties || schemaHasAny(objectContracts)) {
|
||||||
type = "object"
|
type = "object"
|
||||||
} else if (items || schemaHasAny(arrayContracts)) {
|
} else if (items || schemaHasAny(arrayContracts)) {
|
||||||
@@ -419,11 +425,11 @@ export const sampleFromSchemaGeneric = (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
Object.prototype.hasOwnProperty.call(schema, "discriminator") &&
|
Object.hasOwn(schema, "discriminator") &&
|
||||||
schema.discriminator &&
|
schema.discriminator &&
|
||||||
Object.prototype.hasOwnProperty.call(schema.discriminator, "mapping") &&
|
Object.hasOwn(schema.discriminator, "mapping") &&
|
||||||
schema.discriminator.mapping &&
|
schema.discriminator.mapping &&
|
||||||
Object.prototype.hasOwnProperty.call(schema, "$$ref") &&
|
Object.hasOwn(schema, "$$ref") &&
|
||||||
schema.$$ref &&
|
schema.$$ref &&
|
||||||
schema.discriminator.propertyName === propName
|
schema.discriminator.propertyName === propName
|
||||||
) {
|
) {
|
||||||
@@ -459,11 +465,11 @@ export const sampleFromSchemaGeneric = (
|
|||||||
// if json just return
|
// if json just return
|
||||||
if (!respectXML) {
|
if (!respectXML) {
|
||||||
// spacial case yaml parser can not know about
|
// spacial case yaml parser can not know about
|
||||||
if (typeof sample === "number" && type === "string") {
|
if (typeof sample === "number" && type?.includes("string")) {
|
||||||
return `${sample}`
|
return `${sample}`
|
||||||
}
|
}
|
||||||
// return if sample does not need any parsing
|
// return if sample does not need any parsing
|
||||||
if (typeof sample !== "string" || type === "string") {
|
if (typeof sample !== "string" || type?.includes("string")) {
|
||||||
return sample
|
return sample
|
||||||
}
|
}
|
||||||
// check if sample is parsable or just a plain string
|
// check if sample is parsable or just a plain string
|
||||||
@@ -481,7 +487,7 @@ export const sampleFromSchemaGeneric = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate xml sample recursively for array case
|
// generate xml sample recursively for array case
|
||||||
if (type === "array") {
|
if (type?.includes("array")) {
|
||||||
if (!Array.isArray(sample)) {
|
if (!Array.isArray(sample)) {
|
||||||
if (typeof sample === "string") {
|
if (typeof sample === "string") {
|
||||||
return sample
|
return sample
|
||||||
@@ -509,13 +515,13 @@ export const sampleFromSchemaGeneric = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate xml sample recursively for object case
|
// generate xml sample recursively for object case
|
||||||
if (type === "object") {
|
if (type?.includes("object")) {
|
||||||
// case literal example
|
// case literal example
|
||||||
if (typeof sample === "string") {
|
if (typeof sample === "string") {
|
||||||
return sample
|
return sample
|
||||||
}
|
}
|
||||||
for (let propName in sample) {
|
for (let propName in sample) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(sample, propName)) {
|
if (!Object.hasOwn(sample, propName)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -557,10 +563,56 @@ export const sampleFromSchemaGeneric = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use schema to generate sample
|
// use schema to generate sample
|
||||||
|
if (type?.includes("array")) {
|
||||||
|
if (!items) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
if (type === "object") {
|
let sampleArray
|
||||||
|
if (respectXML) {
|
||||||
|
items.xml = items.xml || schema?.xml || {}
|
||||||
|
items.xml.name = items.xml.name || xml.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(items.anyOf)) {
|
||||||
|
sampleArray = items.anyOf.map((i) =>
|
||||||
|
sampleFromSchemaGeneric(
|
||||||
|
liftSampleHelper(items, i, config),
|
||||||
|
config,
|
||||||
|
undefined,
|
||||||
|
respectXML
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (Array.isArray(items.oneOf)) {
|
||||||
|
sampleArray = items.oneOf.map((i) =>
|
||||||
|
sampleFromSchemaGeneric(
|
||||||
|
liftSampleHelper(items, i, config),
|
||||||
|
config,
|
||||||
|
undefined,
|
||||||
|
respectXML
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (!respectXML || (respectXML && xml.wrapped)) {
|
||||||
|
sampleArray = [
|
||||||
|
sampleFromSchemaGeneric(items, config, undefined, respectXML),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
return sampleFromSchemaGeneric(items, config, undefined, respectXML)
|
||||||
|
}
|
||||||
|
sampleArray = handleMinMaxItems(sampleArray)
|
||||||
|
if (respectXML && xml.wrapped) {
|
||||||
|
res[displayName] = sampleArray
|
||||||
|
if (!isEmpty(_attr)) {
|
||||||
|
res[displayName].push({ _attr: _attr })
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return sampleArray
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type?.includes("object")) {
|
||||||
for (let propName in props) {
|
for (let propName in props) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(props, propName)) {
|
if (!Object.hasOwn(props, propName)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (props[propName] && props[propName].deprecated) {
|
if (props[propName] && props[propName].deprecated) {
|
||||||
@@ -630,53 +682,6 @@ export const sampleFromSchemaGeneric = (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "array") {
|
|
||||||
if (!items) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let sampleArray
|
|
||||||
if (respectXML) {
|
|
||||||
items.xml = items.xml || schema?.xml || {}
|
|
||||||
items.xml.name = items.xml.name || xml.name
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(items.anyOf)) {
|
|
||||||
sampleArray = items.anyOf.map((i) =>
|
|
||||||
sampleFromSchemaGeneric(
|
|
||||||
liftSampleHelper(items, i, config),
|
|
||||||
config,
|
|
||||||
undefined,
|
|
||||||
respectXML
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else if (Array.isArray(items.oneOf)) {
|
|
||||||
sampleArray = items.oneOf.map((i) =>
|
|
||||||
sampleFromSchemaGeneric(
|
|
||||||
liftSampleHelper(items, i, config),
|
|
||||||
config,
|
|
||||||
undefined,
|
|
||||||
respectXML
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else if (!respectXML || (respectXML && xml.wrapped)) {
|
|
||||||
sampleArray = [
|
|
||||||
sampleFromSchemaGeneric(items, config, undefined, respectXML),
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return sampleFromSchemaGeneric(items, config, undefined, respectXML)
|
|
||||||
}
|
|
||||||
sampleArray = handleMinMaxItems(sampleArray)
|
|
||||||
if (respectXML && xml.wrapped) {
|
|
||||||
res[displayName] = sampleArray
|
|
||||||
if (!isEmpty(_attr)) {
|
|
||||||
res[displayName].push({ _attr: _attr })
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
return sampleArray
|
|
||||||
}
|
|
||||||
|
|
||||||
let value
|
let value
|
||||||
if (schema && Array.isArray(schema.enum)) {
|
if (schema && Array.isArray(schema.enum)) {
|
||||||
//display enum first value
|
//display enum first value
|
||||||
@@ -714,9 +719,6 @@ export const sampleFromSchemaGeneric = (
|
|||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (type === "file") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (respectXML) {
|
if (respectXML) {
|
||||||
res[displayName] = !isEmpty(_attr) ? [{ _attr: _attr }, value] : value
|
res[displayName] = !isEmpty(_attr) ? [{ _attr: _attr }, value] : value
|
||||||
|
|||||||
@@ -82,6 +82,34 @@ describe("sampleFromSchema", () => {
|
|||||||
2 ** 53 - 1
|
2 ** 53 - 1
|
||||||
)
|
)
|
||||||
expect(sample({ type: "boolean" })).toStrictEqual(true)
|
expect(sample({ type: "boolean" })).toStrictEqual(true)
|
||||||
|
expect(sample({ type: "null" })).toStrictEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle type keyword defined as list of types", function () {
|
||||||
|
const definition = fromJS({
|
||||||
|
type: ["object", "string"],
|
||||||
|
})
|
||||||
|
const expected = {}
|
||||||
|
|
||||||
|
expect(sampleFromSchema(definition)).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should prioritize array when array and object defined as list of types", function () {
|
||||||
|
const definition = fromJS({
|
||||||
|
type: ["object", "array"],
|
||||||
|
})
|
||||||
|
const expected = []
|
||||||
|
|
||||||
|
expect(sampleFromSchema(definition)).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle primitive types defined as list of types", function () {
|
||||||
|
const definition = fromJS({
|
||||||
|
type: ["string", "number"],
|
||||||
|
})
|
||||||
|
const expected = "string"
|
||||||
|
|
||||||
|
expect(sampleFromSchema(definition)).toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("handles Immutable.js objects for nested schemas", function () {
|
it("handles Immutable.js objects for nested schemas", function () {
|
||||||
@@ -353,9 +381,9 @@ describe("sampleFromSchema", () => {
|
|||||||
value: {
|
value: {
|
||||||
message: "Hello, World!",
|
message: "Hello, World!",
|
||||||
},
|
},
|
||||||
$$ref: "#/components/examples/WelcomeExample",
|
$$ref: "https://example.com/#/components/examples/WelcomeExample",
|
||||||
},
|
},
|
||||||
$$ref: "#/components/schemas/Welcome",
|
$$ref: "https://example.com/#/components/schemas/Welcome",
|
||||||
}
|
}
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
@@ -382,10 +410,10 @@ describe("sampleFromSchema", () => {
|
|||||||
value: {
|
value: {
|
||||||
message: "Hello, World!",
|
message: "Hello, World!",
|
||||||
},
|
},
|
||||||
$$ref: "#/components/examples/WelcomeExample",
|
$$ref: "https://example.com/#/components/examples/WelcomeExample",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$$ref: "#/components/schemas/Welcome",
|
$$ref: "https://example.com/#/components/schemas/Welcome",
|
||||||
}
|
}
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
@@ -414,10 +442,10 @@ describe("sampleFromSchema", () => {
|
|||||||
value: {
|
value: {
|
||||||
message: "Hello, World!",
|
message: "Hello, World!",
|
||||||
},
|
},
|
||||||
$$ref: "#/components/examples/WelcomeExample",
|
$$ref: "https://example.com/#/components/examples/WelcomeExample",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$$ref: "#/components/schemas/Welcome",
|
$$ref: "https://example.com/#/components/schemas/Welcome",
|
||||||
}
|
}
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
|
|||||||
Reference in New Issue
Block a user