refactor(samples): move example generation code to samples plugin (#8727)
Refs #8577
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { useFn } from "../hooks"
|
||||
import { useFn } from "./hooks"
|
||||
|
||||
export const upperFirst = (value) => {
|
||||
if (typeof value === "string") {
|
||||
@@ -56,7 +56,7 @@ import {
|
||||
stringify,
|
||||
stringifyConstraints,
|
||||
getDependentRequired,
|
||||
} from "./fn/index"
|
||||
} from "./fn"
|
||||
|
||||
export const withJSONSchemaContext = (Component, overrides = {}) => {
|
||||
const value = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
32
src/core/plugins/samples/fn/get-json-sample-schema.js
Normal file
32
src/core/plugins/samples/fn/get-json-sample-schema.js
Normal 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
|
||||
30
src/core/plugins/samples/fn/get-sample-schema.js
Normal file
30
src/core/plugins/samples/fn/get-sample-schema.js
Normal 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
|
||||
31
src/core/plugins/samples/fn/get-xml-sample-schema.js
Normal file
31
src/core/plugins/samples/fn/get-xml-sample-schema.js
Normal 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
|
||||
34
src/core/plugins/samples/fn/get-yaml-sample-schema.js
Normal file
34
src/core/plugins/samples/fn/get-yaml-sample-schema.js
Normal 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
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
|
||||
195
test/unit/core/plugins/samples/fn/get-sample-schema.js
Normal file
195
test/unit/core/plugins/samples/fn/get-sample-schema.js
Normal 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")
|
||||
})
|
||||
})
|
||||
@@ -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 () {
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user