Skip to content

Commit

Permalink
feat(samples): add support for proper schema merging
Browse files Browse the repository at this point in the history
This change is specific to JSON Schema 2020-12
and OpenAPI 3.1.0.

Refs #8577
  • Loading branch information
char0n committed Jun 11, 2023
1 parent ce417d5 commit 0911bb9
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 118 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* @prettier
*/
import { isBooleanJSONSchema, isJSONSchema } from "./predicates"

const merge = (target, source, config = {}) => {
if (isBooleanJSONSchema(target) && target === true) return true
if (isBooleanJSONSchema(target) && target === false) return false
if (isBooleanJSONSchema(source) && source === true) return true
if (isBooleanJSONSchema(source) && source === false) return false

if (!isJSONSchema(target)) return source
if (!isJSONSchema(source)) return target

/**
* Merging properties from the source object into the target object
* only if they do not already exist in the target object.
*/
const merged = { ...source, ...target }

// merging required keyword
if (Array.isArray(source.required) && Array.isArray(target.required)) {
merged.required = [...new Set([...target.required, ...source.required])]
}

// merging properties keyword
if (source.properties && target.properties) {
const allPropertyNames = new Set([
...Object.keys(source.properties),
...Object.keys(target.properties),
])

merged.properties = {}
for (const name of allPropertyNames) {
const sourceProperty = source.properties[name] || {}
const targetProperty = target.properties[name] || {}

if (
(sourceProperty.readOnly && !config.includeReadOnly) ||
(sourceProperty.writeOnly && !config.includeWriteOnly)
) {
merged.required = (merged.required || []).filter((p) => p !== name)
} else {
merged.properties[name] = merge(targetProperty, sourceProperty, config)
}
}
}

// merging items keyword
if (isJSONSchema(source.items) && isJSONSchema(target.items)) {
merged.items = merge(target.items, source.items, config)
}

// merging contains keyword
if (isJSONSchema(source.contains) && isJSONSchema(target.contains)) {
merged.contains = merge(target.contains, source.contains, config)
}

// merging contentSchema keyword
if (
isJSONSchema(source.contentSchema) &&
isJSONSchema(target.contentSchema)
) {
merged.contentSchema = merge(
target.contentSchema,
source.contentSchema,
config
)
}

return merged
}

export default merge
127 changes: 11 additions & 116 deletions src/core/plugins/json-schema-2020-12/samples-extensions/fn/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,113 +11,8 @@ import { getType } from "./core/type"
import { typeCast } from "./core/utils"
import { hasExample, extractExample } from "./core/example"
import { pick as randomPick } from "./core/random"

const objectConstraints = ["maxProperties", "minProperties", "required"]
const arrayConstraints = [
"minItems",
"maxItems",
"uniqueItems",
"minContains",
"maxContains",
]
const numberConstraints = [
"minimum",
"maximum",
"exclusiveMinimum",
"exclusiveMaximum",
"multipleOf",
]
const stringConstraints = [
"minLength",
"maxLength",
"pattern",
"contentEncoding",
"contentMediaType",
]

const liftSampleHelper = (oldSchema, target, config = {}) => {
const setIfNotDefinedInTarget = (key) => {
if (target[key] === undefined && oldSchema[key] !== undefined) {
target[key] = oldSchema[key]
}
}

;[
"examples",
"example",
"default",
"enum",
"xml",
"type",
"const",
...objectConstraints,
...arrayConstraints,
...numberConstraints,
...stringConstraints,
].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) {
target.properties = {}
}
let props = objectify(oldSchema.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 (!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]
} else {
target.required.push(propName)
}
}
}
}
}
if (oldSchema.items) {
if (!target.items) {
target.items = {}
}
target.items = liftSampleHelper(oldSchema.items, target.items, config)
}

return target
}
import merge from "./core/merge"
import { isBooleanJSONSchema, isJSONSchemaObject } from "./core/predicates"

export const sampleFromSchemaGeneric = (
schema,
Expand All @@ -138,7 +33,7 @@ export const sampleFromSchemaGeneric = (
const schemaToAdd = typeCast(
hasOneOf ? randomPick(schema.oneOf) : randomPick(schema.anyOf)
)
liftSampleHelper(schemaToAdd, schema, config)
schema = merge(schema, schemaToAdd, config)
if (!schema.xml && schemaToAdd.xml) {
schema.xml = schemaToAdd.xml
}
Expand Down Expand Up @@ -489,9 +384,9 @@ export const sampleFromSchemaGeneric = (

if (Array.isArray(contains.anyOf)) {
sampleArray.push(
...contains.anyOf.map((i) =>
...contains.anyOf.map((anyOfSchema) =>
sampleFromSchemaGeneric(
liftSampleHelper(contains, i, config),
merge(anyOfSchema, contains, config),
config,
undefined,
respectXML
Expand All @@ -500,9 +395,9 @@ export const sampleFromSchemaGeneric = (
)
} else if (Array.isArray(contains.oneOf)) {
sampleArray.push(
...contains.oneOf.map((i) =>
...contains.oneOf.map((oneOfSchema) =>
sampleFromSchemaGeneric(
liftSampleHelper(contains, i, config),
merge(oneOfSchema, contains, config),
config,
undefined,
respectXML
Expand All @@ -528,7 +423,7 @@ export const sampleFromSchemaGeneric = (
sampleArray.push(
...items.anyOf.map((i) =>
sampleFromSchemaGeneric(
liftSampleHelper(items, i, config),
merge(i, items, config),
config,
undefined,
respectXML
Expand All @@ -539,7 +434,7 @@ export const sampleFromSchemaGeneric = (
sampleArray.push(
...items.oneOf.map((i) =>
sampleFromSchemaGeneric(
liftSampleHelper(items, i, config),
merge(i, items, config),
config,
undefined,
respectXML
Expand Down Expand Up @@ -591,14 +486,14 @@ export const sampleFromSchemaGeneric = (
return res
}

if (additionalProperties === true) {
if (isBooleanJSONSchema(additionalProperties)) {
if (respectXML) {
res[displayName].push({ additionalProp: "Anything can be here" })
} else {
res.additionalProp1 = {}
}
propertyAddedCounter++
} else if (additionalProperties) {
} else if (isJSONSchemaObject(additionalProperties)) {
const additionalProps = typeCast(additionalProperties)
const additionalPropSample = sampleFromSchemaGeneric(
additionalProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,7 @@ describe("sampleFromSchema", () => {
expect(sampleFromSchema(definition)).toEqual(expected)
})

it("should lift items with anyOf", () => {
it("should merge items with anyOf", () => {
const definition = {
type: "array",
anyOf: [
Expand All @@ -1172,7 +1172,7 @@ describe("sampleFromSchema", () => {
expect(sampleFromSchema(definition)).toEqual(expected)
})

it("should lift items with oneOf", () => {
it("should merge items with oneOf", () => {
const definition = {
type: "array",
oneOf: [
Expand Down

0 comments on commit 0911bb9

Please sign in to comment.