diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/constants.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/constants.js new file mode 100644 index 00000000000..8751dd4c374 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/constants.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +export const SCALAR_TYPES = ["integer", "number", "string", "boolean", "null"] + +export const ALL_TYPES = ["array", "object", ...SCALAR_TYPES] diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/fold-type.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/fold-type.js new file mode 100644 index 00000000000..c8a19f75781 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/fold-type.js @@ -0,0 +1,24 @@ +/** + * @prettier + */ +import { ALL_TYPES } from "./constants" + +const foldType = (type) => { + if (Array.isArray(type)) { + if (type.includes("array")) { + return "array" + } else if (type.includes("object")) { + return "object" + } else if (ALL_TYPES.includes(type.at(0))) { + return type.at(0) + } + } + + if (typeof type === "string" && ALL_TYPES.includes(type)) { + return type + } + + return null +} + +export default foldType diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/predicates.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/predicates.js new file mode 100644 index 00000000000..ae0bf044fd0 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/predicates.js @@ -0,0 +1,24 @@ +/** + * @prettier + */ +import isPlainObject from "lodash/isPlainObject" + +export const isURI = (uri) => { + try { + return new URL(uri) && true + } catch { + return false + } +} + +export const isBooleanJSONSchema = (schema) => { + return typeof schema === "boolean" +} + +export const isJSONSchemaObject = (schema) => { + return isPlainObject(schema) +} + +export const isJSONSchema = (schema) => { + return isBooleanJSONSchema(schema) || isJSONSchemaObject(schema) +} diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js index 6d6f5a18a3c..df40a0bac32 100644 --- a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js @@ -1,10 +1,23 @@ /** * @prettier */ -export const isURI = (uri) => { - try { - return new URL(uri) && true - } catch { - return false +import { isBooleanJSONSchema, isJSONSchemaObject } from "./predicates" + +export const fromJSONBooleanSchema = (schema) => { + if (schema === false) { + return { not: {} } } + + return {} +} + +export const typeCast = (schema) => { + if (isBooleanJSONSchema(schema)) { + return fromJSONBooleanSchema(schema) + } + if (!isJSONSchemaObject(schema)) { + return {} + } + + return schema } diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/main.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/main.js index e0775be5bfc..555aac47b2a 100644 --- a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/main.js +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/main.js @@ -4,22 +4,12 @@ import XML from "xml" import isEmpty from "lodash/isEmpty" -import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils" +import { objectify, normalizeArray, deeplyStripKey } from "core/utils" import memoizeN from "../../../../../helpers/memoizeN" import typeMap from "./types/index" -import { isURI } from "./core/utils" - -const primitive = (schema) => { - schema = objectify(schema) - const { type: typeList } = schema - const type = Array.isArray(typeList) ? typeList.at(0) : typeList - - if (Object.hasOwn(typeMap, type)) { - return typeMap[type](schema) - } - - return `Unknown Type: ${type}` -} +import { isURI } from "./core/predicates" +import foldType from "./core/fold-type" +import { typeCast } from "./core/utils" /** * Do a couple of quick sanity tests to ensure the value @@ -140,7 +130,9 @@ export const sampleFromSchemaGeneric = ( exampleOverride = undefined, respectXML = false ) => { - if (schema && isFunc(schema.toJS)) schema = schema.toJS() + if (typeof schema?.toJS === "function") schema = schema.toJS() + schema = typeCast(schema) + let usePlainValue = exampleOverride !== undefined || (schema && schema.example !== undefined) || @@ -151,7 +143,7 @@ export const sampleFromSchemaGeneric = ( const hasAnyOf = !usePlainValue && schema && schema.anyOf && schema.anyOf.length > 0 if (!usePlainValue && (hasOneOf || hasAnyOf)) { - const schemaToAdd = objectify(hasOneOf ? schema.oneOf[0] : schema.anyOf[0]) + const schemaToAdd = typeCast(hasOneOf ? schema.oneOf[0] : schema.anyOf[0]) liftSampleHelper(schemaToAdd, schema, config) if (!schema.xml && schemaToAdd.xml) { schema.xml = schemaToAdd.xml @@ -211,6 +203,7 @@ export const sampleFromSchemaGeneric = ( items, contains, } = schema || {} + type = foldType(type) let { includeReadOnly, includeWriteOnly } = config xml = xml || {} let { name, prefix, namespace } = xml @@ -339,9 +332,9 @@ export const sampleFromSchemaGeneric = ( } else if (enumAttrVal !== undefined) { _attr[props[propName].xml.name || propName] = enumAttrVal } else { - _attr[props[propName].xml.name || propName] = primitive( - props[propName] - ) + const propSchema = typeCast(props[propName]) + const attrName = props[propName].xml.name || propName + _attr[attrName] = typeMap[propSchema.type](propSchema) } return @@ -468,7 +461,7 @@ export const sampleFromSchemaGeneric = ( ] } - itemSamples = typeMap.array(schema, itemSamples) + itemSamples = typeMap.array(schema, { sample: itemSamples }) if (xml.wrapped) { res[displayName] = itemSamples if (!isEmpty(_attr)) { @@ -606,7 +599,7 @@ export const sampleFromSchemaGeneric = ( } } - sampleArray = typeMap.array(schema, sampleArray) + sampleArray = typeMap.array(schema, { sample: sampleArray }) if (respectXML && xml.wrapped) { res[displayName] = sampleArray if (!isEmpty(_attr)) { @@ -650,7 +643,7 @@ export const sampleFromSchemaGeneric = ( } propertyAddedCounter++ } else if (additionalProperties) { - const additionalProps = objectify(additionalProperties) + const additionalProps = typeCast(additionalProperties) const additionalPropSample = sampleFromSchemaGeneric( additionalProps, config, @@ -699,7 +692,15 @@ export const sampleFromSchemaGeneric = ( value = normalizeArray(schema.enum)[0] } else if (schema) { // display schema default - value = primitive(schema) + const contentSample = Object.hasOwn(schema, "contentSchema") + ? sampleFromSchemaGeneric( + typeCast(schema.contentSchema), + config, + undefined, + respectXML + ) + : undefined + value = typeMap[type](schema, { sample: contentSample }) } else { return } diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js index abef5fc8ca4..babaf619d55 100644 --- a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js @@ -45,8 +45,8 @@ export const applyArrayConstraints = (array, constraints = {}) => { return constrainedArray } -const arrayType = (schema, sampleArray) => { - return applyArrayConstraints(sampleArray, schema) +const arrayType = (schema, { sample }) => { + return applyArrayConstraints(sample, schema) } export default arrayType diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js index dbd05c34768..6d7aeda8cbb 100644 --- a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js @@ -19,4 +19,12 @@ const typeMap = { null: nullType, } -export default typeMap +export default new Proxy(typeMap, { + get(target, prop) { + if (Object.hasOwn(target, prop)) { + return target[prop] + } + + return () => `Unknown Type: ${prop}` + }, +}) diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js index 0db8dd999d1..1ba14250137 100644 --- a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js @@ -4,6 +4,7 @@ import identity from "lodash/identity" import { string as randomString, randexp } from "../core/random" +import { isJSONSchema } from "../core/predicates" import emailGenerator from "../generators/email" import idnEmailGenerator from "../generators/idn-email" import hostnameGenerator from "../generators/hostname" @@ -118,8 +119,9 @@ const applyStringConstraints = (string, constraints = {}) => { return constrainedString } -const stringType = (schema) => { - const { pattern, format, contentEncoding, contentMediaType } = schema +const stringType = (schema, { sample } = {}) => { + const { contentEncoding, contentMediaType, contentSchema } = schema + const { pattern, format } = schema const encode = encoderAPI(contentEncoding) || identity let generatedString @@ -127,7 +129,17 @@ const stringType = (schema) => { generatedString = randexp(pattern) } else if (typeof format === "string") { generatedString = generateFormat(schema) - } else if (typeof contentMediaType === "string") { + } else if ( + isJSONSchema(contentSchema) && + typeof contentMediaType !== "undefined" && + typeof sample !== "undefined" + ) { + if (Array.isArray(sample) || typeof sample === "object") { + generatedString = JSON.stringify(sample) + } else { + generatedString = String(sample) + } + } else if (typeof contentMediaType !== "undefined") { const mediaTypeGenerator = mediaTypeAPI(contentMediaType) if (typeof mediaTypeGenerator === "function") { generatedString = mediaTypeGenerator(schema) diff --git a/test/unit/core/plugins/json-schema-2020-12/samples-extensions/fn.js b/test/unit/core/plugins/json-schema-2020-12/samples-extensions/fn.js index 9e1309b95c2..e84fcfe730f 100644 --- a/test/unit/core/plugins/json-schema-2020-12/samples-extensions/fn.js +++ b/test/unit/core/plugins/json-schema-2020-12/samples-extensions/fn.js @@ -2,7 +2,6 @@ * @prettier * */ -import { Buffer } from "node:buffer" import { fromJS } from "immutable" import { createXMLExample, @@ -291,6 +290,58 @@ describe("sampleFromSchema", () => { expect(sampleFromSchema(definition)).toMatch(base64Regex) }) + it("should handle contentSchema defined as type=object", function () { + const definition = fromJS({ + type: "string", + contentMediaType: "application/json", + contentSchema: { + type: "object", + properties: { + a: { const: "b" }, + }, + }, + }) + + expect(sampleFromSchema(definition)).toStrictEqual('{"a":"b"}') + }) + + it("should handle contentSchema defined as type=string", function () { + const definition = fromJS({ + type: "string", + contentMediaType: "text/plain", + contentSchema: { + type: "string", + }, + }) + + expect(sampleFromSchema(definition)).toStrictEqual("string") + }) + + it("should handle contentSchema defined as type=number", function () { + const definition = fromJS({ + type: "string", + contentMediaType: "text/plain", + contentSchema: { + type: "number", + }, + }) + + expect(sampleFromSchema(definition)).toStrictEqual("0") + }) + + it("should handle contentSchema defined as type=number + contentEncoding", function () { + const definition = fromJS({ + type: "string", + contentEncoding: "base16", + contentMediaType: "text/plain", + contentSchema: { + type: "number", + }, + }) + + expect(sampleFromSchema(definition)).toStrictEqual("30") + }) + it("should handle type keyword defined as list of types", function () { const definition = fromJS({ type: ["object", "string"] }) const expected = {}