From b31de5ed150a70095ae7548556ed2140f22e2b2f Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Tue, 16 Apr 2024 14:10:50 +0200 Subject: [PATCH 1/8] fix(config): cast configuration values into proper types Refs #9808 --- src/core/components/live-response.jsx | 4 +- src/core/components/operation-tag.jsx | 4 +- src/core/config/merge.js | 3 +- src/core/config/type-cast/index.js | 22 ++++++ .../config/type-cast/type-casters/boolean.js | 11 +++ .../config/type-cast/type-casters/combined.js | 7 ++ .../config/type-cast/type-casters/false.js | 7 ++ .../config/type-cast/type-casters/index.js | 36 ++++++++++ .../config/type-cast/type-casters/null.js | 6 ++ .../config/type-cast/type-casters/number.js | 9 +++ .../type-cast/type-casters/undefined.js | 7 ++ src/core/containers/OperationContainer.jsx | 5 +- src/core/containers/filter.jsx | 4 +- .../layout/spec-extensions/wrap-selector.js | 2 +- .../swagger-client/configs-wrap-actions.js | 2 +- .../wrap-components/SyntaxHighlighter.jsx | 2 +- test/unit/core/config/type-cast/index.js | 72 +++++++++++++++++++ .../plugins/swagger-js/withCredentials.js | 38 ---------- 18 files changed, 189 insertions(+), 52 deletions(-) create mode 100644 src/core/config/type-cast/index.js create mode 100644 src/core/config/type-cast/type-casters/boolean.js create mode 100644 src/core/config/type-cast/type-casters/combined.js create mode 100644 src/core/config/type-cast/type-casters/false.js create mode 100644 src/core/config/type-cast/type-casters/index.js create mode 100644 src/core/config/type-cast/type-casters/null.js create mode 100644 src/core/config/type-cast/type-casters/number.js create mode 100644 src/core/config/type-cast/type-casters/undefined.js create mode 100644 test/unit/core/config/type-cast/index.js diff --git a/src/core/components/live-response.jsx b/src/core/components/live-response.jsx index eb7665a4ea2..38ddcafa6df 100644 --- a/src/core/components/live-response.jsx +++ b/src/core/components/live-response.jsx @@ -73,10 +73,10 @@ export default class LiveResponse extends React.Component { return (
- { curlRequest && (requestSnippetsEnabled === true || requestSnippetsEnabled === "true" + { curlRequest && requestSnippetsEnabled ? : - )} + } { url &&

Request URL

diff --git a/src/core/components/operation-tag.jsx b/src/core/components/operation-tag.jsx index 0880c079e82..9f8dcf46e99 100644 --- a/src/core/components/operation-tag.jsx +++ b/src/core/components/operation-tag.jsx @@ -47,8 +47,6 @@ export default class OperationTag extends React.Component { deepLinking, } = getConfigs() - const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" - const Collapse = getComponent("Collapse") const Markdown = getComponent("Markdown", true) const DeepLink = getComponent("DeepLink") @@ -80,7 +78,7 @@ export default class OperationTag extends React.Component { data-is-open={showTag} > diff --git a/src/core/config/merge.js b/src/core/config/merge.js index e56304fb885..29974331c34 100644 --- a/src/core/config/merge.js +++ b/src/core/config/merge.js @@ -13,6 +13,7 @@ * TODO(vladimir.gorej@gmail.com): remove deep-extend in favor of lodash.merge */ import deepExtend from "deep-extend" +import typeCast from "./type-cast" const merge = (target, ...sources) => { let domNode = Symbol.for("domNode") @@ -51,7 +52,7 @@ const merge = (target, ...sources) => { merged.urls.primaryName = primaryName } - return merged + return typeCast(merged) } export default merge diff --git a/src/core/config/type-cast/index.js b/src/core/config/type-cast/index.js new file mode 100644 index 00000000000..5cb02310e32 --- /dev/null +++ b/src/core/config/type-cast/index.js @@ -0,0 +1,22 @@ +/** + * @prettier + */ +import has from "lodash/has" +import get from "lodash/get" +import set from "lodash/set" + +import typeCasters from "./type-casters" + +const typeCast = (options) => { + return Object.entries(typeCasters).reduce( + (acc, [option, typeCaster]) => { + if (has(acc, option)) { + acc = set(acc, option, typeCaster(get(acc, option))) + } + return acc + }, + { ...options } + ) +} + +export default typeCast diff --git a/src/core/config/type-cast/type-casters/boolean.js b/src/core/config/type-cast/type-casters/boolean.js new file mode 100644 index 00000000000..32f8e758e4b --- /dev/null +++ b/src/core/config/type-cast/type-casters/boolean.js @@ -0,0 +1,11 @@ +/** + * @prettier + */ +const booleanTypeCaster = (value) => + value === "true" || value === "1" || value === 1 + ? true + : value === "false" || value === "0" || value === 0 + ? false + : value + +export default booleanTypeCaster diff --git a/src/core/config/type-cast/type-casters/combined.js b/src/core/config/type-cast/type-casters/combined.js new file mode 100644 index 00000000000..dd05b584cf1 --- /dev/null +++ b/src/core/config/type-cast/type-casters/combined.js @@ -0,0 +1,7 @@ +/** + * @prettier + */ +const combinedTypeCaster = (firstTypeCaster, secondTypeCaster) => (value) => + secondTypeCaster(firstTypeCaster(value)) + +export default combinedTypeCaster diff --git a/src/core/config/type-cast/type-casters/false.js b/src/core/config/type-cast/type-casters/false.js new file mode 100644 index 00000000000..7707d17f861 --- /dev/null +++ b/src/core/config/type-cast/type-casters/false.js @@ -0,0 +1,7 @@ +/** + * @prettier + */ +const falseTypeCaster = (value) => + value === "false" || value === "0" || value === 0 ? false : value + +export default falseTypeCaster diff --git a/src/core/config/type-cast/type-casters/index.js b/src/core/config/type-cast/type-casters/index.js new file mode 100644 index 00000000000..4917808bf97 --- /dev/null +++ b/src/core/config/type-cast/type-casters/index.js @@ -0,0 +1,36 @@ +/** + * @prettier + */ + +import booleanTypeCaster from "./boolean" +import combinedTypeCaster from "./combined" +import falseTypeCaster from "./false" +import nullTypeCaster from "./null" +import numberTypeCaster from "./number" +import undefinedTypeCaster from "./undefined" + +const typeCasters = { + deepLinking: booleanTypeCaster, + defaultModelExpandDepth: numberTypeCaster, + defaultModelsExpandDepth: numberTypeCaster, + displayOperationId: booleanTypeCaster, + displayRequestDuration: booleanTypeCaster, + dom_id: nullTypeCaster, + domNode: nullTypeCaster, + filter: combinedTypeCaster(nullTypeCaster, booleanTypeCaster), + maxDisplayedTags: combinedTypeCaster(nullTypeCaster, numberTypeCaster), + oauth2RedirectUrl: undefinedTypeCaster, + persistAuthorization: booleanTypeCaster, + requestSnippetsEnabled: booleanTypeCaster, + showCommonExtensions: booleanTypeCaster, + showExtensions: booleanTypeCaster, + showMutatedRequest: booleanTypeCaster, + syntaxHighlight: falseTypeCaster, + "syntaxHighlight.activated": booleanTypeCaster, + tryItOutEnabled: booleanTypeCaster, + urls: nullTypeCaster, + validatorUrl: nullTypeCaster, + withCredentials: combinedTypeCaster(undefinedTypeCaster, booleanTypeCaster), +} + +export default typeCasters diff --git a/src/core/config/type-cast/type-casters/null.js b/src/core/config/type-cast/type-casters/null.js new file mode 100644 index 00000000000..5bc73256002 --- /dev/null +++ b/src/core/config/type-cast/type-casters/null.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const nullTypeCaster = (value) => (value === "null" ? null : value) + +export default nullTypeCaster diff --git a/src/core/config/type-cast/type-casters/number.js b/src/core/config/type-cast/type-casters/number.js new file mode 100644 index 00000000000..98cabebb621 --- /dev/null +++ b/src/core/config/type-cast/type-casters/number.js @@ -0,0 +1,9 @@ +/** + * @prettier + */ +const numberTypeCaster = (value) => { + const parsedValue = parseInt(value) + return isNaN(parsedValue) ? value : parsedValue +} + +export default numberTypeCaster diff --git a/src/core/config/type-cast/type-casters/undefined.js b/src/core/config/type-cast/type-casters/undefined.js new file mode 100644 index 00000000000..36485aca55a --- /dev/null +++ b/src/core/config/type-cast/type-casters/undefined.js @@ -0,0 +1,7 @@ +/** + * @prettier + */ +const undefinedTypeCaster = (value) => + value === "undefined" ? undefined : value + +export default undefinedTypeCaster diff --git a/src/core/containers/OperationContainer.jsx b/src/core/containers/OperationContainer.jsx index 89ce7f51ae1..6efd7dfc2fb 100644 --- a/src/core/containers/OperationContainer.jsx +++ b/src/core/containers/OperationContainer.jsx @@ -11,7 +11,7 @@ export default class OperationContainer extends PureComponent { const { tryItOutEnabled } = props.getConfigs() this.state = { - tryItOutEnabled: tryItOutEnabled === true || tryItOutEnabled === "true", + tryItOutEnabled, executeInProgress: false } } @@ -61,14 +61,13 @@ export default class OperationContainer extends PureComponent { const showSummary = layoutSelectors.showSummary() const operationId = op.getIn(["operation", "__originalOperationId"]) || op.getIn(["operation", "operationId"]) || opId(op.get("operation"), props.path, props.method) || op.get("id") const isShownKey = ["operations", props.tag, operationId] - const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" const allowTryItOut = supportedSubmitMethods.indexOf(props.method) >= 0 && (typeof props.allowTryItOut === "undefined" ? props.specSelectors.allowTryItOutFor(props.path, props.method) : props.allowTryItOut) const security = op.getIn(["operation", "security"]) || props.specSelectors.security() return { operationId, - isDeepLinkingEnabled, + isDeepLinkingEnabled: deepLinking, showSummary, displayOperationId, displayRequestDuration, diff --git a/src/core/containers/filter.jsx b/src/core/containers/filter.jsx index 872297f055e..68580cfcecc 100644 --- a/src/core/containers/filter.jsx +++ b/src/core/containers/filter.jsx @@ -29,11 +29,11 @@ export default class FilterContainer extends React.Component { return (
- {filter === null || filter === false || filter === "false" ? null : + {!filter ? null :
diff --git a/src/core/plugins/layout/spec-extensions/wrap-selector.js b/src/core/plugins/layout/spec-extensions/wrap-selector.js index 712980179e0..c5aa5eb12e7 100644 --- a/src/core/plugins/layout/spec-extensions/wrap-selector.js +++ b/src/core/plugins/layout/spec-extensions/wrap-selector.js @@ -9,7 +9,7 @@ export const taggedOperations = (oriSelector, system) => (state, ...args) => { // Filter, if requested let filter = layoutSelectors.currentFilter() if (filter) { - if (filter !== true && filter !== "true" && filter !== "false") { + if (filter !== true) { taggedOps = fn.opsFilter(taggedOps, filter) } } diff --git a/src/core/plugins/swagger-client/configs-wrap-actions.js b/src/core/plugins/swagger-client/configs-wrap-actions.js index 34d0e158af0..8a3df485dbd 100644 --- a/src/core/plugins/swagger-client/configs-wrap-actions.js +++ b/src/core/plugins/swagger-client/configs-wrap-actions.js @@ -3,6 +3,6 @@ export const loaded = (ori, system) => (...args) => { const value = system.getConfigs().withCredentials if(value !== undefined) { - system.fn.fetch.withCredentials = typeof value === "string" ? (value === "true") : !!value + system.fn.fetch.withCredentials = value } } diff --git a/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx b/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx index a9e557209f0..8f6e13f6364 100644 --- a/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx +++ b/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx @@ -8,7 +8,7 @@ import get from "lodash/get" const SyntaxHighlighterWrapper = (Original, system) => { const SyntaxHighlighter = ({ renderPlainText, children, ...rest }) => { const configs = system.getConfigs() - const canSyntaxHighlight = !!get(configs, "syntaxHighlight.activated") + const canSyntaxHighlight = get(configs, "syntaxHighlight.activated") const PlainTextViewer = system.getComponent("PlainTextViewer") if (!canSyntaxHighlight && typeof renderPlainText === "function") { diff --git a/test/unit/core/config/type-cast/index.js b/test/unit/core/config/type-cast/index.js new file mode 100644 index 00000000000..b5d3b628106 --- /dev/null +++ b/test/unit/core/config/type-cast/index.js @@ -0,0 +1,72 @@ +/** + * @prettier + */ +import typeCast from "core/config/type-cast" + +describe("typeCast", () => { + it("should convert stringified `true` and `false` values to `boolean`", () => { + const config = { + deepLinking: "true", + tryItOutEnabled: "false", + withCredentials: "true", + filter: "false", + } + + const expectedConfig = { + deepLinking: true, + tryItOutEnabled: false, + withCredentials: true, + filter: false, + } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) + + it("should convert stringified `number` values to `number`", () => { + const config = { + defaultModelExpandDepth: "5", + defaultModelsExpandDepth: "-1", + maxDisplayedTags: "1", + } + + const expectedConfig = { + defaultModelExpandDepth: 5, + defaultModelsExpandDepth: -1, + maxDisplayedTags: 1, + } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) + + it("should convert stringified `null` values to `null`", () => { + const config = { + validatorUrl: "null", + maxDisplayedTags: "null", + filter: "null", + } + + const expectedConfig = { + validatorUrl: null, + maxDisplayedTags: null, + filter: null, + } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) + + it("should convert stringified `undefined` values to `undefined`", () => { + const config = { withCredentials: "undefined" } + + const expectedConfig = { withCredentials: undefined } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) + + it("should not convert `string` values", () => { + const config = { defaultModelRendering: "model", filter: "pet" } + + const expectedConfig = { defaultModelRendering: "model", filter: "pet" } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) +}) diff --git a/test/unit/core/plugins/swagger-js/withCredentials.js b/test/unit/core/plugins/swagger-js/withCredentials.js index 9153d548f80..ff2487abe6b 100644 --- a/test/unit/core/plugins/swagger-js/withCredentials.js +++ b/test/unit/core/plugins/swagger-js/withCredentials.js @@ -49,44 +49,6 @@ describe("swagger-client plugin - withCredentials", () => { const loadedFn = loaded(oriExecute, system) loadedFn() - expect(oriExecute.mock.calls.length).toBe(1) - expect(system.fn.fetch.withCredentials).toBe(false) - }) - - it("should allow setting flag to true via config as string", () => { - // for query string config - const system = { - fn: { - fetch: jest.fn().mockImplementation(() => Promise.resolve()) - }, - getConfigs: () => ({ - withCredentials: "true" - }) - } - const oriExecute = jest.fn() - - const loadedFn = loaded(oriExecute, system) - loadedFn() - - expect(oriExecute.mock.calls.length).toBe(1) - expect(system.fn.fetch.withCredentials).toBe(true) - }) - - it("should allow setting flag to false via config as string", () => { - // for query string config - const system = { - fn: { - fetch: jest.fn().mockImplementation(() => Promise.resolve()) - }, - getConfigs: () => ({ - withCredentials: "false" - }) - } - const oriExecute = jest.fn() - - const loadedFn = loaded(oriExecute, system) - loadedFn() - expect(oriExecute.mock.calls.length).toBe(1) expect(system.fn.fetch.withCredentials).toBe(false) }) From 5062e480c260232b4ac0f2754fea69f7fb5003d2 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Tue, 16 Apr 2024 14:28:59 +0200 Subject: [PATCH 2/8] fix: fix empty string filter not rendering field --- src/core/containers/filter.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/containers/filter.jsx b/src/core/containers/filter.jsx index 68580cfcecc..ad60016e2b3 100644 --- a/src/core/containers/filter.jsx +++ b/src/core/containers/filter.jsx @@ -29,7 +29,7 @@ export default class FilterContainer extends React.Component { return (
- {!filter ? null : + {filter === null || filter === false ? null :
Date: Tue, 16 Apr 2024 16:25:41 +0200 Subject: [PATCH 3/8] change to check type of maxDisplayedTags --- src/core/plugins/layout/spec-extensions/wrap-selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/plugins/layout/spec-extensions/wrap-selector.js b/src/core/plugins/layout/spec-extensions/wrap-selector.js index c5aa5eb12e7..300de31e48c 100644 --- a/src/core/plugins/layout/spec-extensions/wrap-selector.js +++ b/src/core/plugins/layout/spec-extensions/wrap-selector.js @@ -14,7 +14,7 @@ export const taggedOperations = (oriSelector, system) => (state, ...args) => { } } // Limit to [max] items, if specified - if (maxDisplayedTags && !isNaN(maxDisplayedTags) && maxDisplayedTags >= 0) { + if (typeof maxDisplayedTags === "number" && maxDisplayedTags >= 0) { taggedOps = taggedOps.slice(0, maxDisplayedTags) } From d7175442c0a796586eb32445aeff587ac88f7f53 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 17 Apr 2024 13:24:03 +0200 Subject: [PATCH 4/8] apply requested changes --- src/core/config/type-cast/index.js | 12 +-- src/core/config/type-cast/mappings.js | 53 +++++++++++++ .../config/type-cast/type-casters/array.js | 10 +++ .../config/type-cast/type-casters/boolean.js | 11 ++- .../config/type-cast/type-casters/combined.js | 7 -- .../config/type-cast/type-casters/dom-node.js | 7 ++ .../config/type-cast/type-casters/false.js | 7 -- .../config/type-cast/type-casters/filter.js | 13 ++++ .../config/type-cast/type-casters/index.js | 36 --------- .../config/type-cast/type-casters/null.js | 6 -- .../type-cast/type-casters/nullable-array.js | 6 ++ .../type-cast/type-casters/nullable-string.js | 7 ++ .../config/type-cast/type-casters/number.js | 4 +- .../config/type-cast/type-casters/object.js | 11 +++ .../config/type-cast/type-casters/string.js | 6 ++ .../type-casters/syntax-highlight.js | 15 ++++ .../type-casters/undefined-boolean.js | 11 +++ .../type-casters/undefined-string.js | 7 ++ .../type-cast/type-casters/undefined.js | 7 -- .../layout/spec-extensions/wrap-selector.js | 2 +- .../components/SyntaxHighlighter.jsx | 4 +- .../wrap-components/SyntaxHighlighter.jsx | 4 +- test/unit/core/config/type-cast/index.js | 77 +++++++++++++++++-- 23 files changed, 235 insertions(+), 88 deletions(-) create mode 100644 src/core/config/type-cast/mappings.js create mode 100644 src/core/config/type-cast/type-casters/array.js delete mode 100644 src/core/config/type-cast/type-casters/combined.js create mode 100644 src/core/config/type-cast/type-casters/dom-node.js delete mode 100644 src/core/config/type-cast/type-casters/false.js create mode 100644 src/core/config/type-cast/type-casters/filter.js delete mode 100644 src/core/config/type-cast/type-casters/index.js delete mode 100644 src/core/config/type-cast/type-casters/null.js create mode 100644 src/core/config/type-cast/type-casters/nullable-array.js create mode 100644 src/core/config/type-cast/type-casters/nullable-string.js create mode 100644 src/core/config/type-cast/type-casters/object.js create mode 100644 src/core/config/type-cast/type-casters/string.js create mode 100644 src/core/config/type-cast/type-casters/syntax-highlight.js create mode 100644 src/core/config/type-cast/type-casters/undefined-boolean.js create mode 100644 src/core/config/type-cast/type-casters/undefined-string.js delete mode 100644 src/core/config/type-cast/type-casters/undefined.js diff --git a/src/core/config/type-cast/index.js b/src/core/config/type-cast/index.js index 5cb02310e32..d3720df86a4 100644 --- a/src/core/config/type-cast/index.js +++ b/src/core/config/type-cast/index.js @@ -3,15 +3,17 @@ */ import has from "lodash/has" import get from "lodash/get" -import set from "lodash/set" +import set from "lodash/fp/set" -import typeCasters from "./type-casters" +import typeCasters from "./mappings" const typeCast = (options) => { return Object.entries(typeCasters).reduce( - (acc, [option, typeCaster]) => { - if (has(acc, option)) { - acc = set(acc, option, typeCaster(get(acc, option))) + (acc, [optionPath, typeCaster]) => { + if (has(acc, optionPath)) { + const uncasted = get(acc, optionPath) + const casted = typeCaster(uncasted) + acc = set(optionPath, casted, acc) } return acc }, diff --git a/src/core/config/type-cast/mappings.js b/src/core/config/type-cast/mappings.js new file mode 100644 index 00000000000..c885eb2a15f --- /dev/null +++ b/src/core/config/type-cast/mappings.js @@ -0,0 +1,53 @@ +/** + * @prettier + */ +import arrayTypeCaster from "./type-casters/array" +import booleanTypeCaster from "./type-casters/boolean" +import domNodeTypeCaster from "./type-casters/dom-node" +import filterTypeCaster from "./type-casters/filter" +import nullableArrayTypeCaster from "./type-casters/nullable-array" +import nullableStringTypeCaster from "./type-casters/nullable-string" +import numberTypeCaster from "./type-casters/number" +import objectTypeCaster from "./type-casters/object" +import stringTypeCaster from "./type-casters/string" +import syntaxHighlightTypeCaster from "./type-casters/syntax-highlight" +import undefinedBooleanTypeCaster from "./type-casters/undefined-boolean" +import undefinedStringTypeCaster from "./type-casters/undefined-string" + +const typeCasters = { + configUrl: stringTypeCaster, + deepLinking: booleanTypeCaster("deepLinking"), + defaultModelExpandDepth: numberTypeCaster, + defaultModelRendering: stringTypeCaster, + defaultModelsExpandDepth: numberTypeCaster, + displayOperationId: booleanTypeCaster("displayOperationId"), + displayRequestDuration: booleanTypeCaster("displayRequestDuration"), + docExpansion: stringTypeCaster, + dom_id: nullableStringTypeCaster, + domNode: domNodeTypeCaster, + filter: filterTypeCaster, + maxDisplayedTags: numberTypeCaster, + oauth2RedirectUrl: undefinedStringTypeCaster, + persistAuthorization: booleanTypeCaster("persistAuthorization"), + plugins: arrayTypeCaster("plugins"), + pluginsOptions: objectTypeCaster("pluginsOptions"), + presets: arrayTypeCaster("presets"), + requestSnippets: objectTypeCaster("requestSnippets"), + requestSnippetsEnabled: booleanTypeCaster("requestSnippetsEnabled"), + showCommonExtensions: booleanTypeCaster("showCommonExtensions"), + showExtensions: booleanTypeCaster("showExtensions"), + showMutatedRequest: booleanTypeCaster("showMutatedRequest"), + spec: objectTypeCaster("spec"), + supportedSubmitMethods: arrayTypeCaster("supportedSubmitMethods"), + syntaxHighlight: syntaxHighlightTypeCaster, + "syntaxHighlight.activated": booleanTypeCaster("syntaxHighlight.activated"), + "syntaxHighlight.theme": stringTypeCaster, + tryItOutEnabled: booleanTypeCaster("tryItOutEnabled"), + url: stringTypeCaster, + urls: nullableArrayTypeCaster, + "urls.primaryName": stringTypeCaster, + validatorUrl: nullableStringTypeCaster, + withCredentials: undefinedBooleanTypeCaster, +} + +export default typeCasters diff --git a/src/core/config/type-cast/type-casters/array.js b/src/core/config/type-cast/type-casters/array.js new file mode 100644 index 00000000000..75eb1b4eeaf --- /dev/null +++ b/src/core/config/type-cast/type-casters/array.js @@ -0,0 +1,10 @@ +/** + * @prettier + */ +import get from "lodash/get" +import defaultOptions from "../../defaults" + +const arrayTypeCaster = (optionPath) => (value) => + Array.isArray(value) ? value : get(defaultOptions, optionPath) + +export default arrayTypeCaster diff --git a/src/core/config/type-cast/type-casters/boolean.js b/src/core/config/type-cast/type-casters/boolean.js index 32f8e758e4b..c1e3a09b61c 100644 --- a/src/core/config/type-cast/type-casters/boolean.js +++ b/src/core/config/type-cast/type-casters/boolean.js @@ -1,11 +1,14 @@ /** * @prettier */ -const booleanTypeCaster = (value) => - value === "true" || value === "1" || value === 1 +import get from "lodash/get" +import defaultOptions from "../../defaults" + +const booleanTypeCaster = (optionPath) => (value) => + value === true || value === "true" || value === 1 || value === "1" ? true - : value === "false" || value === "0" || value === 0 + : value === false || value === "false" || value === 0 || value === "0" ? false - : value + : get(defaultOptions, optionPath) export default booleanTypeCaster diff --git a/src/core/config/type-cast/type-casters/combined.js b/src/core/config/type-cast/type-casters/combined.js deleted file mode 100644 index dd05b584cf1..00000000000 --- a/src/core/config/type-cast/type-casters/combined.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @prettier - */ -const combinedTypeCaster = (firstTypeCaster, secondTypeCaster) => (value) => - secondTypeCaster(firstTypeCaster(value)) - -export default combinedTypeCaster diff --git a/src/core/config/type-cast/type-casters/dom-node.js b/src/core/config/type-cast/type-casters/dom-node.js new file mode 100644 index 00000000000..4186981bcf5 --- /dev/null +++ b/src/core/config/type-cast/type-casters/dom-node.js @@ -0,0 +1,7 @@ +/** + * @prettier + */ +const domNodeTypeCaster = (value) => + value === null || value === "null" ? null : value + +export default domNodeTypeCaster diff --git a/src/core/config/type-cast/type-casters/false.js b/src/core/config/type-cast/type-casters/false.js deleted file mode 100644 index 7707d17f861..00000000000 --- a/src/core/config/type-cast/type-casters/false.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @prettier - */ -const falseTypeCaster = (value) => - value === "false" || value === "0" || value === 0 ? false : value - -export default falseTypeCaster diff --git a/src/core/config/type-cast/type-casters/filter.js b/src/core/config/type-cast/type-casters/filter.js new file mode 100644 index 00000000000..2134f38f6d0 --- /dev/null +++ b/src/core/config/type-cast/type-casters/filter.js @@ -0,0 +1,13 @@ +/** + * @prettier + */ +const filterTypeCaster = (value) => + value === null || value === "null" + ? null + : value === true || value === "true" || value === 1 || value === "1" + ? true + : value === false || value === "false" || value === 0 || value === "0" + ? false + : String(value) + +export default filterTypeCaster diff --git a/src/core/config/type-cast/type-casters/index.js b/src/core/config/type-cast/type-casters/index.js deleted file mode 100644 index 4917808bf97..00000000000 --- a/src/core/config/type-cast/type-casters/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @prettier - */ - -import booleanTypeCaster from "./boolean" -import combinedTypeCaster from "./combined" -import falseTypeCaster from "./false" -import nullTypeCaster from "./null" -import numberTypeCaster from "./number" -import undefinedTypeCaster from "./undefined" - -const typeCasters = { - deepLinking: booleanTypeCaster, - defaultModelExpandDepth: numberTypeCaster, - defaultModelsExpandDepth: numberTypeCaster, - displayOperationId: booleanTypeCaster, - displayRequestDuration: booleanTypeCaster, - dom_id: nullTypeCaster, - domNode: nullTypeCaster, - filter: combinedTypeCaster(nullTypeCaster, booleanTypeCaster), - maxDisplayedTags: combinedTypeCaster(nullTypeCaster, numberTypeCaster), - oauth2RedirectUrl: undefinedTypeCaster, - persistAuthorization: booleanTypeCaster, - requestSnippetsEnabled: booleanTypeCaster, - showCommonExtensions: booleanTypeCaster, - showExtensions: booleanTypeCaster, - showMutatedRequest: booleanTypeCaster, - syntaxHighlight: falseTypeCaster, - "syntaxHighlight.activated": booleanTypeCaster, - tryItOutEnabled: booleanTypeCaster, - urls: nullTypeCaster, - validatorUrl: nullTypeCaster, - withCredentials: combinedTypeCaster(undefinedTypeCaster, booleanTypeCaster), -} - -export default typeCasters diff --git a/src/core/config/type-cast/type-casters/null.js b/src/core/config/type-cast/type-casters/null.js deleted file mode 100644 index 5bc73256002..00000000000 --- a/src/core/config/type-cast/type-casters/null.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @prettier - */ -const nullTypeCaster = (value) => (value === "null" ? null : value) - -export default nullTypeCaster diff --git a/src/core/config/type-cast/type-casters/nullable-array.js b/src/core/config/type-cast/type-casters/nullable-array.js new file mode 100644 index 00000000000..7606fe4c4a6 --- /dev/null +++ b/src/core/config/type-cast/type-casters/nullable-array.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const nullableArrayTypeCaster = (value) => (Array.isArray(value) ? value : null) + +export default nullableArrayTypeCaster diff --git a/src/core/config/type-cast/type-casters/nullable-string.js b/src/core/config/type-cast/type-casters/nullable-string.js new file mode 100644 index 00000000000..f4f0b23f95e --- /dev/null +++ b/src/core/config/type-cast/type-casters/nullable-string.js @@ -0,0 +1,7 @@ +/** + * @prettier + */ +const nullableStringTypeCaster = (value) => + value === null || value === "null" ? null : String(value) + +export default nullableStringTypeCaster diff --git a/src/core/config/type-cast/type-casters/number.js b/src/core/config/type-cast/type-casters/number.js index 98cabebb621..3aa87364769 100644 --- a/src/core/config/type-cast/type-casters/number.js +++ b/src/core/config/type-cast/type-casters/number.js @@ -2,8 +2,8 @@ * @prettier */ const numberTypeCaster = (value) => { - const parsedValue = parseInt(value) - return isNaN(parsedValue) ? value : parsedValue + const parsedValue = parseInt(value, 10) + return Number.isNaN(parsedValue) ? NaN : parsedValue } export default numberTypeCaster diff --git a/src/core/config/type-cast/type-casters/object.js b/src/core/config/type-cast/type-casters/object.js new file mode 100644 index 00000000000..1fc0b8eb5b5 --- /dev/null +++ b/src/core/config/type-cast/type-casters/object.js @@ -0,0 +1,11 @@ +/** + * @prettier + */ +import isPlainObject from "lodash/isPlainObject" +import get from "lodash/get" +import defaultOptions from "../../defaults" + +const objectTypeCaster = (optionPath) => (value) => + isPlainObject(value) ? value : get(defaultOptions, optionPath) + +export default objectTypeCaster diff --git a/src/core/config/type-cast/type-casters/string.js b/src/core/config/type-cast/type-casters/string.js new file mode 100644 index 00000000000..ed562fd2ea4 --- /dev/null +++ b/src/core/config/type-cast/type-casters/string.js @@ -0,0 +1,6 @@ +/** + * @prettier + */ +const stringTypeCaster = (value) => String(value) + +export default stringTypeCaster diff --git a/src/core/config/type-cast/type-casters/syntax-highlight.js b/src/core/config/type-cast/type-casters/syntax-highlight.js new file mode 100644 index 00000000000..b2d28a3b391 --- /dev/null +++ b/src/core/config/type-cast/type-casters/syntax-highlight.js @@ -0,0 +1,15 @@ +/** + * @prettier + */ +import isPlainObject from "lodash/isPlainObject" +import defaultOptions from "../../defaults" + +const syntaxHighlightTypeCaster = (value) => { + return isPlainObject(value) + ? value + : value === false || value === "false" || value === 0 || value === "0" + ? { activated: false } + : defaultOptions.syntaxHighlight +} + +export default syntaxHighlightTypeCaster diff --git a/src/core/config/type-cast/type-casters/undefined-boolean.js b/src/core/config/type-cast/type-casters/undefined-boolean.js new file mode 100644 index 00000000000..b84ecb276b5 --- /dev/null +++ b/src/core/config/type-cast/type-casters/undefined-boolean.js @@ -0,0 +1,11 @@ +/** + * @prettier + */ +const undefinedBooleanTypeCaster = (value) => + value === true || value === "true" || value === 1 || value === "1" + ? true + : value === true || value === "false" || value === 0 || value === "0" + ? false + : undefined + +export default undefinedBooleanTypeCaster diff --git a/src/core/config/type-cast/type-casters/undefined-string.js b/src/core/config/type-cast/type-casters/undefined-string.js new file mode 100644 index 00000000000..21ef142d1a7 --- /dev/null +++ b/src/core/config/type-cast/type-casters/undefined-string.js @@ -0,0 +1,7 @@ +/** + * @prettier + */ +const undefinedStringTypeCaster = (value) => + value === undefined || value === "undefined" ? undefined : String(value) + +export default undefinedStringTypeCaster diff --git a/src/core/config/type-cast/type-casters/undefined.js b/src/core/config/type-cast/type-casters/undefined.js deleted file mode 100644 index 36485aca55a..00000000000 --- a/src/core/config/type-cast/type-casters/undefined.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @prettier - */ -const undefinedTypeCaster = (value) => - value === "undefined" ? undefined : value - -export default undefinedTypeCaster diff --git a/src/core/plugins/layout/spec-extensions/wrap-selector.js b/src/core/plugins/layout/spec-extensions/wrap-selector.js index 300de31e48c..4aed3aa0b3c 100644 --- a/src/core/plugins/layout/spec-extensions/wrap-selector.js +++ b/src/core/plugins/layout/spec-extensions/wrap-selector.js @@ -14,7 +14,7 @@ export const taggedOperations = (oriSelector, system) => (state, ...args) => { } } // Limit to [max] items, if specified - if (typeof maxDisplayedTags === "number" && maxDisplayedTags >= 0) { + if (maxDisplayedTags >= 0) { taggedOps = taggedOps.slice(0, maxDisplayedTags) } diff --git a/src/core/plugins/syntax-highlighting/components/SyntaxHighlighter.jsx b/src/core/plugins/syntax-highlighting/components/SyntaxHighlighter.jsx index 43db2e2a70c..0ca96190937 100644 --- a/src/core/plugins/syntax-highlighting/components/SyntaxHighlighter.jsx +++ b/src/core/plugins/syntax-highlighting/components/SyntaxHighlighter.jsx @@ -4,7 +4,6 @@ import React from "react" import PropTypes from "prop-types" import ReactSyntaxHighlighter from "react-syntax-highlighter/dist/esm/light" -import get from "lodash/get" const SyntaxHighlighter = ({ language, @@ -13,8 +12,7 @@ const SyntaxHighlighter = ({ syntaxHighlighting = {}, children = "", }) => { - const configs = getConfigs() - const theme = get(configs, "syntaxHighlight.theme") + const theme = getConfigs().syntaxHighlight.theme const { styles, defaultStyle } = syntaxHighlighting const style = styles?.[theme] ?? defaultStyle diff --git a/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx b/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx index 8f6e13f6364..a505d1b8039 100644 --- a/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx +++ b/src/core/plugins/syntax-highlighting/wrap-components/SyntaxHighlighter.jsx @@ -3,12 +3,10 @@ */ import React from "react" import PropTypes from "prop-types" -import get from "lodash/get" const SyntaxHighlighterWrapper = (Original, system) => { const SyntaxHighlighter = ({ renderPlainText, children, ...rest }) => { - const configs = system.getConfigs() - const canSyntaxHighlight = get(configs, "syntaxHighlight.activated") + const canSyntaxHighlight = system.getConfigs().syntaxHighlight.activated const PlainTextViewer = system.getComponent("PlainTextViewer") if (!canSyntaxHighlight && typeof renderPlainText === "function") { diff --git a/test/unit/core/config/type-cast/index.js b/test/unit/core/config/type-cast/index.js index b5d3b628106..af9e6976f1e 100644 --- a/test/unit/core/config/type-cast/index.js +++ b/test/unit/core/config/type-cast/index.js @@ -3,8 +3,19 @@ */ import typeCast from "core/config/type-cast" +jest.mock("root/swagger-config.yaml", () => {}) +jest.mock("swagger-client/es/resolver/strategies/generic", () => {}) +jest.mock("swagger-client/es/resolver/strategies/openapi-2", () => {}) +jest.mock("swagger-client/es/resolver/strategies/openapi-3-0", () => {}) +jest.mock("swagger-client/es/resolver/strategies/openapi-3-1-apidom", () => {}) +jest.mock("swagger-client/es/resolver", () => {}) +jest.mock("swagger-client/es/execute", () => {}) +jest.mock("swagger-client/es/http", () => {}) +jest.mock("swagger-client/es/subtree-resolver", () => {}) +jest.mock("swagger-client/es/helpers", () => {}) + describe("typeCast", () => { - it("should convert stringified `true` and `false` values to `boolean`", () => { + it("should cast stringified `true` and `false` values to `boolean`", () => { const config = { deepLinking: "true", tryItOutEnabled: "false", @@ -22,7 +33,7 @@ describe("typeCast", () => { expect(typeCast(config)).toStrictEqual(expectedConfig) }) - it("should convert stringified `number` values to `number`", () => { + it("should cast stringified `number` values to `number`", () => { const config = { defaultModelExpandDepth: "5", defaultModelsExpandDepth: "-1", @@ -38,23 +49,37 @@ describe("typeCast", () => { expect(typeCast(config)).toStrictEqual(expectedConfig) }) - it("should convert stringified `null` values to `null`", () => { + it("should cast stringified `null` values to `null`", () => { const config = { validatorUrl: "null", - maxDisplayedTags: "null", filter: "null", } const expectedConfig = { validatorUrl: null, - maxDisplayedTags: null, filter: null, } expect(typeCast(config)).toStrictEqual(expectedConfig) }) - it("should convert stringified `undefined` values to `undefined`", () => { + it("should cast non-number values to `NaN`", () => { + const config = { + maxDisplayedTags: "null", + defaultModelExpandDepth: {}, + defaultModelsExpandDepth: false, + } + + const expectedConfig = { + maxDisplayedTags: NaN, + defaultModelExpandDepth: NaN, + defaultModelsExpandDepth: NaN, + } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) + + it("should cast stringified `undefined` values to `undefined`", () => { const config = { withCredentials: "undefined" } const expectedConfig = { withCredentials: undefined } @@ -62,11 +87,49 @@ describe("typeCast", () => { expect(typeCast(config)).toStrictEqual(expectedConfig) }) - it("should not convert `string` values", () => { + it("should cast `string` values to `string`", () => { const config = { defaultModelRendering: "model", filter: "pet" } const expectedConfig = { defaultModelRendering: "model", filter: "pet" } expect(typeCast(config)).toStrictEqual(expectedConfig) }) + + it("should cast stringified values to correct type", () => { + const config = { + dom_id: "null", + oauth2RedirectUrl: "undefined", + syntaxHighlight: "false", + urls: "null", + withCredentials: "false", + } + + const expectedConfig = { + dom_id: null, + oauth2RedirectUrl: undefined, + syntaxHighlight: { activated: false }, + urls: null, + withCredentials: false, + } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) + + it("should cast incorrect value types to default value", () => { + const config = { + deepLinking: "deepLinking", + urls: "urls", + syntaxHighlight: "syntaxHighlight", + spec: "spec", + } + + const expectedConfig = { + deepLinking: false, + urls: null, + syntaxHighlight: { activated: true, theme: "agate" }, + spec: {}, + } + + expect(typeCast(config)).toStrictEqual(expectedConfig) + }) }) From cc23a87500630cb4481ccc3014a4bf1ca0cacf35 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 17 Apr 2024 13:29:11 +0200 Subject: [PATCH 5/8] update test mocks --- test/unit/core/config/type-cast/index.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/test/unit/core/config/type-cast/index.js b/test/unit/core/config/type-cast/index.js index af9e6976f1e..762ffa99487 100644 --- a/test/unit/core/config/type-cast/index.js +++ b/test/unit/core/config/type-cast/index.js @@ -3,16 +3,7 @@ */ import typeCast from "core/config/type-cast" -jest.mock("root/swagger-config.yaml", () => {}) -jest.mock("swagger-client/es/resolver/strategies/generic", () => {}) -jest.mock("swagger-client/es/resolver/strategies/openapi-2", () => {}) -jest.mock("swagger-client/es/resolver/strategies/openapi-3-0", () => {}) -jest.mock("swagger-client/es/resolver/strategies/openapi-3-1-apidom", () => {}) -jest.mock("swagger-client/es/resolver", () => {}) -jest.mock("swagger-client/es/execute", () => {}) -jest.mock("swagger-client/es/http", () => {}) -jest.mock("swagger-client/es/subtree-resolver", () => {}) -jest.mock("swagger-client/es/helpers", () => {}) +jest.mock("core/presets/apis", () => {}) describe("typeCast", () => { it("should cast stringified `true` and `false` values to `boolean`", () => { From eb399cf334e97d2609762d1a3bc88179dc4e9fef Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 17 Apr 2024 16:32:37 +0200 Subject: [PATCH 6/8] update default options and getting them --- flavors/swagger-ui-react/index.jsx | 2 +- src/core/config/defaults.js | 4 +- src/core/config/type-cast/index.js | 4 +- src/core/config/type-cast/mappings.js | 125 +++++++++++++----- .../config/type-cast/type-casters/array.js | 7 +- .../config/type-cast/type-casters/boolean.js | 7 +- .../config/type-cast/type-casters/filter.js | 12 +- .../config/type-cast/type-casters/number.js | 4 +- .../config/type-cast/type-casters/object.js | 6 +- .../type-casters/syntax-highlight.js | 5 +- src/core/containers/filter.jsx | 4 +- test/unit/core/config/type-cast/index.js | 24 +--- 12 files changed, 120 insertions(+), 84 deletions(-) diff --git a/flavors/swagger-ui-react/index.jsx b/flavors/swagger-ui-react/index.jsx index 0c5ec434312..31c5d4cb37e 100644 --- a/flavors/swagger-ui-react/index.jsx +++ b/flavors/swagger-ui-react/index.jsx @@ -142,7 +142,7 @@ SwaggerUI.defaultProps = { deepLinking: false, showExtensions: false, showCommonExtensions: false, - filter: null, + filter: false, requestSnippetsEnabled: false, requestSnippets: { generators: { diff --git a/src/core/config/defaults.js b/src/core/config/defaults.js index aa304ad1784..128c1d9cf5d 100644 --- a/src/core/config/defaults.js +++ b/src/core/config/defaults.js @@ -11,8 +11,8 @@ const defaultOptions = Object.freeze({ urls: null, layout: "BaseLayout", docExpansion: "list", - maxDisplayedTags: null, - filter: null, + maxDisplayedTags: -1, + filter: false, validatorUrl: "https://validator.swagger.io/validator", oauth2RedirectUrl: `${window.location.protocol}//${window.location.host}${window.location.pathname.substring(0, window.location.pathname.lastIndexOf("/"))}/oauth2-redirect.html`, persistAuthorization: false, diff --git a/src/core/config/type-cast/index.js b/src/core/config/type-cast/index.js index d3720df86a4..608e0312b77 100644 --- a/src/core/config/type-cast/index.js +++ b/src/core/config/type-cast/index.js @@ -9,10 +9,10 @@ import typeCasters from "./mappings" const typeCast = (options) => { return Object.entries(typeCasters).reduce( - (acc, [optionPath, typeCaster]) => { + (acc, [optionPath, { typeCaster, defaultValue }]) => { if (has(acc, optionPath)) { const uncasted = get(acc, optionPath) - const casted = typeCaster(uncasted) + const casted = typeCaster(uncasted, defaultValue) acc = set(optionPath, casted, acc) } return acc diff --git a/src/core/config/type-cast/mappings.js b/src/core/config/type-cast/mappings.js index c885eb2a15f..a0288f853a6 100644 --- a/src/core/config/type-cast/mappings.js +++ b/src/core/config/type-cast/mappings.js @@ -13,41 +13,100 @@ import stringTypeCaster from "./type-casters/string" import syntaxHighlightTypeCaster from "./type-casters/syntax-highlight" import undefinedBooleanTypeCaster from "./type-casters/undefined-boolean" import undefinedStringTypeCaster from "./type-casters/undefined-string" +import defaultOptions from "../defaults" const typeCasters = { - configUrl: stringTypeCaster, - deepLinking: booleanTypeCaster("deepLinking"), - defaultModelExpandDepth: numberTypeCaster, - defaultModelRendering: stringTypeCaster, - defaultModelsExpandDepth: numberTypeCaster, - displayOperationId: booleanTypeCaster("displayOperationId"), - displayRequestDuration: booleanTypeCaster("displayRequestDuration"), - docExpansion: stringTypeCaster, - dom_id: nullableStringTypeCaster, - domNode: domNodeTypeCaster, - filter: filterTypeCaster, - maxDisplayedTags: numberTypeCaster, - oauth2RedirectUrl: undefinedStringTypeCaster, - persistAuthorization: booleanTypeCaster("persistAuthorization"), - plugins: arrayTypeCaster("plugins"), - pluginsOptions: objectTypeCaster("pluginsOptions"), - presets: arrayTypeCaster("presets"), - requestSnippets: objectTypeCaster("requestSnippets"), - requestSnippetsEnabled: booleanTypeCaster("requestSnippetsEnabled"), - showCommonExtensions: booleanTypeCaster("showCommonExtensions"), - showExtensions: booleanTypeCaster("showExtensions"), - showMutatedRequest: booleanTypeCaster("showMutatedRequest"), - spec: objectTypeCaster("spec"), - supportedSubmitMethods: arrayTypeCaster("supportedSubmitMethods"), - syntaxHighlight: syntaxHighlightTypeCaster, - "syntaxHighlight.activated": booleanTypeCaster("syntaxHighlight.activated"), - "syntaxHighlight.theme": stringTypeCaster, - tryItOutEnabled: booleanTypeCaster("tryItOutEnabled"), - url: stringTypeCaster, - urls: nullableArrayTypeCaster, - "urls.primaryName": stringTypeCaster, - validatorUrl: nullableStringTypeCaster, - withCredentials: undefinedBooleanTypeCaster, + configUrl: { typeCaster: stringTypeCaster }, + deepLinking: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.deepLinking, + }, + defaultModelExpandDepth: { + typeCaster: numberTypeCaster, + defaultValue: defaultOptions.defaultModelExpandDepth, + }, + defaultModelRendering: { typeCaster: stringTypeCaster }, + defaultModelsExpandDepth: { + typeCaster: numberTypeCaster, + defaultValue: defaultOptions.defaultModelsExpandDepth, + }, + displayOperationId: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.displayOperationId, + }, + displayRequestDuration: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.displayRequestDuration, + }, + docExpansion: { typeCaster: stringTypeCaster }, + dom_id: { typeCaster: nullableStringTypeCaster }, + domNode: { typeCaster: domNodeTypeCaster }, + filter: { typeCaster: filterTypeCaster }, + maxDisplayedTags: { + typeCaster: numberTypeCaster, + defaultValue: defaultOptions.maxDisplayedTags, + }, + oauth2RedirectUrl: { typeCaster: undefinedStringTypeCaster }, + persistAuthorization: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.persistAuthorization, + }, + plugins: { + typeCaster: arrayTypeCaster, + defaultValue: defaultOptions.plugins, + }, + pluginsOptions: { + typeCaster: objectTypeCaster, + pluginsOptions: defaultOptions.pluginsOptions, + }, + "pluginsOptions.pluginsLoadType": { typeCaster: stringTypeCaster }, + presets: { + typeCaster: arrayTypeCaster, + defaultValue: defaultOptions.presets, + }, + requestSnippets: { + typeCaster: objectTypeCaster, + defaultValue: defaultOptions.requestSnippets, + }, + requestSnippetsEnabled: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.requestSnippetsEnabled, + }, + showCommonExtensions: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.showCommonExtensions, + }, + showExtensions: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.showExtensions, + }, + showMutatedRequest: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.showMutatedRequest, + }, + spec: { typeCaster: objectTypeCaster, defaultValue: defaultOptions.spec }, + supportedSubmitMethods: { + typeCaster: arrayTypeCaster, + defaultValue: defaultOptions.supportedSubmitMethods, + }, + syntaxHighlight: { + typeCaster: syntaxHighlightTypeCaster, + defaultValue: defaultOptions.syntaxHighlight, + }, + "syntaxHighlight.activated": { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.syntaxHighlight.activated, + }, + "syntaxHighlight.theme": { typeCaster: stringTypeCaster }, + tryItOutEnabled: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.tryItOutEnabled, + }, + url: { typeCaster: stringTypeCaster }, + urls: { typeCaster: nullableArrayTypeCaster }, + "urls.primaryName": { typeCaster: stringTypeCaster }, + validatorUrl: { typeCaster: nullableStringTypeCaster }, + withCredentials: { typeCaster: undefinedBooleanTypeCaster }, } export default typeCasters diff --git a/src/core/config/type-cast/type-casters/array.js b/src/core/config/type-cast/type-casters/array.js index 75eb1b4eeaf..d11dafa78fe 100644 --- a/src/core/config/type-cast/type-casters/array.js +++ b/src/core/config/type-cast/type-casters/array.js @@ -1,10 +1,7 @@ /** * @prettier */ -import get from "lodash/get" -import defaultOptions from "../../defaults" - -const arrayTypeCaster = (optionPath) => (value) => - Array.isArray(value) ? value : get(defaultOptions, optionPath) +const arrayTypeCaster = (value, defaultValue = []) => + Array.isArray(value) ? value : defaultValue export default arrayTypeCaster diff --git a/src/core/config/type-cast/type-casters/boolean.js b/src/core/config/type-cast/type-casters/boolean.js index c1e3a09b61c..0cc584aad77 100644 --- a/src/core/config/type-cast/type-casters/boolean.js +++ b/src/core/config/type-cast/type-casters/boolean.js @@ -1,14 +1,11 @@ /** * @prettier */ -import get from "lodash/get" -import defaultOptions from "../../defaults" - -const booleanTypeCaster = (optionPath) => (value) => +const booleanTypeCaster = (value, defaultValue = false) => value === true || value === "true" || value === 1 || value === "1" ? true : value === false || value === "false" || value === 0 || value === "0" ? false - : get(defaultOptions, optionPath) + : defaultValue export default booleanTypeCaster diff --git a/src/core/config/type-cast/type-casters/filter.js b/src/core/config/type-cast/type-casters/filter.js index 2134f38f6d0..edf574f2c41 100644 --- a/src/core/config/type-cast/type-casters/filter.js +++ b/src/core/config/type-cast/type-casters/filter.js @@ -2,12 +2,10 @@ * @prettier */ const filterTypeCaster = (value) => - value === null || value === "null" - ? null - : value === true || value === "true" || value === 1 || value === "1" - ? true - : value === false || value === "false" || value === 0 || value === "0" - ? false - : String(value) + value === true || value === "true" || value === 1 || value === "1" + ? true + : value === false || value === "false" || value === 0 || value === "0" + ? false + : String(value) export default filterTypeCaster diff --git a/src/core/config/type-cast/type-casters/number.js b/src/core/config/type-cast/type-casters/number.js index 3aa87364769..7fa17ae17e5 100644 --- a/src/core/config/type-cast/type-casters/number.js +++ b/src/core/config/type-cast/type-casters/number.js @@ -1,9 +1,9 @@ /** * @prettier */ -const numberTypeCaster = (value) => { +const numberTypeCaster = (value, defaultValue = -1) => { const parsedValue = parseInt(value, 10) - return Number.isNaN(parsedValue) ? NaN : parsedValue + return Number.isNaN(parsedValue) ? defaultValue : parsedValue } export default numberTypeCaster diff --git a/src/core/config/type-cast/type-casters/object.js b/src/core/config/type-cast/type-casters/object.js index 1fc0b8eb5b5..f4a4db27e74 100644 --- a/src/core/config/type-cast/type-casters/object.js +++ b/src/core/config/type-cast/type-casters/object.js @@ -2,10 +2,8 @@ * @prettier */ import isPlainObject from "lodash/isPlainObject" -import get from "lodash/get" -import defaultOptions from "../../defaults" -const objectTypeCaster = (optionPath) => (value) => - isPlainObject(value) ? value : get(defaultOptions, optionPath) +const objectTypeCaster = (value, defaultValue = {}) => + isPlainObject(value) ? value : defaultValue export default objectTypeCaster diff --git a/src/core/config/type-cast/type-casters/syntax-highlight.js b/src/core/config/type-cast/type-casters/syntax-highlight.js index b2d28a3b391..a4d33c1c5db 100644 --- a/src/core/config/type-cast/type-casters/syntax-highlight.js +++ b/src/core/config/type-cast/type-casters/syntax-highlight.js @@ -2,14 +2,13 @@ * @prettier */ import isPlainObject from "lodash/isPlainObject" -import defaultOptions from "../../defaults" -const syntaxHighlightTypeCaster = (value) => { +const syntaxHighlightTypeCaster = (value, defaultValue) => { return isPlainObject(value) ? value : value === false || value === "false" || value === 0 || value === "0" ? { activated: false } - : defaultOptions.syntaxHighlight + : defaultValue } export default syntaxHighlightTypeCaster diff --git a/src/core/containers/filter.jsx b/src/core/containers/filter.jsx index ad60016e2b3..440b1ac4ad9 100644 --- a/src/core/containers/filter.jsx +++ b/src/core/containers/filter.jsx @@ -29,11 +29,11 @@ export default class FilterContainer extends React.Component { return (
- {filter === null || filter === false ? null : + {filter === false ? null :
diff --git a/test/unit/core/config/type-cast/index.js b/test/unit/core/config/type-cast/index.js index 762ffa99487..f47a29e5500 100644 --- a/test/unit/core/config/type-cast/index.js +++ b/test/unit/core/config/type-cast/index.js @@ -43,28 +43,10 @@ describe("typeCast", () => { it("should cast stringified `null` values to `null`", () => { const config = { validatorUrl: "null", - filter: "null", } const expectedConfig = { validatorUrl: null, - filter: null, - } - - expect(typeCast(config)).toStrictEqual(expectedConfig) - }) - - it("should cast non-number values to `NaN`", () => { - const config = { - maxDisplayedTags: "null", - defaultModelExpandDepth: {}, - defaultModelsExpandDepth: false, - } - - const expectedConfig = { - maxDisplayedTags: NaN, - defaultModelExpandDepth: NaN, - defaultModelsExpandDepth: NaN, } expect(typeCast(config)).toStrictEqual(expectedConfig) @@ -112,6 +94,9 @@ describe("typeCast", () => { urls: "urls", syntaxHighlight: "syntaxHighlight", spec: "spec", + maxDisplayedTags: "null", + defaultModelExpandDepth: {}, + defaultModelsExpandDepth: false, } const expectedConfig = { @@ -119,6 +104,9 @@ describe("typeCast", () => { urls: null, syntaxHighlight: { activated: true, theme: "agate" }, spec: {}, + maxDisplayedTags: -1, + defaultModelExpandDepth: 1, + defaultModelsExpandDepth: 1, } expect(typeCast(config)).toStrictEqual(expectedConfig) From b847ba4047334f93458d6afc39cb303d3674d569 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 17 Apr 2024 16:35:22 +0200 Subject: [PATCH 7/8] remove null filter test --- test/unit/components/filter.jsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/unit/components/filter.jsx b/test/unit/components/filter.jsx index 4fd740a7896..caa85f0f4a7 100644 --- a/test/unit/components/filter.jsx +++ b/test/unit/components/filter.jsx @@ -30,21 +30,6 @@ describe("", function(){ expect(renderedColInsideFilter.length).toEqual(1) }) - it("does not render FilterContainer if filter is null", function(){ - - // Given - let props = {...mockedProps} - props.layoutSelectors = {...mockedProps.specSelectors} - props.layoutSelectors.currentFilter = function() {return null} - - // When - let wrapper = mount() - - // Then - const renderedColInsideFilter = wrapper.find(Col) - expect(renderedColInsideFilter.length).toEqual(0) - }) - it("does not render FilterContainer if filter is false", function(){ // Given From 42bd46126c405d0d5ca3f6f5014095e775742764 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Thu, 18 Apr 2024 10:45:54 +0200 Subject: [PATCH 8/8] update type casters, default values and factorization --- flavors/swagger-ui-react/index.jsx | 2 +- src/core/config/defaults.js | 2 +- src/core/config/factorization/store.js | 4 ++-- src/core/config/merge.js | 2 +- src/core/config/type-cast/mappings.js | 7 +++++-- src/core/config/type-cast/type-casters/filter.js | 12 ++++++------ .../type-cast/type-casters/undefined-boolean.js | 11 ----------- .../plugins/swagger-client/configs-wrap-actions.js | 4 +--- test/unit/core/config/type-cast/index.js | 10 ---------- 9 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 src/core/config/type-cast/type-casters/undefined-boolean.js diff --git a/flavors/swagger-ui-react/index.jsx b/flavors/swagger-ui-react/index.jsx index 31c5d4cb37e..43aff411e1b 100644 --- a/flavors/swagger-ui-react/index.jsx +++ b/flavors/swagger-ui-react/index.jsx @@ -164,7 +164,7 @@ SwaggerUI.defaultProps = { }, tryItOutEnabled: false, displayRequestDuration: false, - withCredentials: undefined, + withCredentials: false, persistAuthorization: false, oauth2RedirectUrl: undefined, } diff --git a/src/core/config/defaults.js b/src/core/config/defaults.js index 128c1d9cf5d..19c2a0a080b 100644 --- a/src/core/config/defaults.js +++ b/src/core/config/defaults.js @@ -30,7 +30,7 @@ const defaultOptions = Object.freeze({ defaultModelsExpandDepth: 1, showExtensions: false, showCommonExtensions: false, - withCredentials: undefined, + withCredentials: false, requestSnippetsEnabled: false, requestSnippets: { generators: { diff --git a/src/core/config/factorization/store.js b/src/core/config/factorization/store.js index 9d29c6c4ae1..055aba034e6 100644 --- a/src/core/config/factorization/store.js +++ b/src/core/config/factorization/store.js @@ -1,10 +1,10 @@ /** * @prettier */ -import merge from "../merge" +import deepExtend from "deep-extend" const storeFactorization = (options) => { - const state = merge( + const state = deepExtend( { layout: { layout: options.layout, diff --git a/src/core/config/merge.js b/src/core/config/merge.js index 29974331c34..04d02efaec3 100644 --- a/src/core/config/merge.js +++ b/src/core/config/merge.js @@ -7,7 +7,7 @@ * * NOTE1: lodash.merge & lodash.mergeWith prefers to ignore undefined values * NOTE2: special handling of `domNode` option is now required as `deep-extend` will corrupt it (lodash.merge handles it correctly) - * NOTE3: oauth2RedirectUrl and withCredentials options can be set to undefined. By expecting null instead of undefined, we can't use lodash.merge. + * NOTE3: oauth2RedirectUrl option can be set to undefined. By expecting null instead of undefined, we can't use lodash.merge. * NOTE4: urls.primaryName needs to handled in special way, because it's an arbitrary property on Array instance * * TODO(vladimir.gorej@gmail.com): remove deep-extend in favor of lodash.merge diff --git a/src/core/config/type-cast/mappings.js b/src/core/config/type-cast/mappings.js index a0288f853a6..30ef5e3dfd5 100644 --- a/src/core/config/type-cast/mappings.js +++ b/src/core/config/type-cast/mappings.js @@ -11,7 +11,6 @@ import numberTypeCaster from "./type-casters/number" import objectTypeCaster from "./type-casters/object" import stringTypeCaster from "./type-casters/string" import syntaxHighlightTypeCaster from "./type-casters/syntax-highlight" -import undefinedBooleanTypeCaster from "./type-casters/undefined-boolean" import undefinedStringTypeCaster from "./type-casters/undefined-string" import defaultOptions from "../defaults" @@ -42,6 +41,7 @@ const typeCasters = { dom_id: { typeCaster: nullableStringTypeCaster }, domNode: { typeCaster: domNodeTypeCaster }, filter: { typeCaster: filterTypeCaster }, + layout: { typeCaster: stringTypeCaster }, maxDisplayedTags: { typeCaster: numberTypeCaster, defaultValue: defaultOptions.maxDisplayedTags, @@ -106,7 +106,10 @@ const typeCasters = { urls: { typeCaster: nullableArrayTypeCaster }, "urls.primaryName": { typeCaster: stringTypeCaster }, validatorUrl: { typeCaster: nullableStringTypeCaster }, - withCredentials: { typeCaster: undefinedBooleanTypeCaster }, + withCredentials: { + typeCaster: booleanTypeCaster, + defaultValue: defaultOptions.withCredentials, + }, } export default typeCasters diff --git a/src/core/config/type-cast/type-casters/filter.js b/src/core/config/type-cast/type-casters/filter.js index edf574f2c41..e13af3e787a 100644 --- a/src/core/config/type-cast/type-casters/filter.js +++ b/src/core/config/type-cast/type-casters/filter.js @@ -1,11 +1,11 @@ /** * @prettier */ -const filterTypeCaster = (value) => - value === true || value === "true" || value === 1 || value === "1" - ? true - : value === false || value === "false" || value === 0 || value === "0" - ? false - : String(value) +import booleanTypeCaster from "./boolean" + +const filterTypeCaster = (value) => { + const defaultValue = String(value) + return booleanTypeCaster(value, defaultValue) +} export default filterTypeCaster diff --git a/src/core/config/type-cast/type-casters/undefined-boolean.js b/src/core/config/type-cast/type-casters/undefined-boolean.js deleted file mode 100644 index b84ecb276b5..00000000000 --- a/src/core/config/type-cast/type-casters/undefined-boolean.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @prettier - */ -const undefinedBooleanTypeCaster = (value) => - value === true || value === "true" || value === 1 || value === "1" - ? true - : value === true || value === "false" || value === 0 || value === "0" - ? false - : undefined - -export default undefinedBooleanTypeCaster diff --git a/src/core/plugins/swagger-client/configs-wrap-actions.js b/src/core/plugins/swagger-client/configs-wrap-actions.js index 8a3df485dbd..99770d6379a 100644 --- a/src/core/plugins/swagger-client/configs-wrap-actions.js +++ b/src/core/plugins/swagger-client/configs-wrap-actions.js @@ -2,7 +2,5 @@ export const loaded = (ori, system) => (...args) => { ori(...args) const value = system.getConfigs().withCredentials - if(value !== undefined) { - system.fn.fetch.withCredentials = value - } + system.fn.fetch.withCredentials = value } diff --git a/test/unit/core/config/type-cast/index.js b/test/unit/core/config/type-cast/index.js index f47a29e5500..c233b13fbb7 100644 --- a/test/unit/core/config/type-cast/index.js +++ b/test/unit/core/config/type-cast/index.js @@ -52,14 +52,6 @@ describe("typeCast", () => { expect(typeCast(config)).toStrictEqual(expectedConfig) }) - it("should cast stringified `undefined` values to `undefined`", () => { - const config = { withCredentials: "undefined" } - - const expectedConfig = { withCredentials: undefined } - - expect(typeCast(config)).toStrictEqual(expectedConfig) - }) - it("should cast `string` values to `string`", () => { const config = { defaultModelRendering: "model", filter: "pet" } @@ -74,7 +66,6 @@ describe("typeCast", () => { oauth2RedirectUrl: "undefined", syntaxHighlight: "false", urls: "null", - withCredentials: "false", } const expectedConfig = { @@ -82,7 +73,6 @@ describe("typeCast", () => { oauth2RedirectUrl: undefined, syntaxHighlight: { activated: false }, urls: null, - withCredentials: false, } expect(typeCast(config)).toStrictEqual(expectedConfig)