feat(json-schema): expose API that generates examples from JSON Schema (#9190)
This allows to use the samples API in a static way without fully instantiating SwaggerUI. Refs #9188
This commit is contained in:
@@ -44,16 +44,6 @@ import Accordion from "./components/Accordion/Accordion"
|
||||
import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton"
|
||||
import ChevronRightIcon from "./components/icons/ChevronRight"
|
||||
import { upperFirst, hasKeyword, isExpandable } from "./fn"
|
||||
import {
|
||||
sampleFromSchema,
|
||||
sampleFromSchemaGeneric,
|
||||
createXMLExample,
|
||||
memoizedSampleFromSchema,
|
||||
memoizedCreateXMLExample,
|
||||
encoderAPI,
|
||||
mediaTypeAPI,
|
||||
formatAPI,
|
||||
} from "./samples-extensions/fn/index"
|
||||
import { JSONSchemaDeepExpansionContext } from "./context"
|
||||
import { useFn, useConfig, useComponent, useIsExpandedDeeply } from "./hooks"
|
||||
import { withJSONSchemaContext } from "./hoc"
|
||||
@@ -114,14 +104,6 @@ const JSONSchema202012Plugin = () => ({
|
||||
useConfig,
|
||||
useComponent,
|
||||
useIsExpandedDeeply,
|
||||
sampleFromSchema,
|
||||
sampleFromSchemaGeneric,
|
||||
sampleEncoderAPI: encoderAPI,
|
||||
sampleFormatAPI: formatAPI,
|
||||
sampleMediaTypeAPI: mediaTypeAPI,
|
||||
createXMLExample,
|
||||
memoizedSampleFromSchema,
|
||||
memoizedCreateXMLExample,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import EncoderRegistry from "core/plugins/json-schema-2020-12/samples-extensions/fn/class/EncoderRegistry"
|
||||
|
||||
const registry = new EncoderRegistry()
|
||||
|
||||
const encoderAPI = (encodingName, encoder) => {
|
||||
if (typeof encoder === "function") {
|
||||
return registry.register(encodingName, encoder)
|
||||
} else if (encoder === null) {
|
||||
return registry.unregister(encodingName)
|
||||
}
|
||||
|
||||
return registry.get(encodingName)
|
||||
}
|
||||
encoderAPI.getDefaults = () => registry.defaults
|
||||
|
||||
export default encoderAPI
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import Registry from "../class/Registry"
|
||||
|
||||
const registry = new Registry()
|
||||
|
||||
const formatAPI = (format, generator) => {
|
||||
if (typeof generator === "function") {
|
||||
return registry.register(format, generator)
|
||||
} else if (generator === null) {
|
||||
return registry.unregister(format)
|
||||
}
|
||||
|
||||
return registry.get(format)
|
||||
}
|
||||
|
||||
export default formatAPI
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import MediaTypeRegistry from "../class/MediaTypeRegistry"
|
||||
|
||||
const registry = new MediaTypeRegistry()
|
||||
|
||||
const mediaTypeAPI = (mediaType, generator) => {
|
||||
if (typeof generator === "function") {
|
||||
return registry.register(mediaType, generator)
|
||||
} else if (generator === null) {
|
||||
return registry.unregister(mediaType)
|
||||
}
|
||||
|
||||
const mediaTypeNoParams = mediaType.split(";").at(0)
|
||||
const topLevelMediaType = `${mediaTypeNoParams.split("/").at(0)}/*`
|
||||
|
||||
return (
|
||||
registry.get(mediaType) ||
|
||||
registry.get(mediaTypeNoParams) ||
|
||||
registry.get(topLevelMediaType)
|
||||
)
|
||||
}
|
||||
mediaTypeAPI.getDefaults = () => registry.defaults
|
||||
|
||||
export default mediaTypeAPI
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import Registry from "./Registry"
|
||||
import encode7bit from "../encoders/7bit"
|
||||
import encode8bit from "../encoders/8bit"
|
||||
import encodeBinary from "../encoders/binary"
|
||||
import encodeQuotedPrintable from "../encoders/quoted-printable"
|
||||
import encodeBase16 from "../encoders/base16"
|
||||
import encodeBase32 from "../encoders/base32"
|
||||
import encodeBase64 from "../encoders/base64"
|
||||
|
||||
class EncoderRegistry extends Registry {
|
||||
#defaults = {
|
||||
"7bit": encode7bit,
|
||||
"8bit": encode8bit,
|
||||
binary: encodeBinary,
|
||||
"quoted-printable": encodeQuotedPrintable,
|
||||
base16: encodeBase16,
|
||||
base32: encodeBase32,
|
||||
base64: encodeBase64,
|
||||
}
|
||||
|
||||
data = { ...this.#defaults }
|
||||
|
||||
get defaults() {
|
||||
return { ...this.#defaults }
|
||||
}
|
||||
}
|
||||
|
||||
export default EncoderRegistry
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import Registry from "./Registry"
|
||||
import textMediaTypesGenerators from "../generators/media-types/text"
|
||||
import imageMediaTypesGenerators from "../generators/media-types/image"
|
||||
import audioMediaTypesGenerators from "../generators/media-types/audio"
|
||||
import videoMediaTypesGenerators from "../generators/media-types/video"
|
||||
import applicationMediaTypesGenerators from "../generators/media-types/application"
|
||||
|
||||
class MediaTypeRegistry extends Registry {
|
||||
#defaults = {
|
||||
...textMediaTypesGenerators,
|
||||
...imageMediaTypesGenerators,
|
||||
...audioMediaTypesGenerators,
|
||||
...videoMediaTypesGenerators,
|
||||
...applicationMediaTypesGenerators,
|
||||
}
|
||||
|
||||
data = { ...this.#defaults }
|
||||
|
||||
get defaults() {
|
||||
return { ...this.#defaults }
|
||||
}
|
||||
}
|
||||
|
||||
export default MediaTypeRegistry
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
class Registry {
|
||||
data = {}
|
||||
|
||||
register(name, value) {
|
||||
this.data[name] = value
|
||||
}
|
||||
|
||||
unregister(name) {
|
||||
if (typeof name === "undefined") {
|
||||
this.data = {}
|
||||
} else {
|
||||
delete this.data[name]
|
||||
}
|
||||
}
|
||||
|
||||
get(name) {
|
||||
return this.data[name]
|
||||
}
|
||||
}
|
||||
|
||||
export default Registry
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
export const SCALAR_TYPES = ["number", "integer", "string", "boolean", "null"]
|
||||
|
||||
export const ALL_TYPES = ["array", "object", ...SCALAR_TYPES]
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { isJSONSchemaObject } from "./predicates"
|
||||
|
||||
/**
|
||||
* Precedence of keywords that provides author defined values (top of the list = higher priority)
|
||||
*
|
||||
* ### examples
|
||||
* Array containing example values for the item defined by the schema.
|
||||
* Not guaranteed to be valid or invalid against the schema
|
||||
*
|
||||
* ### default
|
||||
* Default value for an item defined by the schema.
|
||||
* Is expected to be a valid instance of the schema.
|
||||
*
|
||||
* ### example
|
||||
* Deprecated. Part of OpenAPI 3.1.0 Schema Object dialect.
|
||||
* Represents single example. Equivalent of `examples` keywords
|
||||
* with single item.
|
||||
*/
|
||||
|
||||
export const hasExample = (schema) => {
|
||||
if (!isJSONSchemaObject(schema)) return false
|
||||
|
||||
const { examples, example, default: defaultVal } = schema
|
||||
|
||||
if (Array.isArray(examples) && examples.length >= 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof defaultVal !== "undefined") {
|
||||
return true
|
||||
}
|
||||
|
||||
return typeof example !== "undefined"
|
||||
}
|
||||
|
||||
export const extractExample = (schema) => {
|
||||
if (!isJSONSchemaObject(schema)) return null
|
||||
|
||||
const { examples, example, default: defaultVal } = schema
|
||||
|
||||
if (Array.isArray(examples) && examples.length >= 1) {
|
||||
return examples.at(0)
|
||||
}
|
||||
|
||||
if (typeof defaultVal !== "undefined") {
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
if (typeof example !== "undefined") {
|
||||
return example
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { normalizeArray as ensureArray } from "core/utils"
|
||||
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 the type keyword
|
||||
if (source.type && target.type) {
|
||||
if (Array.isArray(source.type) && typeof source.type === "string") {
|
||||
const mergedType = ensureArray(source.type).concat(target.type)
|
||||
merged.type = Array.from(new Set(mergedType))
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import isPlainObject from "lodash/isPlainObject"
|
||||
|
||||
export const isBooleanJSONSchema = (schema) => {
|
||||
return typeof schema === "boolean"
|
||||
}
|
||||
|
||||
export const isJSONSchemaObject = (schema) => {
|
||||
return isPlainObject(schema)
|
||||
}
|
||||
|
||||
export const isJSONSchema = (schema) => {
|
||||
return isBooleanJSONSchema(schema) || isJSONSchemaObject(schema)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import randomBytes from "randombytes"
|
||||
import RandExp from "randexp"
|
||||
|
||||
/**
|
||||
* Some of the functions returns constants. This is due to the nature
|
||||
* of SwaggerUI expectations - provide as stable data as possible.
|
||||
*
|
||||
* In future, we may decide to randomize these function and provide
|
||||
* true random values.
|
||||
*/
|
||||
|
||||
export const bytes = (length) => randomBytes(length)
|
||||
|
||||
export const randexp = (pattern) => {
|
||||
try {
|
||||
const randexpInstance = new RandExp(pattern)
|
||||
return randexpInstance.gen()
|
||||
} catch {
|
||||
// invalid regex should not cause a crash (regex syntax varies across languages)
|
||||
return "string"
|
||||
}
|
||||
}
|
||||
|
||||
export const pick = (list) => {
|
||||
return list.at(0)
|
||||
}
|
||||
|
||||
export const string = () => "string"
|
||||
|
||||
export const number = () => 0
|
||||
|
||||
export const integer = () => 0
|
||||
@@ -1,153 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { ALL_TYPES } from "./constants"
|
||||
import { isJSONSchemaObject } from "./predicates"
|
||||
import { pick as randomPick } from "./random"
|
||||
import { hasExample, extractExample } from "./example"
|
||||
|
||||
const inferringKeywords = {
|
||||
array: [
|
||||
"items",
|
||||
"prefixItems",
|
||||
"contains",
|
||||
"maxContains",
|
||||
"minContains",
|
||||
"maxItems",
|
||||
"minItems",
|
||||
"uniqueItems",
|
||||
"unevaluatedItems",
|
||||
],
|
||||
object: [
|
||||
"properties",
|
||||
"additionalProperties",
|
||||
"patternProperties",
|
||||
"propertyNames",
|
||||
"minProperties",
|
||||
"maxProperties",
|
||||
"required",
|
||||
"dependentSchemas",
|
||||
"dependentRequired",
|
||||
"unevaluatedProperties",
|
||||
],
|
||||
string: [
|
||||
"pattern",
|
||||
"format",
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"contentEncoding",
|
||||
"contentMediaType",
|
||||
"contentSchema",
|
||||
],
|
||||
integer: [
|
||||
"minimum",
|
||||
"maximum",
|
||||
"exclusiveMinimum",
|
||||
"exclusiveMaximum",
|
||||
"multipleOf",
|
||||
],
|
||||
}
|
||||
inferringKeywords.number = inferringKeywords.integer
|
||||
|
||||
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) => {
|
||||
if (Array.isArray(type) && type.length >= 1) {
|
||||
if (type.includes("array")) {
|
||||
return "array"
|
||||
} else if (type.includes("object")) {
|
||||
return "object"
|
||||
} else {
|
||||
const pickedType = randomPick(type)
|
||||
if (ALL_TYPES.includes(pickedType)) {
|
||||
return pickedType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ALL_TYPES.includes(type)) {
|
||||
return type
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const inferType = (schema, processedSchemas = new WeakSet()) => {
|
||||
if (!isJSONSchemaObject(schema)) return fallbackType
|
||||
if (processedSchemas.has(schema)) return fallbackType
|
||||
|
||||
processedSchemas.add(schema)
|
||||
|
||||
let { type, const: constant } = schema
|
||||
type = foldType(type)
|
||||
|
||||
// inferring type from inferring keywords
|
||||
if (typeof type !== "string") {
|
||||
const inferringTypes = Object.keys(inferringKeywords)
|
||||
|
||||
interrupt: for (let i = 0; i < inferringTypes.length; i += 1) {
|
||||
const inferringType = inferringTypes[i]
|
||||
const inferringTypeKeywords = inferringKeywords[inferringType]
|
||||
|
||||
for (let j = 0; j < inferringTypeKeywords.length; j += 1) {
|
||||
const inferringKeyword = inferringTypeKeywords[j]
|
||||
if (Object.hasOwn(schema, inferringKeyword)) {
|
||||
type = inferringType
|
||||
break interrupt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inferring type from const keyword
|
||||
if (typeof type !== "string" && typeof constant !== "undefined") {
|
||||
const constType = inferTypeFromValue(constant)
|
||||
type = typeof constType === "string" ? constType : type
|
||||
}
|
||||
|
||||
// inferring type from combining schemas
|
||||
if (typeof type !== "string") {
|
||||
const combineTypes = (keyword) => {
|
||||
if (Array.isArray(schema[keyword])) {
|
||||
const combinedTypes = schema[keyword].map((subSchema) =>
|
||||
inferType(subSchema, processedSchemas)
|
||||
)
|
||||
return foldType(combinedTypes)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const allOf = combineTypes("allOf")
|
||||
const anyOf = combineTypes("anyOf")
|
||||
const oneOf = combineTypes("oneOf")
|
||||
const not = schema.not ? inferType(schema.not, processedSchemas) : null
|
||||
|
||||
if (allOf || anyOf || oneOf || not) {
|
||||
type = foldType([allOf, anyOf, oneOf, not].filter(Boolean))
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
return type || fallbackType
|
||||
}
|
||||
|
||||
export const getType = (schema) => {
|
||||
return inferType(schema)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
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
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const encode7bit = (content) => Buffer.from(content).toString("ascii")
|
||||
|
||||
export default encode7bit
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const encode8bit = (content) => Buffer.from(content).toString("utf8")
|
||||
|
||||
export default encode8bit
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const encodeBase16 = (content) => Buffer.from(content).toString("hex")
|
||||
|
||||
export default encodeBase16
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const encodeBase32 = (content) => {
|
||||
const utf8Value = Buffer.from(content).toString("utf8")
|
||||
const base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
|
||||
let paddingCount = 0
|
||||
let base32Str = ""
|
||||
let buffer = 0
|
||||
let bufferLength = 0
|
||||
|
||||
for (let i = 0; i < utf8Value.length; i++) {
|
||||
buffer = (buffer << 8) | utf8Value.charCodeAt(i)
|
||||
bufferLength += 8
|
||||
|
||||
while (bufferLength >= 5) {
|
||||
base32Str += base32Alphabet.charAt((buffer >>> (bufferLength - 5)) & 31)
|
||||
bufferLength -= 5
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferLength > 0) {
|
||||
base32Str += base32Alphabet.charAt((buffer << (5 - bufferLength)) & 31)
|
||||
paddingCount = (8 - ((utf8Value.length * 8) % 5)) % 5
|
||||
}
|
||||
|
||||
for (let i = 0; i < paddingCount; i++) {
|
||||
base32Str += "="
|
||||
}
|
||||
|
||||
return base32Str
|
||||
}
|
||||
|
||||
export default encodeBase32
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const encodeBase64 = (content) => Buffer.from(content).toString("base64")
|
||||
|
||||
export default encodeBase64
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const encodeBinary = (content) => Buffer.from(content).toString("binary")
|
||||
|
||||
export default encodeBinary
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const encodeQuotedPrintable = (content) => {
|
||||
let quotedPrintable = ""
|
||||
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const charCode = content.charCodeAt(i)
|
||||
|
||||
if (charCode === 61) {
|
||||
// ASCII content of "="
|
||||
quotedPrintable += "=3D"
|
||||
} else if (
|
||||
(charCode >= 33 && charCode <= 60) ||
|
||||
(charCode >= 62 && charCode <= 126) ||
|
||||
charCode === 9 ||
|
||||
charCode === 32
|
||||
) {
|
||||
quotedPrintable += content.charAt(i)
|
||||
} else if (charCode === 13 || charCode === 10) {
|
||||
quotedPrintable += "\r\n"
|
||||
} else if (charCode > 126) {
|
||||
// convert non-ASCII characters to UTF-8 and encode each byte
|
||||
const utf8 = unescape(encodeURIComponent(content.charAt(i)))
|
||||
for (let j = 0; j < utf8.length; j++) {
|
||||
quotedPrintable +=
|
||||
"=" + ("0" + utf8.charCodeAt(j).toString(16)).slice(-2).toUpperCase()
|
||||
}
|
||||
} else {
|
||||
quotedPrintable +=
|
||||
"=" + ("0" + charCode.toString(16)).slice(-2).toUpperCase()
|
||||
}
|
||||
}
|
||||
|
||||
return quotedPrintable
|
||||
}
|
||||
|
||||
export default encodeQuotedPrintable
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const dateTimeGenerator = () => new Date().toISOString()
|
||||
|
||||
export default dateTimeGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const dateGenerator = () => new Date().toISOString().substring(0, 10)
|
||||
|
||||
export default dateGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const doubleGenerator = () => 0.1
|
||||
|
||||
export default doubleGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const durationGenerator = () => "P3D" // expresses a duration of 3 days
|
||||
|
||||
export default durationGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const emailGenerator = () => "user@example.com"
|
||||
|
||||
export default emailGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const floatGenerator = () => 0.1
|
||||
|
||||
export default floatGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const hostnameGenerator = () => "example.com"
|
||||
|
||||
export default hostnameGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const idnEmailGenerator = () => "실례@example.com"
|
||||
|
||||
export default idnEmailGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const idnHostnameGenerator = () => "실례.com"
|
||||
|
||||
export default idnHostnameGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const int32Generator = () => (2 ** 30) >>> 0
|
||||
|
||||
export default int32Generator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const int64Generator = () => 2 ** 53 - 1
|
||||
|
||||
export default int64Generator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const ipv4Generator = () => "198.51.100.42"
|
||||
|
||||
export default ipv4Generator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const ipv6Generator = () => "2001:0db8:5b96:0000:0000:426f:8e17:642a"
|
||||
|
||||
export default ipv6Generator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const iriReferenceGenerator = () => "path/실례.html"
|
||||
|
||||
export default iriReferenceGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const iriGenerator = () => "https://실례.com/"
|
||||
|
||||
export default iriGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const jsonPointerGenerator = () => "/a/b/c"
|
||||
|
||||
export default jsonPointerGenerator
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { bytes } from "../../core/random"
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
const applicationMediaTypesGenerators = {
|
||||
"application/json": () => '{"key":"value"}',
|
||||
"application/ld+json": () => '{"name": "John Doe"}',
|
||||
"application/x-httpd-php": () => "<?php echo '<p>Hello World!</p>'; ?>",
|
||||
"application/rtf": () => String.raw`{\rtf1\adeflang1025\ansi\ansicpg1252\uc1`,
|
||||
"application/x-sh": () => 'echo "Hello World!"',
|
||||
"application/xhtml+xml": () => "<p>content</p>",
|
||||
"application/*": () => bytes(25).toString("binary"),
|
||||
}
|
||||
|
||||
export default applicationMediaTypesGenerators
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { bytes } from "../../core/random"
|
||||
|
||||
const audioMediaTypesGenerators = {
|
||||
"audio/*": () => bytes(25).toString("binary"),
|
||||
}
|
||||
|
||||
export default audioMediaTypesGenerators
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { bytes } from "../../core/random"
|
||||
|
||||
const imageMediaTypesGenerators = {
|
||||
"image/*": () => bytes(25).toString("binary"),
|
||||
}
|
||||
|
||||
export default imageMediaTypesGenerators
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
const textMediaTypesGenerators = {
|
||||
"text/plain": () => "string",
|
||||
"text/css": () => ".selector { border: 1px solid red }",
|
||||
"text/csv": () => "value1,value2,value3",
|
||||
"text/html": () => "<p>content</p>",
|
||||
"text/calendar": () => "BEGIN:VCALENDAR",
|
||||
"text/javascript": () => "console.dir('Hello world!');",
|
||||
"text/xml": () => '<person age="30">John Doe</person>',
|
||||
"text/*": () => "string",
|
||||
}
|
||||
|
||||
export default textMediaTypesGenerators
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { bytes } from "../../core/random"
|
||||
|
||||
const videoMediaTypesGenerators = {
|
||||
"video/*": () => bytes(25).toString("binary"),
|
||||
}
|
||||
|
||||
export default videoMediaTypesGenerators
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const passwordGenerator = () => "********"
|
||||
|
||||
export default passwordGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const regexGenerator = () => "^[a-z]+$"
|
||||
|
||||
export default regexGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const relativeJsonPointerGenerator = () => "1/0"
|
||||
|
||||
export default relativeJsonPointerGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const timeGenerator = () => new Date().toISOString().substring(11)
|
||||
|
||||
export default timeGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const uriReferenceGenerator = () => "path/index.html"
|
||||
|
||||
export default uriReferenceGenerator
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const uriTemplateGenerator = () =>
|
||||
"https://example.com/dictionary/{term:1}/{term}"
|
||||
|
||||
export default uriTemplateGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const uriGenerator = () => "https://example.com/"
|
||||
|
||||
export default uriGenerator
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const uuidGenerator = () => "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||
|
||||
export default uuidGenerator
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
export {
|
||||
sampleFromSchema,
|
||||
sampleFromSchemaGeneric,
|
||||
createXMLExample,
|
||||
memoizedSampleFromSchema,
|
||||
memoizedCreateXMLExample,
|
||||
} from "./main"
|
||||
export { default as encoderAPI } from "./api/encoderAPI"
|
||||
export { default as formatAPI } from "./api/formatAPI"
|
||||
export { default as mediaTypeAPI } from "./api/mediaTypeAPI"
|
||||
@@ -1,521 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import XML from "xml"
|
||||
import isEmpty from "lodash/isEmpty"
|
||||
import isPlainObject from "lodash/isPlainObject"
|
||||
|
||||
import { objectify, normalizeArray } from "core/utils"
|
||||
import memoizeN from "core/utils/memoizeN"
|
||||
import typeMap from "./types/index"
|
||||
import { getType } from "./core/type"
|
||||
import { typeCast } from "./core/utils"
|
||||
import { hasExample, extractExample } from "./core/example"
|
||||
import { pick as randomPick } from "./core/random"
|
||||
import merge from "./core/merge"
|
||||
import { isBooleanJSONSchema, isJSONSchemaObject } from "./core/predicates"
|
||||
|
||||
export const sampleFromSchemaGeneric = (
|
||||
schema,
|
||||
config = {},
|
||||
exampleOverride = undefined,
|
||||
respectXML = false
|
||||
) => {
|
||||
if (typeof schema?.toJS === "function") schema = schema.toJS()
|
||||
schema = typeCast(schema)
|
||||
|
||||
let usePlainValue = exampleOverride !== undefined || hasExample(schema)
|
||||
// first check if there is the need of combining this schema with others required by allOf
|
||||
const hasOneOf =
|
||||
!usePlainValue && Array.isArray(schema.oneOf) && schema.oneOf.length > 0
|
||||
const hasAnyOf =
|
||||
!usePlainValue && Array.isArray(schema.anyOf) && schema.anyOf.length > 0
|
||||
if (!usePlainValue && (hasOneOf || hasAnyOf)) {
|
||||
const schemaToAdd = typeCast(
|
||||
hasOneOf ? randomPick(schema.oneOf) : randomPick(schema.anyOf)
|
||||
)
|
||||
schema = merge(schema, schemaToAdd, config)
|
||||
if (!schema.xml && schemaToAdd.xml) {
|
||||
schema.xml = schemaToAdd.xml
|
||||
}
|
||||
if (hasExample(schema) && hasExample(schemaToAdd)) {
|
||||
usePlainValue = true
|
||||
}
|
||||
}
|
||||
const _attr = {}
|
||||
let { xml, properties, additionalProperties, items, contains } = schema || {}
|
||||
let type = getType(schema)
|
||||
let { includeReadOnly, includeWriteOnly } = config
|
||||
xml = xml || {}
|
||||
let { name, prefix, namespace } = xml
|
||||
let displayName
|
||||
let res = {}
|
||||
|
||||
if (!Object.hasOwn(schema, "type")) {
|
||||
schema.type = type
|
||||
}
|
||||
|
||||
// set xml naming and attributes
|
||||
if (respectXML) {
|
||||
name = name || "notagname"
|
||||
// add prefix to name if exists
|
||||
displayName = (prefix ? `${prefix}:` : "") + name
|
||||
if (namespace) {
|
||||
//add prefix to namespace if exists
|
||||
let namespacePrefix = prefix ? `xmlns:${prefix}` : "xmlns"
|
||||
_attr[namespacePrefix] = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// init xml default response sample obj
|
||||
if (respectXML) {
|
||||
res[displayName] = []
|
||||
}
|
||||
|
||||
// add to result helper init for xml or json
|
||||
const props = objectify(properties)
|
||||
let addPropertyToResult
|
||||
let propertyAddedCounter = 0
|
||||
|
||||
const hasExceededMaxProperties = () =>
|
||||
Number.isInteger(schema.maxProperties) &&
|
||||
schema.maxProperties > 0 &&
|
||||
propertyAddedCounter >= schema.maxProperties
|
||||
|
||||
const requiredPropertiesToAdd = () => {
|
||||
if (!Array.isArray(schema.required) || schema.required.length === 0) {
|
||||
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 (!Array.isArray(schema.required)) return true
|
||||
if (schema.required.length === 0) return true
|
||||
|
||||
return !schema.required.includes(propName)
|
||||
}
|
||||
|
||||
const canAddProperty = (propName) => {
|
||||
if (!(Number.isInteger(schema.maxProperties) && schema.maxProperties > 0)) {
|
||||
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]) {
|
||||
// case it is a xml attribute
|
||||
props[propName].xml = props[propName].xml || {}
|
||||
|
||||
if (props[propName].xml.attribute) {
|
||||
const enumAttrVal = Array.isArray(props[propName].enum)
|
||||
? randomPick(props[propName].enum)
|
||||
: undefined
|
||||
if (hasExample(props[propName])) {
|
||||
_attr[props[propName].xml.name || propName] = extractExample(
|
||||
props[propName]
|
||||
)
|
||||
} else if (enumAttrVal !== undefined) {
|
||||
_attr[props[propName].xml.name || propName] = enumAttrVal
|
||||
} else {
|
||||
const propSchema = typeCast(props[propName])
|
||||
const propSchemaType = getType(propSchema)
|
||||
const attrName = props[propName].xml.name || propName
|
||||
_attr[attrName] = typeMap[propSchemaType](propSchema)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
props[propName].xml.name = props[propName].xml.name || propName
|
||||
} else if (!props[propName] && additionalProperties !== false) {
|
||||
// case only additionalProperty that is not defined in schema
|
||||
props[propName] = {
|
||||
xml: {
|
||||
name: propName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let t = sampleFromSchemaGeneric(
|
||||
props[propName],
|
||||
config,
|
||||
overrideE,
|
||||
respectXML
|
||||
)
|
||||
if (!canAddProperty(propName)) {
|
||||
return
|
||||
}
|
||||
|
||||
propertyAddedCounter++
|
||||
if (Array.isArray(t)) {
|
||||
res[displayName] = res[displayName].concat(t)
|
||||
} else {
|
||||
res[displayName].push(t)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addPropertyToResult = (propName, overrideE) => {
|
||||
if (!canAddProperty(propName)) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
isPlainObject(schema.discriminator?.mapping) &&
|
||||
schema.discriminator.propertyName === propName &&
|
||||
typeof schema.$$ref === "string"
|
||||
) {
|
||||
for (const pair in schema.discriminator.mapping) {
|
||||
if (schema.$$ref.search(schema.discriminator.mapping[pair]) !== -1) {
|
||||
res[propName] = pair
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res[propName] = sampleFromSchemaGeneric(
|
||||
props[propName],
|
||||
config,
|
||||
overrideE,
|
||||
respectXML
|
||||
)
|
||||
}
|
||||
propertyAddedCounter++
|
||||
}
|
||||
}
|
||||
|
||||
// check for plain value and if found use it to generate sample from it
|
||||
if (usePlainValue) {
|
||||
let sample
|
||||
if (exampleOverride !== undefined) {
|
||||
sample = exampleOverride
|
||||
} else {
|
||||
sample = extractExample(schema)
|
||||
}
|
||||
|
||||
// if json just return
|
||||
if (!respectXML) {
|
||||
// spacial case yaml parser can not know about
|
||||
if (typeof sample === "number" && type === "string") {
|
||||
return `${sample}`
|
||||
}
|
||||
// return if sample does not need any parsing
|
||||
if (typeof sample !== "string" || type === "string") {
|
||||
return sample
|
||||
}
|
||||
// check if sample is parsable or just a plain string
|
||||
try {
|
||||
return JSON.parse(sample)
|
||||
} catch {
|
||||
// sample is just plain string return it
|
||||
return sample
|
||||
}
|
||||
}
|
||||
|
||||
// generate xml sample recursively for array case
|
||||
if (type === "array") {
|
||||
if (!Array.isArray(sample)) {
|
||||
if (typeof sample === "string") {
|
||||
return sample
|
||||
}
|
||||
sample = [sample]
|
||||
}
|
||||
|
||||
let itemSamples = []
|
||||
|
||||
if (isJSONSchemaObject(items)) {
|
||||
items.xml = items.xml || xml || {}
|
||||
items.xml.name = items.xml.name || xml.name
|
||||
itemSamples = sample.map((s) =>
|
||||
sampleFromSchemaGeneric(items, config, s, respectXML)
|
||||
)
|
||||
}
|
||||
|
||||
if (isJSONSchemaObject(contains)) {
|
||||
contains.xml = contains.xml || xml || {}
|
||||
contains.xml.name = contains.xml.name || xml.name
|
||||
itemSamples = [
|
||||
sampleFromSchemaGeneric(contains, config, undefined, respectXML),
|
||||
...itemSamples,
|
||||
]
|
||||
}
|
||||
|
||||
itemSamples = typeMap.array(schema, { sample: itemSamples })
|
||||
if (xml.wrapped) {
|
||||
res[displayName] = itemSamples
|
||||
if (!isEmpty(_attr)) {
|
||||
res[displayName].push({ _attr: _attr })
|
||||
}
|
||||
} else {
|
||||
res = itemSamples
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// generate xml sample recursively for object case
|
||||
if (type === "object") {
|
||||
// case literal example
|
||||
if (typeof sample === "string") {
|
||||
return sample
|
||||
}
|
||||
for (const propName in sample) {
|
||||
if (!Object.hasOwn(sample, propName)) {
|
||||
continue
|
||||
}
|
||||
if (props[propName]?.readOnly && !includeReadOnly) {
|
||||
continue
|
||||
}
|
||||
if (props[propName]?.writeOnly && !includeWriteOnly) {
|
||||
continue
|
||||
}
|
||||
if (props[propName]?.xml?.attribute) {
|
||||
_attr[props[propName].xml.name || propName] = sample[propName]
|
||||
continue
|
||||
}
|
||||
addPropertyToResult(propName, sample[propName])
|
||||
}
|
||||
if (!isEmpty(_attr)) {
|
||||
res[displayName].push({ _attr: _attr })
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
res[displayName] = !isEmpty(_attr) ? [{ _attr: _attr }, sample] : sample
|
||||
return res
|
||||
}
|
||||
|
||||
// use schema to generate sample
|
||||
if (type === "array") {
|
||||
let sampleArray = []
|
||||
|
||||
if (isJSONSchemaObject(contains)) {
|
||||
if (respectXML) {
|
||||
contains.xml = contains.xml || schema.xml || {}
|
||||
contains.xml.name = contains.xml.name || xml.name
|
||||
}
|
||||
|
||||
if (Array.isArray(contains.anyOf)) {
|
||||
sampleArray.push(
|
||||
...contains.anyOf.map((anyOfSchema) =>
|
||||
sampleFromSchemaGeneric(
|
||||
merge(anyOfSchema, contains, config),
|
||||
config,
|
||||
undefined,
|
||||
respectXML
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (Array.isArray(contains.oneOf)) {
|
||||
sampleArray.push(
|
||||
...contains.oneOf.map((oneOfSchema) =>
|
||||
sampleFromSchemaGeneric(
|
||||
merge(oneOfSchema, contains, config),
|
||||
config,
|
||||
undefined,
|
||||
respectXML
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (!respectXML || (respectXML && xml.wrapped)) {
|
||||
sampleArray.push(
|
||||
sampleFromSchemaGeneric(contains, config, undefined, respectXML)
|
||||
)
|
||||
} else {
|
||||
return sampleFromSchemaGeneric(contains, config, undefined, respectXML)
|
||||
}
|
||||
}
|
||||
|
||||
if (isJSONSchemaObject(items)) {
|
||||
if (respectXML) {
|
||||
items.xml = items.xml || schema.xml || {}
|
||||
items.xml.name = items.xml.name || xml.name
|
||||
}
|
||||
|
||||
if (Array.isArray(items.anyOf)) {
|
||||
sampleArray.push(
|
||||
...items.anyOf.map((i) =>
|
||||
sampleFromSchemaGeneric(
|
||||
merge(i, items, config),
|
||||
config,
|
||||
undefined,
|
||||
respectXML
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (Array.isArray(items.oneOf)) {
|
||||
sampleArray.push(
|
||||
...items.oneOf.map((i) =>
|
||||
sampleFromSchemaGeneric(
|
||||
merge(i, items, config),
|
||||
config,
|
||||
undefined,
|
||||
respectXML
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (!respectXML || (respectXML && xml.wrapped)) {
|
||||
sampleArray.push(
|
||||
sampleFromSchemaGeneric(items, config, undefined, respectXML)
|
||||
)
|
||||
} else {
|
||||
return sampleFromSchemaGeneric(items, config, undefined, respectXML)
|
||||
}
|
||||
}
|
||||
|
||||
sampleArray = typeMap.array(schema, { sample: sampleArray })
|
||||
if (respectXML && xml.wrapped) {
|
||||
res[displayName] = sampleArray
|
||||
if (!isEmpty(_attr)) {
|
||||
res[displayName].push({ _attr: _attr })
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
return sampleArray
|
||||
}
|
||||
|
||||
if (type === "object") {
|
||||
for (let propName in props) {
|
||||
if (!Object.hasOwn(props, propName)) {
|
||||
continue
|
||||
}
|
||||
if (props[propName]?.deprecated) {
|
||||
continue
|
||||
}
|
||||
if (props[propName]?.readOnly && !includeReadOnly) {
|
||||
continue
|
||||
}
|
||||
if (props[propName]?.writeOnly && !includeWriteOnly) {
|
||||
continue
|
||||
}
|
||||
addPropertyToResult(propName)
|
||||
}
|
||||
if (respectXML && _attr) {
|
||||
res[displayName].push({ _attr: _attr })
|
||||
}
|
||||
|
||||
if (hasExceededMaxProperties()) {
|
||||
return res
|
||||
}
|
||||
|
||||
if (isBooleanJSONSchema(additionalProperties) && additionalProperties) {
|
||||
if (respectXML) {
|
||||
res[displayName].push({ additionalProp: "Anything can be here" })
|
||||
} else {
|
||||
res.additionalProp1 = {}
|
||||
}
|
||||
propertyAddedCounter++
|
||||
} else if (isJSONSchemaObject(additionalProperties)) {
|
||||
const additionalProps = additionalProperties
|
||||
const additionalPropSample = sampleFromSchemaGeneric(
|
||||
additionalProps,
|
||||
config,
|
||||
undefined,
|
||||
respectXML
|
||||
)
|
||||
|
||||
if (
|
||||
respectXML &&
|
||||
typeof additionalProps?.xml?.name === "string" &&
|
||||
additionalProps?.xml?.name !== "notagname"
|
||||
) {
|
||||
res[displayName].push(additionalPropSample)
|
||||
} else {
|
||||
const toGenerateCount =
|
||||
Number.isInteger(schema.minProperties) &&
|
||||
schema.minProperties > 0 &&
|
||||
propertyAddedCounter < schema.minProperties
|
||||
? schema.minProperties - propertyAddedCounter
|
||||
: 3
|
||||
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++
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
let value
|
||||
if (typeof schema.const !== "undefined") {
|
||||
// display const value
|
||||
value = schema.const
|
||||
} else if (schema && Array.isArray(schema.enum)) {
|
||||
//display enum first value
|
||||
value = randomPick(normalizeArray(schema.enum))
|
||||
} else {
|
||||
// display schema default
|
||||
const contentSample = isJSONSchemaObject(schema.contentSchema)
|
||||
? sampleFromSchemaGeneric(
|
||||
schema.contentSchema,
|
||||
config,
|
||||
undefined,
|
||||
respectXML
|
||||
)
|
||||
: undefined
|
||||
value = typeMap[type](schema, { sample: contentSample })
|
||||
}
|
||||
|
||||
if (respectXML) {
|
||||
res[displayName] = !isEmpty(_attr) ? [{ _attr: _attr }, value] : value
|
||||
return res
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
export const createXMLExample = (schema, config, o) => {
|
||||
const json = sampleFromSchemaGeneric(schema, config, o, true)
|
||||
if (!json) {
|
||||
return
|
||||
}
|
||||
if (typeof json === "string") {
|
||||
return json
|
||||
}
|
||||
return XML(json, { declaration: true, indent: "\t" })
|
||||
}
|
||||
|
||||
export const sampleFromSchema = (schema, config, o) => {
|
||||
return sampleFromSchemaGeneric(schema, config, o, false)
|
||||
}
|
||||
|
||||
const resolver = (arg1, arg2, arg3) => [
|
||||
arg1,
|
||||
JSON.stringify(arg2),
|
||||
JSON.stringify(arg3),
|
||||
]
|
||||
|
||||
export const memoizedCreateXMLExample = memoizeN(createXMLExample, resolver)
|
||||
|
||||
export const memoizedSampleFromSchema = memoizeN(sampleFromSchema, resolver)
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
export const applyArrayConstraints = (array, constraints = {}) => {
|
||||
const { minItems, maxItems, uniqueItems } = constraints
|
||||
const { contains, minContains, maxContains } = constraints
|
||||
let constrainedArray = [...array]
|
||||
|
||||
if (contains != null && typeof contains === "object") {
|
||||
if (Number.isInteger(minContains) && minContains > 1) {
|
||||
const containsItem = constrainedArray.at(0)
|
||||
for (let i = 1; i < minContains; i += 1) {
|
||||
constrainedArray.unshift(containsItem)
|
||||
}
|
||||
}
|
||||
if (Number.isInteger(maxContains) && maxContains > 0) {
|
||||
/**
|
||||
* This is noop. `minContains` already generate minimum required
|
||||
* number of items that satisfies `contains`. `maxContains` would
|
||||
* have no effect.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if (Number.isInteger(maxItems) && maxItems > 0) {
|
||||
constrainedArray = array.slice(0, maxItems)
|
||||
}
|
||||
if (Number.isInteger(minItems) && minItems > 0) {
|
||||
for (let i = 0; constrainedArray.length < minItems; i += 1) {
|
||||
constrainedArray.push(constrainedArray[i % constrainedArray.length])
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueItems === true) {
|
||||
/**
|
||||
* If uniqueItems is true, it implies that every item in the array must be unique.
|
||||
* This overrides any minItems constraint that cannot be satisfied with unique items.
|
||||
* So if minItems is greater than the number of unique items,
|
||||
* it should be reduced to the number of unique items.
|
||||
*/
|
||||
constrainedArray = Array.from(new Set(constrainedArray))
|
||||
}
|
||||
|
||||
return constrainedArray
|
||||
}
|
||||
|
||||
const arrayType = (schema, { sample }) => {
|
||||
return applyArrayConstraints(sample, schema)
|
||||
}
|
||||
|
||||
export default arrayType
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
const booleanType = (schema) => {
|
||||
return typeof schema.default === "boolean" ? schema.default : true
|
||||
}
|
||||
|
||||
export default booleanType
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import arrayType from "./array"
|
||||
import objectType from "./object"
|
||||
import stringType from "./string"
|
||||
import numberType from "./number"
|
||||
import integerType from "./integer"
|
||||
import booleanType from "./boolean"
|
||||
import nullType from "./null"
|
||||
|
||||
const typeMap = {
|
||||
array: arrayType,
|
||||
object: objectType,
|
||||
string: stringType,
|
||||
number: numberType,
|
||||
integer: integerType,
|
||||
boolean: booleanType,
|
||||
null: nullType,
|
||||
}
|
||||
|
||||
export default new Proxy(typeMap, {
|
||||
get(target, prop) {
|
||||
if (typeof prop === "string" && Object.hasOwn(target, prop)) {
|
||||
return target[prop]
|
||||
}
|
||||
|
||||
return () => `Unknown Type: ${prop}`
|
||||
},
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { integer as randomInteger } from "../core/random"
|
||||
import formatAPI from "../api/formatAPI"
|
||||
import int32Generator from "../generators/int32"
|
||||
import int64Generator from "../generators/int64"
|
||||
|
||||
const generateFormat = (schema) => {
|
||||
const { format } = schema
|
||||
|
||||
const formatGenerator = formatAPI(format)
|
||||
if (typeof formatGenerator === "function") {
|
||||
return formatGenerator(schema)
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "int32": {
|
||||
return int32Generator()
|
||||
}
|
||||
case "int64": {
|
||||
return int64Generator()
|
||||
}
|
||||
}
|
||||
|
||||
return randomInteger()
|
||||
}
|
||||
const integerType = (schema) => {
|
||||
const { format } = schema
|
||||
|
||||
if (typeof format === "string") {
|
||||
return generateFormat(schema)
|
||||
}
|
||||
|
||||
return randomInteger()
|
||||
}
|
||||
|
||||
export default integerType
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
const nullType = () => {
|
||||
return null
|
||||
}
|
||||
|
||||
export default nullType
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { number as randomNumber } from "../core/random"
|
||||
import formatAPI from "../api/formatAPI"
|
||||
import floatGenerator from "../generators/float"
|
||||
import doubleGenerator from "../generators/double"
|
||||
|
||||
const generateFormat = (schema) => {
|
||||
const { format } = schema
|
||||
|
||||
const formatGenerator = formatAPI(format)
|
||||
if (typeof formatGenerator === "function") {
|
||||
return formatGenerator(schema)
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "float": {
|
||||
return floatGenerator()
|
||||
}
|
||||
case "double": {
|
||||
return doubleGenerator()
|
||||
}
|
||||
}
|
||||
|
||||
return randomNumber()
|
||||
}
|
||||
|
||||
const applyNumberConstraints = (number, constraints = {}) => {
|
||||
const { minimum, maximum, exclusiveMinimum, exclusiveMaximum } = constraints
|
||||
const { multipleOf } = constraints
|
||||
const epsilon = Number.isInteger(number) ? 1 : Number.EPSILON
|
||||
let minValue = typeof minimum === "number" ? minimum : null
|
||||
let maxValue = typeof maximum === "number" ? maximum : null
|
||||
let constrainedNumber = number
|
||||
|
||||
if (typeof exclusiveMinimum === "number") {
|
||||
minValue =
|
||||
minValue !== null
|
||||
? Math.max(minValue, exclusiveMinimum + epsilon)
|
||||
: exclusiveMinimum + epsilon
|
||||
}
|
||||
if (typeof exclusiveMaximum === "number") {
|
||||
maxValue =
|
||||
maxValue !== null
|
||||
? Math.min(maxValue, exclusiveMaximum - epsilon)
|
||||
: exclusiveMaximum - epsilon
|
||||
}
|
||||
constrainedNumber =
|
||||
(minValue > maxValue && number) || minValue || maxValue || constrainedNumber
|
||||
|
||||
if (typeof multipleOf === "number" && multipleOf > 0) {
|
||||
const remainder = constrainedNumber % multipleOf
|
||||
constrainedNumber =
|
||||
remainder === 0
|
||||
? constrainedNumber
|
||||
: constrainedNumber + multipleOf - remainder
|
||||
}
|
||||
|
||||
return constrainedNumber
|
||||
}
|
||||
|
||||
const numberType = (schema) => {
|
||||
const { format } = schema
|
||||
let generatedNumber
|
||||
|
||||
if (typeof format === "string") {
|
||||
generatedNumber = generateFormat(schema)
|
||||
} else {
|
||||
generatedNumber = randomNumber()
|
||||
}
|
||||
|
||||
return applyNumberConstraints(generatedNumber, schema)
|
||||
}
|
||||
|
||||
export default numberType
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
const objectType = () => {
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
|
||||
export default objectType
|
||||
@@ -1,154 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
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"
|
||||
import idnHostnameGenerator from "../generators/idn-hostname"
|
||||
import ipv4Generator from "../generators/ipv4"
|
||||
import ipv6Generator from "../generators/ipv6"
|
||||
import uriGenerator from "../generators/uri"
|
||||
import uriReferenceGenerator from "../generators/uri-reference"
|
||||
import iriGenerator from "../generators/iri"
|
||||
import iriReferenceGenerator from "../generators/iri-reference"
|
||||
import uuidGenerator from "../generators/uuid"
|
||||
import uriTemplateGenerator from "../generators/uri-template"
|
||||
import jsonPointerGenerator from "../generators/json-pointer"
|
||||
import relativeJsonPointerGenerator from "../generators/relative-json-pointer"
|
||||
import dateTimeGenerator from "../generators/date-time"
|
||||
import dateGenerator from "../generators/date"
|
||||
import timeGenerator from "../generators/time"
|
||||
import durationGenerator from "../generators/duration"
|
||||
import passwordGenerator from "../generators/password"
|
||||
import regexGenerator from "../generators/regex"
|
||||
import formatAPI from "../api/formatAPI"
|
||||
import encoderAPI from "../api/encoderAPI"
|
||||
import mediaTypeAPI from "../api/mediaTypeAPI"
|
||||
|
||||
const generateFormat = (schema) => {
|
||||
const { format } = schema
|
||||
|
||||
const formatGenerator = formatAPI(format)
|
||||
if (typeof formatGenerator === "function") {
|
||||
return formatGenerator(schema)
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "email": {
|
||||
return emailGenerator()
|
||||
}
|
||||
case "idn-email": {
|
||||
return idnEmailGenerator()
|
||||
}
|
||||
case "hostname": {
|
||||
return hostnameGenerator()
|
||||
}
|
||||
case "idn-hostname": {
|
||||
return idnHostnameGenerator()
|
||||
}
|
||||
case "ipv4": {
|
||||
return ipv4Generator()
|
||||
}
|
||||
case "ipv6": {
|
||||
return ipv6Generator()
|
||||
}
|
||||
case "uri": {
|
||||
return uriGenerator()
|
||||
}
|
||||
case "uri-reference": {
|
||||
return uriReferenceGenerator()
|
||||
}
|
||||
case "iri": {
|
||||
return iriGenerator()
|
||||
}
|
||||
case "iri-reference": {
|
||||
return iriReferenceGenerator()
|
||||
}
|
||||
case "uuid": {
|
||||
return uuidGenerator()
|
||||
}
|
||||
case "uri-template": {
|
||||
return uriTemplateGenerator()
|
||||
}
|
||||
case "json-pointer": {
|
||||
return jsonPointerGenerator()
|
||||
}
|
||||
case "relative-json-pointer": {
|
||||
return relativeJsonPointerGenerator()
|
||||
}
|
||||
case "date-time": {
|
||||
return dateTimeGenerator()
|
||||
}
|
||||
case "date": {
|
||||
return dateGenerator()
|
||||
}
|
||||
case "time": {
|
||||
return timeGenerator()
|
||||
}
|
||||
case "duration": {
|
||||
return durationGenerator()
|
||||
}
|
||||
case "password": {
|
||||
return passwordGenerator()
|
||||
}
|
||||
case "regex": {
|
||||
return regexGenerator()
|
||||
}
|
||||
}
|
||||
|
||||
return randomString()
|
||||
}
|
||||
|
||||
const applyStringConstraints = (string, constraints = {}) => {
|
||||
const { maxLength, minLength } = constraints
|
||||
let constrainedString = string
|
||||
|
||||
if (Number.isInteger(maxLength) && maxLength > 0) {
|
||||
constrainedString = constrainedString.slice(0, maxLength)
|
||||
}
|
||||
if (Number.isInteger(minLength) && minLength > 0) {
|
||||
let i = 0
|
||||
while (constrainedString.length < minLength) {
|
||||
constrainedString += constrainedString[i++ % constrainedString.length]
|
||||
}
|
||||
}
|
||||
|
||||
return constrainedString
|
||||
}
|
||||
const stringType = (schema, { sample } = {}) => {
|
||||
const { contentEncoding, contentMediaType, contentSchema } = schema
|
||||
const { pattern, format } = schema
|
||||
const encode = encoderAPI(contentEncoding) || identity
|
||||
let generatedString
|
||||
|
||||
if (typeof pattern === "string") {
|
||||
generatedString = randexp(pattern)
|
||||
} else if (typeof format === "string") {
|
||||
generatedString = generateFormat(schema)
|
||||
} else if (
|
||||
isJSONSchema(contentSchema) &&
|
||||
typeof contentMediaType === "string" &&
|
||||
typeof sample !== "undefined"
|
||||
) {
|
||||
if (Array.isArray(sample) || typeof sample === "object") {
|
||||
generatedString = JSON.stringify(sample)
|
||||
} else {
|
||||
generatedString = String(sample)
|
||||
}
|
||||
} else if (typeof contentMediaType === "string") {
|
||||
const mediaTypeGenerator = mediaTypeAPI(contentMediaType)
|
||||
if (typeof mediaTypeGenerator === "function") {
|
||||
generatedString = mediaTypeGenerator(schema)
|
||||
}
|
||||
} else {
|
||||
generatedString = randomString()
|
||||
}
|
||||
|
||||
return encode(applyStringConstraints(generatedString, schema))
|
||||
}
|
||||
|
||||
export default stringType
|
||||
Reference in New Issue
Block a user