refactor(samples): move example generation code to samples plugin (#8727)

Refs #8577
This commit is contained in:
Vladimír Gorej
2023-05-30 15:01:15 +02:00
parent ac3b69fb3c
commit 027f53c302
15 changed files with 360 additions and 160 deletions

View File

@@ -1,7 +1,7 @@
/**
* @prettier
*/
import { useFn } from "../hooks"
import { useFn } from "./hooks"
export const upperFirst = (value) => {
if (typeof value === "string") {

View File

@@ -56,7 +56,7 @@ import {
stringify,
stringifyConstraints,
getDependentRequired,
} from "./fn/index"
} from "./fn"
export const withJSONSchemaContext = (Component, overrides = {}) => {
const value = {

View File

@@ -43,15 +43,14 @@ import KeywordWriteOnly from "./components/keywords/WriteOnly"
import Accordion from "./components/Accordion/Accordion"
import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton"
import ChevronRightIcon from "./components/icons/ChevronRight"
import { upperFirst, hasKeyword, isExpandable } from "./fn/index"
import { upperFirst, hasKeyword, isExpandable } from "./fn"
import {
inferSchema,
sampleFromSchema,
sampleFromSchemaGeneric,
createXMLExample,
memoizedSampleFromSchema,
memoizedCreateXMLExample,
} from "./fn/samples"
} from "./samples-extensions/fn"
import { JSONSchemaDeepExpansionContext } from "./context"
import { useFn, useConfig, useComponent, useIsExpandedDeeply } from "./hooks"
import { withJSONSchemaContext } from "./hoc"
@@ -113,7 +112,6 @@ const JSONSchema202012Plugin = () => ({
useConfig,
useComponent,
useIsExpandedDeeply,
inferSchema,
sampleFromSchema,
sampleFromSchemaGeneric,
createXMLExample,

View File

@@ -709,16 +709,6 @@ export const sampleFromSchemaGeneric = (
return value
}
export const inferSchema = (thing) => {
if (thing.schema) thing = thing.schema
if (thing.properties) {
thing.type = "object"
}
return thing // Hopefully this will have something schema like in it... `type` for example
}
export const createXMLExample = (schema, config, o) => {
const json = sampleFromSchemaGeneric(schema, config, o, true)
if (!json) {

View File

@@ -98,7 +98,7 @@ export const createOnlyOAS31ComponentWrapper =
/* eslint-enable react/jsx-filename-extension */
/** Utilize JSON Schema 2020-12 samples **/
export const wrapSampleFn =
const wrapSampleFn =
(fnName) =>
(getSystem) =>
(...args) => {
@@ -111,7 +111,6 @@ export const wrapSampleFn =
return fn[fnName](...args)
}
export const wrapInferSchema = wrapSampleFn("inferSchema")
export const wrapSampleFromSchema = wrapSampleFn("sampleFromSchema")
export const wrapSampleFromSchemaGeneric = wrapSampleFn(
"sampleFromSchemaGeneric"

View File

@@ -20,7 +20,6 @@ import {
isOAS31 as isOAS31Fn,
createOnlyOAS31Selector as createOnlyOAS31SelectorFn,
createSystemSelector as createSystemSelectorFn,
wrapInferSchema,
wrapSampleFromSchema,
wrapSampleFromSchemaGeneric,
wrapCreateXMLExample,
@@ -90,7 +89,6 @@ const OAS31Plugin = ({ getSystem }) => {
}
// wraps schema generators and make them specific to OpenAPI version
if (typeof system.fn.inferSchema === "function") {
pluginFn.inferSchema = wrapInferSchema(getSystem)
pluginFn.sampleFromSchema = wrapSampleFromSchema(getSystem)
pluginFn.sampleFromSchemaGeneric = wrapSampleFromSchemaGeneric(getSystem)
pluginFn.createXMLExample = wrapCreateXMLExample(getSystem)

View File

@@ -0,0 +1,32 @@
/**
* @prettier
*/
import some from "lodash/some"
const shouldStringifyTypesConfig = [
{
when: /json/,
shouldStringifyTypes: ["string"],
},
]
const defaultStringifyTypes = ["object"]
const makeGetJsonSampleSchema =
(getSystem) => (schema, config, contentType, exampleOverride) => {
const { fn } = getSystem()
const res = fn.memoizedSampleFromSchema(schema, config, exampleOverride)
const resType = typeof res
const typesToStringify = shouldStringifyTypesConfig.reduce(
(types, nextConfig) =>
nextConfig.when.test(contentType)
? [...types, ...nextConfig.shouldStringifyTypes]
: types,
defaultStringifyTypes
)
return some(typesToStringify, (x) => x === resType)
? JSON.stringify(res, null, 2)
: res
}
export default makeGetJsonSampleSchema

View File

@@ -0,0 +1,30 @@
/**
* @prettier
*/
const makeGetSampleSchema =
(getSystem) =>
(schema, contentType = "", config = {}, exampleOverride = undefined) => {
const { fn } = getSystem()
if (typeof schema?.toJS === "function") {
schema = schema.toJS()
}
if (typeof exampleOverride?.toJS === "function") {
exampleOverride = exampleOverride.toJS()
}
if (/xml/.test(contentType)) {
return fn.getXmlSampleSchema(schema, config, exampleOverride)
}
if (/(yaml|yml)/.test(contentType)) {
return fn.getYamlSampleSchema(
schema,
config,
contentType,
exampleOverride
)
}
return fn.getJsonSampleSchema(schema, config, contentType, exampleOverride)
}
export default makeGetSampleSchema

View File

@@ -0,0 +1,31 @@
/**
* @prettier
*/
const makeGetXmlSampleSchema =
(getSystem) => (schema, config, exampleOverride) => {
const { fn } = getSystem()
if (schema && !schema.xml) {
schema.xml = {}
}
if (schema && !schema.xml.name) {
if (
!schema.$$ref &&
(schema.type ||
schema.items ||
schema.properties ||
schema.additionalProperties)
) {
// eslint-disable-next-line quotes
return '<?xml version="1.0" encoding="UTF-8"?>\n<!-- XML example cannot be generated; root element name is undefined -->'
}
if (schema.$$ref) {
let match = schema.$$ref.match(/\S*\/(\S+)$/)
schema.xml.name = match[1]
}
}
return fn.memoizedCreateXMLExample(schema, config, exampleOverride)
}
export default makeGetXmlSampleSchema

View File

@@ -0,0 +1,34 @@
/**
* @prettier
*/
import YAML, { JSON_SCHEMA } from "js-yaml"
const makeGetYamlSampleSchema =
(getSystem) => (schema, config, contentType, exampleOverride) => {
const { fn } = getSystem()
const jsonExample = fn.getJsonSampleSchema(
schema,
config,
contentType,
exampleOverride
)
let yamlString
try {
yamlString = YAML.dump(
YAML.load(jsonExample),
{
lineWidth: -1, // don't generate line folds
},
{ schema: JSON_SCHEMA }
)
if (yamlString[yamlString.length - 1] === "\n") {
yamlString = yamlString.slice(0, yamlString.length - 1)
}
} catch (e) {
console.error(e)
return "error: could not generate yaml example"
}
return yamlString.replace(/\t/g, " ")
}
export default makeGetYamlSampleSchema

View File

@@ -3,7 +3,7 @@ import RandExp from "randexp"
import isEmpty from "lodash/isEmpty"
import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils"
import memoizeN from "../../../helpers/memoizeN"
import memoizeN from "../../../../helpers/memoizeN"
const generateStringFromRegex = (pattern) => {
try {

View File

@@ -1,5 +1,32 @@
import * as fn from "./fn"
/**
* @prettier
*/
import {
sampleFromSchema,
inferSchema,
sampleFromSchemaGeneric,
createXMLExample,
memoizedCreateXMLExample,
memoizedSampleFromSchema,
} from "./fn/index"
import makeGetJsonSampleSchema from "./fn/get-json-sample-schema"
import makeGetYamlSampleSchema from "./fn/get-yaml-sample-schema"
import makeGetXmlSampleSchema from "./fn/get-xml-sample-schema"
import makeGetSampleSchema from "./fn/get-sample-schema"
export default function () {
return { fn }
}
const SamplesPlugin = ({ getSystem }) => ({
fn: {
inferSchema,
sampleFromSchema,
sampleFromSchemaGeneric,
createXMLExample,
memoizedSampleFromSchema,
memoizedCreateXMLExample,
getJsonSampleSchema: makeGetJsonSampleSchema(getSystem),
getYamlSampleSchema: makeGetYamlSampleSchema(getSystem),
getXmlSampleSchema: makeGetXmlSampleSchema(getSystem),
getSampleSchema: makeGetSampleSchema(getSystem),
},
})
export default SamplesPlugin

View File

@@ -0,0 +1,195 @@
/**
* @prettier
*/
import {
memoizedSampleFromSchema,
memoizedCreateXMLExample,
} from "core/plugins/samples/fn/index"
import makeGetSampleSchema from "core/plugins/samples/fn/get-sample-schema"
import makeGetJsonSampleSchema from "core/plugins/samples/fn/get-json-sample-schema"
import makeGetYamlSampleSchema from "core/plugins/samples/fn/get-yaml-sample-schema"
import makeGetXmlSampleSchema from "core/plugins/samples/fn/get-xml-sample-schema"
describe("getSampleSchema", () => {
const oriDate = Date
const getSystem = () => ({
fn: {
memoizedSampleFromSchema,
memoizedCreateXMLExample,
getJsonSampleSchema: makeGetJsonSampleSchema(getSystem),
getYamlSampleSchema: makeGetYamlSampleSchema(getSystem),
getXmlSampleSchema: makeGetXmlSampleSchema(getSystem),
},
})
const getSampleSchema = makeGetSampleSchema(getSystem)
beforeEach(() => {
Date = function () {
this.toISOString = function () {
return "2018-07-07T07:07:05.189Z"
}
}
})
afterEach(() => {
Date = oriDate
})
it("should stringify string values if json content-type", () => {
// Given
const res = getSampleSchema(
{
type: "string",
format: "date-time",
},
"text/json"
)
// Then
expect(res).toEqual(JSON.stringify(new Date().toISOString()))
})
it("should not unnecessarily stringify string values for other content-types", () => {
// Given
const res = getSampleSchema({
type: "string",
format: "date-time",
})
// Then
expect(res).toEqual(new Date().toISOString())
})
it("should not unnecessarily stringify non-object values", () => {
// Given
const res = getSampleSchema({
type: "number",
})
// Then
expect(res).toEqual(0)
})
it("should not unnecessarily stringify non-object values if content-type is json", () => {
// Given
const res = getSampleSchema(
{
type: "number",
},
"application/json"
)
// Then
expect(res).toEqual(0)
})
it("should stringify object when literal string example is provided if json content-type", () => {
// Given
const expected = "<MyObject></MyObject>"
const res = getSampleSchema(
{
type: "object",
},
"text/json",
{},
expected
)
// Then
expect(res).toEqual(JSON.stringify(expected))
})
it("should parse valid json literal example if json content-type", () => {
// Given
const expected = { test: 123 }
const res = getSampleSchema(
{
type: "object",
},
"text/json",
{},
JSON.stringify(expected)
)
// Then
const actual = JSON.parse(res)
expect(actual.test).toEqual(123)
})
it("should handle number example with string schema as string", () => {
// Given
const expected = 123
const res = getSampleSchema(
{
type: "string",
},
"text/json",
{},
expected
)
// Then
const actual = JSON.parse(res)
expect(actual).toEqual("123")
})
it("should handle number literal example with string schema as string", () => {
// Given
const expected = "123"
const res = getSampleSchema(
{
type: "string",
},
"text/json",
{},
expected
)
// Then
const actual = JSON.parse(res)
expect(actual).toEqual("123")
})
it("should handle number literal example with number schema as number", () => {
// Given
const expected = "123"
const res = getSampleSchema(
{
type: "number",
},
"text/json",
{},
expected
)
// Then
const actual = JSON.parse(res)
expect(actual).toEqual(123)
})
it("should return yaml example if yaml is contained in the content-type", () => {
const res = getSampleSchema(
{
type: "object",
},
"text/yaml",
{},
{ test: 123 }
)
expect(res).toEqual("test: 123")
})
it("should return yaml example if yml is contained in the content-type", () => {
const res = getSampleSchema(
{
type: "object",
},
"text/yml",
{},
{ test: 123 }
)
expect(res).toEqual("test: 123")
})
})

View File

@@ -1,5 +1,5 @@
import { fromJS } from "immutable"
import { createXMLExample, sampleFromSchema, memoizedCreateXMLExample, memoizedSampleFromSchema } from "corePlugins/samples/fn"
import { createXMLExample, sampleFromSchema, memoizedCreateXMLExample, memoizedSampleFromSchema } from "corePlugins/samples/fn/index"
describe("sampleFromSchema", () => {
it("handles Immutable.js objects for nested schemas", function () {

View File

@@ -24,7 +24,6 @@ import {
requiresValidationURL,
extractFileNameFromContentDispositionHeader,
deeplyStripKey,
getSampleSchema,
paramToIdentifier,
paramToValue,
generateCodeVerifier,
@@ -1615,139 +1614,6 @@ describe("utils", () => {
})
describe("getSampleSchema", () => {
const oriDate = Date
beforeEach(() => {
Date = function () {
this.toISOString = function () {
return "2018-07-07T07:07:05.189Z"
}
}
})
afterEach(() => {
Date = oriDate
})
it("should stringify string values if json content-type", () => {
// Given
const res = getSampleSchema({
type: "string",
format: "date-time"
}, "text/json")
// Then
expect(res).toEqual(JSON.stringify(new Date().toISOString()))
})
it("should not unnecessarily stringify string values for other content-types", () => {
// Given
const res = getSampleSchema({
type: "string",
format: "date-time"
})
// Then
expect(res).toEqual(new Date().toISOString())
})
it("should not unnecessarily stringify non-object values", () => {
// Given
const res = getSampleSchema({
type: "number"
})
// Then
expect(res).toEqual(0)
})
it("should not unnecessarily stringify non-object values if content-type is json", () => {
// Given
const res = getSampleSchema({
type: "number"
}, "application/json")
// Then
expect(res).toEqual(0)
})
it("should stringify object when literal string example is provided if json content-type", () => {
// Given
const expected = "<MyObject></MyObject>"
const res = getSampleSchema({
type: "object",
}, "text/json", {}, expected)
// Then
expect(res).toEqual(JSON.stringify(expected))
})
it("should parse valid json literal example if json content-type", () => {
// Given
const expected = {test: 123}
const res = getSampleSchema({
type: "object",
}, "text/json", {}, JSON.stringify(expected))
// Then
const actual = JSON.parse(res)
expect(actual.test).toEqual(123)
})
it("should handle number example with string schema as string", () => {
// Given
const expected = 123
const res = getSampleSchema({
type: "string",
}, "text/json", {}, expected)
// Then
const actual = JSON.parse(res)
expect(actual).toEqual("123")
})
it("should handle number literal example with string schema as string", () => {
// Given
const expected = "123"
const res = getSampleSchema({
type: "string",
}, "text/json", {}, expected)
// Then
const actual = JSON.parse(res)
expect(actual).toEqual("123")
})
it("should handle number literal example with number schema as number", () => {
// Given
const expected = "123"
const res = getSampleSchema({
type: "number",
}, "text/json", {}, expected)
// Then
const actual = JSON.parse(res)
expect(actual).toEqual(123)
})
it("should return yaml example if yaml is contained in the content-type", () => {
const res = getSampleSchema({
type: "object",
}, "text/yaml", {}, {test: 123})
expect(res).toEqual("test: 123")
})
it("should return yaml example if yml is contained in the content-type", () => {
const res = getSampleSchema({
type: "object",
}, "text/yml", {}, {test: 123})
expect(res).toEqual("test: 123")
})
})
describe("paramToIdentifier", () => {
it("should convert an Immutable parameter map to an identifier", () => {
const param = fromJS({