diff --git a/src/core/plugins/json-schema-2020-12/index.js b/src/core/plugins/json-schema-2020-12/index.js index a92bfa8eef3..aceee751acf 100644 --- a/src/core/plugins/json-schema-2020-12/index.js +++ b/src/core/plugins/json-schema-2020-12/index.js @@ -50,7 +50,10 @@ import { createXMLExample, memoizedSampleFromSchema, memoizedCreateXMLExample, -} from "./samples-extensions/fn" + encoderAPI, + mediaTypeAPI, + formatAPI, +} from "./samples-extensions/fn/index" import { JSONSchemaDeepExpansionContext } from "./context" import { useFn, useConfig, useComponent, useIsExpandedDeeply } from "./hooks" import { withJSONSchemaContext } from "./hoc" @@ -113,6 +116,9 @@ const JSONSchema202012Plugin = () => ({ useIsExpandedDeeply, sampleFromSchema, sampleFromSchemaGeneric, + sampleEncoderAPI: encoderAPI, + sampleFormatAPI: formatAPI, + sampleMediaTypeAPI: mediaTypeAPI, createXMLExample, memoizedSampleFromSchema, memoizedCreateXMLExample, diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/encoderAPI.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/encoderAPI.js new file mode 100644 index 00000000000..4d4ee996d12 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/encoderAPI.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/formatAPI.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/formatAPI.js new file mode 100644 index 00000000000..9d1cd6b1d63 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/formatAPI.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/mediaTypeAPI.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/mediaTypeAPI.js new file mode 100644 index 00000000000..9fac528bcc4 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/api/mediaTypeAPI.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/EncoderRegistry.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/EncoderRegistry.js new file mode 100644 index 00000000000..f9c575adf9a --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/EncoderRegistry.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/MediaTypeRegistry.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/MediaTypeRegistry.js new file mode 100644 index 00000000000..26be49ec2b7 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/MediaTypeRegistry.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/Registry.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/Registry.js new file mode 100644 index 00000000000..e47f95074a4 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/class/Registry.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/random.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/random.js new file mode 100644 index 00000000000..cd2ec61efad --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/random.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js new file mode 100644 index 00000000000..6d6f5a18a3c --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js @@ -0,0 +1,10 @@ +/** + * @prettier + */ +export const isURI = (uri) => { + try { + return new URL(uri) && true + } catch { + return false + } +} diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/7bit.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/7bit.js new file mode 100644 index 00000000000..ac690cdd5fa --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/7bit.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const encode7bit = (content) => Buffer.from(content).toString("ascii") + +export default encode7bit diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/8bit.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/8bit.js new file mode 100644 index 00000000000..5f7e7cc80c0 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/8bit.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const encode8bit = (content) => Buffer.from(content).toString("utf8") + +export default encode8bit diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base16.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base16.js new file mode 100644 index 00000000000..eca3a35a710 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base16.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const encodeBase16 = (content) => Buffer.from(content).toString("hex") + +export default encodeBase16 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base32.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base32.js new file mode 100644 index 00000000000..583a5a02865 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base32.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base64.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base64.js new file mode 100644 index 00000000000..bb6378d1df3 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base64.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const encodeBase64 = (content) => Buffer.from(content).toString("base64") + +export default encodeBase64 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/binary.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/binary.js new file mode 100644 index 00000000000..0b1dd143f36 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/binary.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const encodeBinary = (content) => Buffer.from(content).toString("binary") + +export default encodeBinary diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/quoted-printable.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/quoted-printable.js new file mode 100644 index 00000000000..2beb4465b9e --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/quoted-printable.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date-time.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date-time.js new file mode 100644 index 00000000000..30cee3a3a2d --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date-time.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const dateTimeGenerator = () => new Date().toISOString() + +export default dateTimeGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date.js new file mode 100644 index 00000000000..a1c217dbe50 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const dateGenerator = () => new Date().toISOString().substring(0, 10) + +export default dateGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/double.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/double.js new file mode 100644 index 00000000000..ec0e871097e --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/double.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const doubleGenerator = () => 0.1 + +export default doubleGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/duration.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/duration.js new file mode 100644 index 00000000000..ae5a8d73535 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/duration.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const durationGenerator = () => "P3D" // expresses a duration of 3 days + +export default durationGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/email.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/email.js new file mode 100644 index 00000000000..61ac4baab18 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/email.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const emailGenerator = () => "user@example.com" + +export default emailGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/float.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/float.js new file mode 100644 index 00000000000..5b2f9c2acdc --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/float.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const floatGenerator = () => 0.1 + +export default floatGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/hostname.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/hostname.js new file mode 100644 index 00000000000..a1a65ff446a --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/hostname.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const hostnameGenerator = () => "example.com" + +export default hostnameGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-email.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-email.js new file mode 100644 index 00000000000..948a89b8924 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-email.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const idnEmailGenerator = () => "실례@example.com" + +export default idnEmailGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-hostname.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-hostname.js new file mode 100644 index 00000000000..7c50a026432 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-hostname.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const idnHostnameGenerator = () => "실례.com" + +export default idnHostnameGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int32.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int32.js new file mode 100644 index 00000000000..5c31cb0047e --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int32.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const int32Generator = () => (2 ** 30) >>> 0 + +export default int32Generator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int64.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int64.js new file mode 100644 index 00000000000..611d1c593cb --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int64.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const int64Generator = () => 2 ** 53 - 1 + +export default int64Generator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv4.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv4.js new file mode 100644 index 00000000000..8a18d45ce2b --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv4.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const ipv4Generator = () => "198.51.100.42" + +export default ipv4Generator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv6.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv6.js new file mode 100644 index 00000000000..d933b87552c --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv6.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const ipv6Generator = () => "2001:0db8:5b96:0000:0000:426f:8e17:642a" + +export default ipv6Generator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri-reference.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri-reference.js new file mode 100644 index 00000000000..9cfe9e3395b --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri-reference.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const iriReferenceGenerator = () => "path/실례.html" + +export default iriReferenceGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri.js new file mode 100644 index 00000000000..7930babd2fa --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const iriGenerator = () => "https://실례.com/" + +export default iriGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/json-pointer.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/json-pointer.js new file mode 100644 index 00000000000..0f14a483206 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/json-pointer.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const jsonPointerGenerator = () => "/a/b/c" + +export default jsonPointerGenerator diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/application.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/application.js new file mode 100644 index 00000000000..b6ad219803d --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/application.js @@ -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": () => "Hello World!
'; ?>", + "application/rtf": () => String.raw`{\rtf1\adeflang1025\ansi\ansicpg1252\uc1`, + "application/x-sh": () => 'echo "Hello World!"', + "application/xhtml+xml": () => "content
", + "application/*": () => bytes(25).toString("binary"), +} + +export default applicationMediaTypesGenerators diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/audio.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/audio.js new file mode 100644 index 00000000000..8cc505e2c55 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/audio.js @@ -0,0 +1,10 @@ +/** + * @prettier + */ +import { bytes } from "../../core/random" + +const audioMediaTypesGenerators = { + "audio/*": () => bytes(25).toString("binary"), +} + +export default audioMediaTypesGenerators diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/image.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/image.js new file mode 100644 index 00000000000..5c54e30ce85 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/image.js @@ -0,0 +1,10 @@ +/** + * @prettier + */ +import { bytes } from "../../core/random" + +const imageMediaTypesGenerators = { + "image/*": () => bytes(25).toString("binary"), +} + +export default imageMediaTypesGenerators diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/text.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/text.js new file mode 100644 index 00000000000..6d5a1201138 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/text.js @@ -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": () => "content
", + "text/calendar": () => "BEGIN:VCALENDAR", + "text/javascript": () => "console.dir('Hello world!');", + "text/xml": () => 'content
", - "text/calendar": () => "BEGIN:VCALENDAR", - "text/javascript": () => "console.dir('Hello world!');", - "text/xml": () => 'content
", - "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 */ +import memoizeN from "../../../../../helpers/memoizeN" +import typeMap from "./types/index" +import { isURI } from "./core/utils" const primitive = (schema) => { schema = objectify(schema) - const { type: typeList, format } = schema + const { type: typeList } = schema const type = Array.isArray(typeList) ? typeList.at(0) : typeList - const fn = primitives[`${type}_${format}`] || primitives[type] - - return typeof fn === "function" ? fn(schema) : `Unknown Type: ${schema.type}` -} - -const isURI = (uri) => { - 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)) + if (Object.hasOwn(typeMap, type)) { + return typeMap[type](schema) } - 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 + return `Unknown Type: ${type}` } /** @@ -823,7 +468,7 @@ export const sampleFromSchemaGeneric = ( ] } - itemSamples = applyArrayConstraints(itemSamples, schema) + itemSamples = typeMap.array(schema, itemSamples) if (xml.wrapped) { res[displayName] = itemSamples if (!isEmpty(_attr)) { @@ -961,7 +606,7 @@ export const sampleFromSchemaGeneric = ( } } - sampleArray = applyArrayConstraints(sampleArray, schema) + sampleArray = typeMap.array(schema, sampleArray) if (respectXML && xml.wrapped) { res[displayName] = sampleArray if (!isEmpty(_attr)) { @@ -1055,12 +700,6 @@ export const sampleFromSchemaGeneric = ( } else if (schema) { // display schema default value = primitive(schema) - if (typeof value === "number") { - value = applyNumberConstraints(value, schema) - } - if (typeof value === "string") { - value = applyStringConstraints(value, schema) - } } else { return } diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js new file mode 100644 index 00000000000..abef5fc8ca4 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/boolean.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/boolean.js new file mode 100644 index 00000000000..e506215ff9f --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/boolean.js @@ -0,0 +1,9 @@ +/** + * @prettier + */ + +const booleanType = (schema) => { + return typeof schema.default === "boolean" ? schema.default : true +} + +export default booleanType diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js new file mode 100644 index 00000000000..dbd05c34768 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/integer.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/integer.js new file mode 100644 index 00000000000..f5159a46789 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/integer.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/null.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/null.js new file mode 100644 index 00000000000..f7fd20d2bb8 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/null.js @@ -0,0 +1,9 @@ +/** + * @prettier + */ + +const nullType = () => { + return null +} + +export default nullType diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/number.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/number.js new file mode 100644 index 00000000000..afc6720e0e9 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/number.js @@ -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 diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/object.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/object.js new file mode 100644 index 00000000000..bc76c580e8c --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/object.js @@ -0,0 +1,9 @@ +/** + * @prettier + */ + +const objectType = () => { + throw new Error("Not implemented") +} + +export default objectType diff --git a/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js new file mode 100644 index 00000000000..0db8dd999d1 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js @@ -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