feat(samples): add support for custom formats, encoders and media types (#8905)
This change is specific to JSON Schema 2020-12 and OpenAPI 3.1.0. Refs #8577
This commit is contained in:
@@ -50,7 +50,10 @@ import {
|
|||||||
createXMLExample,
|
createXMLExample,
|
||||||
memoizedSampleFromSchema,
|
memoizedSampleFromSchema,
|
||||||
memoizedCreateXMLExample,
|
memoizedCreateXMLExample,
|
||||||
} from "./samples-extensions/fn"
|
encoderAPI,
|
||||||
|
mediaTypeAPI,
|
||||||
|
formatAPI,
|
||||||
|
} from "./samples-extensions/fn/index"
|
||||||
import { JSONSchemaDeepExpansionContext } from "./context"
|
import { JSONSchemaDeepExpansionContext } from "./context"
|
||||||
import { useFn, useConfig, useComponent, useIsExpandedDeeply } from "./hooks"
|
import { useFn, useConfig, useComponent, useIsExpandedDeeply } from "./hooks"
|
||||||
import { withJSONSchemaContext } from "./hoc"
|
import { withJSONSchemaContext } from "./hoc"
|
||||||
@@ -113,6 +116,9 @@ const JSONSchema202012Plugin = () => ({
|
|||||||
useIsExpandedDeeply,
|
useIsExpandedDeeply,
|
||||||
sampleFromSchema,
|
sampleFromSchema,
|
||||||
sampleFromSchemaGeneric,
|
sampleFromSchemaGeneric,
|
||||||
|
sampleEncoderAPI: encoderAPI,
|
||||||
|
sampleFormatAPI: formatAPI,
|
||||||
|
sampleMediaTypeAPI: mediaTypeAPI,
|
||||||
createXMLExample,
|
createXMLExample,
|
||||||
memoizedSampleFromSchema,
|
memoizedSampleFromSchema,
|
||||||
memoizedCreateXMLExample,
|
memoizedCreateXMLExample,
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
import randomBytes from "randombytes"
|
||||||
|
import RandExp from "randexp"
|
||||||
|
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 string = () => "string"
|
||||||
|
|
||||||
|
export const number = () => 0
|
||||||
|
|
||||||
|
export const integer = () => 0
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
export const isURI = (uri) => {
|
||||||
|
try {
|
||||||
|
return new URL(uri) && true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const encode7bit = (content) => Buffer.from(content).toString("ascii")
|
||||||
|
|
||||||
|
export default encode7bit
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const encode8bit = (content) => Buffer.from(content).toString("utf8")
|
||||||
|
|
||||||
|
export default encode8bit
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const encodeBase16 = (content) => Buffer.from(content).toString("hex")
|
||||||
|
|
||||||
|
export default encodeBase16
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const encodeBase64 = (content) => Buffer.from(content).toString("base64")
|
||||||
|
|
||||||
|
export default encodeBase64
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const encodeBinary = (content) => Buffer.from(content).toString("binary")
|
||||||
|
|
||||||
|
export default encodeBinary
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const dateTimeGenerator = () => new Date().toISOString()
|
||||||
|
|
||||||
|
export default dateTimeGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const dateGenerator = () => new Date().toISOString().substring(0, 10)
|
||||||
|
|
||||||
|
export default dateGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const doubleGenerator = () => 0.1
|
||||||
|
|
||||||
|
export default doubleGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const durationGenerator = () => "P3D" // expresses a duration of 3 days
|
||||||
|
|
||||||
|
export default durationGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const emailGenerator = () => "user@example.com"
|
||||||
|
|
||||||
|
export default emailGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const floatGenerator = () => 0.1
|
||||||
|
|
||||||
|
export default floatGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const hostnameGenerator = () => "example.com"
|
||||||
|
|
||||||
|
export default hostnameGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const idnEmailGenerator = () => "실례@example.com"
|
||||||
|
|
||||||
|
export default idnEmailGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const idnHostnameGenerator = () => "실례.com"
|
||||||
|
|
||||||
|
export default idnHostnameGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const int32Generator = () => (2 ** 30) >>> 0
|
||||||
|
|
||||||
|
export default int32Generator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const int64Generator = () => 2 ** 53 - 1
|
||||||
|
|
||||||
|
export default int64Generator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const ipv4Generator = () => "198.51.100.42"
|
||||||
|
|
||||||
|
export default ipv4Generator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const ipv6Generator = () => "2001:0db8:5b96:0000:0000:426f:8e17:642a"
|
||||||
|
|
||||||
|
export default ipv6Generator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const iriReferenceGenerator = () => "path/실례.html"
|
||||||
|
|
||||||
|
export default iriReferenceGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const iriGenerator = () => "https://실례.com/"
|
||||||
|
|
||||||
|
export default iriGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const jsonPointerGenerator = () => "/a/b/c"
|
||||||
|
|
||||||
|
export default jsonPointerGenerator
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
import { bytes } from "../../core/random"
|
||||||
|
|
||||||
|
const audioMediaTypesGenerators = {
|
||||||
|
"audio/*": () => bytes(25).toString("binary"),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default audioMediaTypesGenerators
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
import { bytes } from "../../core/random"
|
||||||
|
|
||||||
|
const imageMediaTypesGenerators = {
|
||||||
|
"image/*": () => bytes(25).toString("binary"),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default imageMediaTypesGenerators
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
import { bytes } from "../../core/random"
|
||||||
|
|
||||||
|
const videoMediaTypesGenerators = {
|
||||||
|
"video/*": () => bytes(25).toString("binary"),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default videoMediaTypesGenerators
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const passwordGenerator = () => "********"
|
||||||
|
|
||||||
|
export default passwordGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const regexGenerator = () => "^[a-z]+$"
|
||||||
|
|
||||||
|
export default regexGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const relativeJsonPointerGenerator = () => "1/0"
|
||||||
|
|
||||||
|
export default relativeJsonPointerGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const timeGenerator = () => new Date().toISOString().substring(11)
|
||||||
|
|
||||||
|
export default timeGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const uriReferenceGenerator = () => "path/index.html"
|
||||||
|
|
||||||
|
export default uriReferenceGenerator
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const uriTemplateGenerator = () =>
|
||||||
|
"https://example.com/dictionary/{term:1}/{term}"
|
||||||
|
|
||||||
|
export default uriTemplateGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const uriGenerator = () => "https://example.com/"
|
||||||
|
|
||||||
|
export default uriGenerator
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
const uuidGenerator = () => "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||||
|
|
||||||
|
export default uuidGenerator
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @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"
|
||||||
@@ -2,378 +2,23 @@
|
|||||||
* @prettier
|
* @prettier
|
||||||
*/
|
*/
|
||||||
import XML from "xml"
|
import XML from "xml"
|
||||||
import RandExp from "randexp"
|
|
||||||
import isEmpty from "lodash/isEmpty"
|
import isEmpty from "lodash/isEmpty"
|
||||||
import randomBytes from "randombytes"
|
|
||||||
|
|
||||||
import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils"
|
import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils"
|
||||||
import memoizeN from "../../../../helpers/memoizeN"
|
import memoizeN from "../../../../../helpers/memoizeN"
|
||||||
|
import typeMap from "./types/index"
|
||||||
const twentyFiveRandomBytesString = randomBytes(25).toString("binary")
|
import { isURI } from "./core/utils"
|
||||||
|
|
||||||
const stringFromRegex = (pattern) => {
|
|
||||||
try {
|
|
||||||
const randexp = new RandExp(pattern)
|
|
||||||
return randexp.gen()
|
|
||||||
} catch {
|
|
||||||
// invalid regex should not cause a crash (regex syntax varies across languages)
|
|
||||||
return "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentEncodings = {
|
|
||||||
"7bit": (content) => Buffer.from(content).toString("ascii"),
|
|
||||||
"8bit": (content) => Buffer.from(content).toString("utf8"),
|
|
||||||
binary: (content) => Buffer.from(content).toString("binary"),
|
|
||||||
"quoted-printable": (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
|
|
||||||
},
|
|
||||||
base16: (content) => Buffer.from(content).toString("hex"),
|
|
||||||
base32: (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
|
|
||||||
},
|
|
||||||
base64: (content) => Buffer.from(content).toString("base64"),
|
|
||||||
}
|
|
||||||
|
|
||||||
const encodeContent = (content, encoding) => {
|
|
||||||
if (typeof contentEncodings[encoding] === "function") {
|
|
||||||
return contentEncodings[encoding](content)
|
|
||||||
}
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
|
||||||
const contentMediaTypes = {
|
|
||||||
// text media type subtypes
|
|
||||||
"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",
|
|
||||||
// image media type subtypes
|
|
||||||
"image/*": () => twentyFiveRandomBytesString,
|
|
||||||
// audio media type subtypes
|
|
||||||
"audio/*": () => twentyFiveRandomBytesString,
|
|
||||||
// video media type subtypes
|
|
||||||
"video/*": () => twentyFiveRandomBytesString,
|
|
||||||
// application media type subtypes
|
|
||||||
"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/*": () => twentyFiveRandomBytesString,
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentFromMediaType = (mediaType) => {
|
|
||||||
const mediaTypeNoParams = mediaType.split(";").at(0)
|
|
||||||
const topLevelMediaType = `${mediaTypeNoParams.split("/").at(0)}/*`
|
|
||||||
|
|
||||||
if (typeof contentMediaTypes[mediaTypeNoParams] === "function") {
|
|
||||||
return contentMediaTypes[mediaTypeNoParams]()
|
|
||||||
}
|
|
||||||
if (typeof contentMediaTypes[topLevelMediaType] === "function") {
|
|
||||||
return contentMediaTypes[topLevelMediaType]()
|
|
||||||
}
|
|
||||||
|
|
||||||
return "string"
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
const primitives = {
|
|
||||||
string: (schema) => {
|
|
||||||
const { pattern, contentEncoding, contentMediaType } = schema
|
|
||||||
const content =
|
|
||||||
typeof pattern === "string"
|
|
||||||
? stringFromRegex(pattern)
|
|
||||||
: typeof contentMediaType === "string"
|
|
||||||
? contentFromMediaType(contentMediaType)
|
|
||||||
: "string"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_email: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "user@example.com"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_idn-email": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "실례@example.com"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_hostname: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "example.com"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_idn-hostname": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "실례.com"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_ipv4: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "198.51.100.42"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_ipv6: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "2001:0db8:5b96:0000:0000:426f:8e17:642a"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_uri: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "https://example.com/"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_uri-reference": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "path/index.html"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_iri: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "https://실례.com/"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_iri-reference": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "path/실례.html"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_uuid: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_uri-template": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "https://example.com/dictionary/{term:1}/{term}"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_json-pointer": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "/a/b/c"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_relative-json-pointer": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "1/0"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
"string_date-time": (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = new Date().toISOString()
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_date: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = new Date().toISOString().substring(0, 10)
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_time: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = new Date().toISOString().substring(11)
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_duration: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "P3D" // expresses a duration of 3 days
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_password: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "********"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
string_regex: (schema) => {
|
|
||||||
const { contentEncoding } = schema
|
|
||||||
const content = "^[a-z]+$"
|
|
||||||
return encodeContent(content, contentEncoding)
|
|
||||||
},
|
|
||||||
number: () => 0,
|
|
||||||
number_float: () => 0.1,
|
|
||||||
number_double: () => 0.1,
|
|
||||||
integer: () => 0,
|
|
||||||
integer_int32: () => (2 ** 30) >>> 0,
|
|
||||||
integer_int64: () => 2 ** 53 - 1,
|
|
||||||
boolean: (schema) =>
|
|
||||||
typeof schema.default === "boolean" ? schema.default : true,
|
|
||||||
null: () => null,
|
|
||||||
}
|
|
||||||
/* eslint-enable camelcase */
|
|
||||||
|
|
||||||
const primitive = (schema) => {
|
const primitive = (schema) => {
|
||||||
schema = objectify(schema)
|
schema = objectify(schema)
|
||||||
const { type: typeList, format } = schema
|
const { type: typeList } = schema
|
||||||
const type = Array.isArray(typeList) ? typeList.at(0) : typeList
|
const type = Array.isArray(typeList) ? typeList.at(0) : typeList
|
||||||
|
|
||||||
const fn = primitives[`${type}_${format}`] || primitives[type]
|
if (Object.hasOwn(typeMap, type)) {
|
||||||
|
return typeMap[type](schema)
|
||||||
return typeof fn === "function" ? fn(schema) : `Unknown Type: ${schema.type}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isURI = (uri) => {
|
return `Unknown Type: ${type}`
|
||||||
try {
|
|
||||||
return new URL(uri) && true
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -823,7 +468,7 @@ export const sampleFromSchemaGeneric = (
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
itemSamples = applyArrayConstraints(itemSamples, schema)
|
itemSamples = typeMap.array(schema, itemSamples)
|
||||||
if (xml.wrapped) {
|
if (xml.wrapped) {
|
||||||
res[displayName] = itemSamples
|
res[displayName] = itemSamples
|
||||||
if (!isEmpty(_attr)) {
|
if (!isEmpty(_attr)) {
|
||||||
@@ -961,7 +606,7 @@ export const sampleFromSchemaGeneric = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sampleArray = applyArrayConstraints(sampleArray, schema)
|
sampleArray = typeMap.array(schema, sampleArray)
|
||||||
if (respectXML && xml.wrapped) {
|
if (respectXML && xml.wrapped) {
|
||||||
res[displayName] = sampleArray
|
res[displayName] = sampleArray
|
||||||
if (!isEmpty(_attr)) {
|
if (!isEmpty(_attr)) {
|
||||||
@@ -1055,12 +700,6 @@ export const sampleFromSchemaGeneric = (
|
|||||||
} else if (schema) {
|
} else if (schema) {
|
||||||
// display schema default
|
// display schema default
|
||||||
value = primitive(schema)
|
value = primitive(schema)
|
||||||
if (typeof value === "number") {
|
|
||||||
value = applyNumberConstraints(value, schema)
|
|
||||||
}
|
|
||||||
if (typeof value === "string") {
|
|
||||||
value = applyStringConstraints(value, schema)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* @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, sampleArray) => {
|
||||||
|
return applyArrayConstraints(sampleArray, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default arrayType
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
const booleanType = (schema) => {
|
||||||
|
return typeof schema.default === "boolean" ? schema.default : true
|
||||||
|
}
|
||||||
|
|
||||||
|
export default booleanType
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* @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 typeMap
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
const nullType = () => {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default nullType
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
const objectType = () => {
|
||||||
|
throw new Error("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
export default objectType
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
import identity from "lodash/identity"
|
||||||
|
|
||||||
|
import { string as randomString, randexp } from "../core/random"
|
||||||
|
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) => {
|
||||||
|
const { pattern, format, contentEncoding, contentMediaType } = 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 (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