Skip to content

Commit

Permalink
feat(samples): add support for contentMediaType keyword
Browse files Browse the repository at this point in the history
This change is specific to JSON Schema 2020-12
and OpenAPI 3.1.0.

Refs #8577
  • Loading branch information
char0n committed Jun 9, 2023
1 parent 5cf9276 commit 162b98e
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 3 deletions.
61 changes: 58 additions & 3 deletions src/core/plugins/json-schema-2020-12/samples-extensions/fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import XML from "xml"
import RandExp from "randexp"
import isEmpty from "lodash/isEmpty"
import randomBytes from "randombytes"

import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils"
import memoizeN from "../../../../helpers/memoizeN"

const twentyFiveRandomBytesString = randomBytes(25).toString("binary")

const stringFromRegex = (pattern) => {
try {
const randexp = new RandExp(pattern)
Expand Down Expand Up @@ -96,11 +99,57 @@ const encodeContent = (content, encoding) => {
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 } = schema
const content = pattern ? stringFromRegex(pattern) : "string"
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) => {
Expand Down Expand Up @@ -349,7 +398,13 @@ const numberConstraints = [
"exclusiveMaximum",
"multipleOf",
]
const stringConstraints = ["minLength", "maxLength", "pattern"]
const stringConstraints = [
"minLength",
"maxLength",
"pattern",
"contentEncoding",
"contentMediaType",
]

const liftSampleHelper = (oldSchema, target, config = {}) => {
const setIfNotDefinedInTarget = (key) => {
Expand Down
144 changes: 144 additions & 0 deletions test/unit/core/plugins/json-schema-2020-12/samples-extensions/fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @prettier
*
*/
import { Buffer } from "node:buffer"
import { fromJS } from "immutable"
import {
createXMLExample,
Expand Down Expand Up @@ -147,6 +148,149 @@ describe("sampleFromSchema", () => {
).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 () {
const definition = fromJS({ type: ["object", "string"] })
const expected = {}
Expand Down

0 comments on commit 162b98e

Please sign in to comment.