feat(samples): add support for contentMediaType keyword (#8903)
This change is specific to JSON Schema 2020-12 and OpenAPI 3.1.0. Refs #8577
This commit is contained in:
@@ -4,10 +4,13 @@
|
|||||||
import XML from "xml"
|
import XML from "xml"
|
||||||
import RandExp from "randexp"
|
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"
|
||||||
|
|
||||||
|
const twentyFiveRandomBytesString = randomBytes(25).toString("binary")
|
||||||
|
|
||||||
const stringFromRegex = (pattern) => {
|
const stringFromRegex = (pattern) => {
|
||||||
try {
|
try {
|
||||||
const randexp = new RandExp(pattern)
|
const randexp = new RandExp(pattern)
|
||||||
@@ -96,11 +99,57 @@ const encodeContent = (content, encoding) => {
|
|||||||
return 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 */
|
/* eslint-disable camelcase */
|
||||||
const primitives = {
|
const primitives = {
|
||||||
string: (schema) => {
|
string: (schema) => {
|
||||||
const { pattern, contentEncoding } = schema
|
const { pattern, contentEncoding, contentMediaType } = schema
|
||||||
const content = pattern ? stringFromRegex(pattern) : "string"
|
const content =
|
||||||
|
typeof pattern === "string"
|
||||||
|
? stringFromRegex(pattern)
|
||||||
|
: typeof contentMediaType === "string"
|
||||||
|
? contentFromMediaType(contentMediaType)
|
||||||
|
: "string"
|
||||||
return encodeContent(content, contentEncoding)
|
return encodeContent(content, contentEncoding)
|
||||||
},
|
},
|
||||||
string_email: (schema) => {
|
string_email: (schema) => {
|
||||||
@@ -349,7 +398,13 @@ const numberConstraints = [
|
|||||||
"exclusiveMaximum",
|
"exclusiveMaximum",
|
||||||
"multipleOf",
|
"multipleOf",
|
||||||
]
|
]
|
||||||
const stringConstraints = ["minLength", "maxLength", "pattern"]
|
const stringConstraints = [
|
||||||
|
"minLength",
|
||||||
|
"maxLength",
|
||||||
|
"pattern",
|
||||||
|
"contentEncoding",
|
||||||
|
"contentMediaType",
|
||||||
|
]
|
||||||
|
|
||||||
const liftSampleHelper = (oldSchema, target, config = {}) => {
|
const liftSampleHelper = (oldSchema, target, config = {}) => {
|
||||||
const setIfNotDefinedInTarget = (key) => {
|
const setIfNotDefinedInTarget = (key) => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* @prettier
|
* @prettier
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
import { Buffer } from "node:buffer"
|
||||||
import { fromJS } from "immutable"
|
import { fromJS } from "immutable"
|
||||||
import {
|
import {
|
||||||
createXMLExample,
|
createXMLExample,
|
||||||
@@ -147,6 +148,149 @@ describe("sampleFromSchema", () => {
|
|||||||
).toStrictEqual("path/실례.html") // act as an identity function when unknown encoding
|
).toStrictEqual("path/실례.html") // act as an identity function when unknown encoding
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should return appropriate example given contentMediaType", function () {
|
||||||
|
const sample = (schema) => sampleFromSchema(fromJS(schema))
|
||||||
|
|
||||||
|
expect(
|
||||||
|
sample({ type: "string", contentMediaType: "text/plain" })
|
||||||
|
).toStrictEqual("string")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/css",
|
||||||
|
})
|
||||||
|
).toStrictEqual(".selector { border: 1px solid red }")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/csv",
|
||||||
|
})
|
||||||
|
).toStrictEqual("value1,value2,value3")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/html",
|
||||||
|
})
|
||||||
|
).toStrictEqual("<p>content</p>")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/calendar",
|
||||||
|
})
|
||||||
|
).toStrictEqual("BEGIN:VCALENDAR")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/javascript",
|
||||||
|
})
|
||||||
|
).toStrictEqual("console.dir('Hello world!');")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/xml",
|
||||||
|
})
|
||||||
|
).toStrictEqual('<person age="30">John Doe</person>')
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/cql", // unknown mime type
|
||||||
|
})
|
||||||
|
).toStrictEqual("string")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "image/png",
|
||||||
|
})
|
||||||
|
).toHaveLength(25)
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "audio/mp4",
|
||||||
|
})
|
||||||
|
).toHaveLength(25)
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "video/3gpp",
|
||||||
|
})
|
||||||
|
).toHaveLength(25)
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "application/json",
|
||||||
|
})
|
||||||
|
).toStrictEqual('{"key":"value"}')
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "application/ld+json",
|
||||||
|
})
|
||||||
|
).toStrictEqual('{"name": "John Doe"}')
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "application/x-httpd-php",
|
||||||
|
})
|
||||||
|
).toStrictEqual("<?php echo '<p>Hello World!</p>'; ?>")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "application/rtf",
|
||||||
|
})
|
||||||
|
).toStrictEqual(String.raw`{\rtf1\adeflang1025\ansi\ansicpg1252\uc1`)
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "application/x-sh",
|
||||||
|
})
|
||||||
|
).toStrictEqual('echo "Hello World!"')
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "application/xhtml+xml",
|
||||||
|
})
|
||||||
|
).toStrictEqual("<p>content</p>")
|
||||||
|
expect(
|
||||||
|
sample({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "application/unknown",
|
||||||
|
})
|
||||||
|
).toHaveLength(25)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should strip parameters from contentMediaType and recognizes it", function () {
|
||||||
|
const definition = fromJS({
|
||||||
|
type: "string",
|
||||||
|
contentMediaType: "text/css",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(sampleFromSchema(definition)).toStrictEqual(
|
||||||
|
".selector { border: 1px solid red }"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle combination of format + contentMediaType", function () {
|
||||||
|
const definition = fromJS({
|
||||||
|
type: "string",
|
||||||
|
format: "hostname",
|
||||||
|
contentMediaType: "text/css",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(sampleFromSchema(definition)).toStrictEqual("example.com")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle combination of contentEncoding + contentMediaType", function () {
|
||||||
|
const definition = fromJS({
|
||||||
|
type: "string",
|
||||||
|
contentEncoding: "base64",
|
||||||
|
contentMediaType: "image/png",
|
||||||
|
})
|
||||||
|
const base64Regex =
|
||||||
|
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
|
||||||
|
|
||||||
|
expect(sampleFromSchema(definition)).toMatch(base64Regex)
|
||||||
|
})
|
||||||
|
|
||||||
it("should handle type keyword defined as list of types", function () {
|
it("should handle type keyword defined as list of types", function () {
|
||||||
const definition = fromJS({ type: ["object", "string"] })
|
const definition = fromJS({ type: ["object", "string"] })
|
||||||
const expected = {}
|
const expected = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user