Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(config): cast configuration values into proper types #9829

Merged
merged 9 commits into from
Apr 18, 2024
4 changes: 2 additions & 2 deletions src/core/components/live-response.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ export default class LiveResponse extends React.Component {

return (
<div>
{ curlRequest && (requestSnippetsEnabled === true || requestSnippetsEnabled === "true"
{ curlRequest && requestSnippetsEnabled
? <RequestSnippets request={ curlRequest }/>
: <Curl request={ curlRequest } />
)}
}
{ url && <div>
<div className="request-url">
<h4>Request URL</h4>
Expand Down
4 changes: 1 addition & 3 deletions src/core/components/operation-tag.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -80,7 +78,7 @@ export default class OperationTag extends React.Component {
data-is-open={showTag}
>
<DeepLink
enabled={isDeepLinkingEnabled}
enabled={deepLinking}
isShown={showTag}
path={createDeepLinkPath(tag)}
text={tag} />
Expand Down
3 changes: 2 additions & 1 deletion src/core/config/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* TODO([email protected]): 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")
Expand Down Expand Up @@ -51,7 +52,7 @@ const merge = (target, ...sources) => {
merged.urls.primaryName = primaryName
}

return merged
return typeCast(merged)
}

export default merge
22 changes: 22 additions & 0 deletions src/core/config/type-cast/index.js
Original file line number Diff line number Diff line change
@@ -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) => {
char0n marked this conversation as resolved.
Show resolved Hide resolved
return Object.entries(typeCasters).reduce(
(acc, [option, typeCaster]) => {
char0n marked this conversation as resolved.
Show resolved Hide resolved
if (has(acc, option)) {
acc = set(acc, option, typeCaster(get(acc, option)))
char0n marked this conversation as resolved.
Show resolved Hide resolved
}
return acc
},
{ ...options }
)
}

export default typeCast
11 changes: 11 additions & 0 deletions src/core/config/type-cast/type-casters/boolean.js
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions src/core/config/type-cast/type-casters/combined.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @prettier
*/
const combinedTypeCaster = (firstTypeCaster, secondTypeCaster) => (value) =>
char0n marked this conversation as resolved.
Show resolved Hide resolved
secondTypeCaster(firstTypeCaster(value))

export default combinedTypeCaster
7 changes: 7 additions & 0 deletions src/core/config/type-cast/type-casters/false.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @prettier
*/
const falseTypeCaster = (value) =>
value === "false" || value === "0" || value === 0 ? false : value

export default falseTypeCaster
36 changes: 36 additions & 0 deletions src/core/config/type-cast/type-casters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @prettier
char0n marked this conversation as resolved.
Show resolved Hide resolved
*/

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 = {
char0n marked this conversation as resolved.
Show resolved Hide resolved
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
6 changes: 6 additions & 0 deletions src/core/config/type-cast/type-casters/null.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @prettier
*/
const nullTypeCaster = (value) => (value === "null" ? null : value)

export default nullTypeCaster
9 changes: 9 additions & 0 deletions src/core/config/type-cast/type-casters/number.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @prettier
*/
const numberTypeCaster = (value) => {
char0n marked this conversation as resolved.
Show resolved Hide resolved
const parsedValue = parseInt(value)
char0n marked this conversation as resolved.
Show resolved Hide resolved
return isNaN(parsedValue) ? value : parsedValue
char0n marked this conversation as resolved.
Show resolved Hide resolved
}

export default numberTypeCaster
7 changes: 7 additions & 0 deletions src/core/config/type-cast/type-casters/undefined.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @prettier
*/
const undefinedTypeCaster = (value) =>
value === "undefined" ? undefined : value

export default undefinedTypeCaster
5 changes: 2 additions & 3 deletions src/core/containers/OperationContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default class OperationContainer extends PureComponent {
const { tryItOutEnabled } = props.getConfigs()

this.state = {
tryItOutEnabled: tryItOutEnabled === true || tryItOutEnabled === "true",
tryItOutEnabled,
executeInProgress: false
}
}
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/core/containers/filter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ export default class FilterContainer extends React.Component {

return (
<div>
{filter === null || filter === false || filter === "false" ? null :
{!filter ? null :
<div className="filter-container">
<Col className="filter wrapper" mobile={12}>
<input className={classNames.join(" ")} placeholder="Filter by tag" type="text"
onChange={this.onFilterChange} value={filter === true || filter === "true" ? "" : filter}
onChange={this.onFilterChange} value={filter === true ? "" : filter}
char0n marked this conversation as resolved.
Show resolved Hide resolved
disabled={isLoading}/>
</Col>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/core/plugins/layout/spec-extensions/wrap-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
char0n marked this conversation as resolved.
Show resolved Hide resolved
taggedOps = fn.opsFilter(taggedOps, filter)
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/plugins/swagger-client/configs-wrap-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
glowcloud marked this conversation as resolved.
Show resolved Hide resolved
const PlainTextViewer = system.getComponent("PlainTextViewer")

if (!canSyntaxHighlight && typeof renderPlainText === "function") {
Expand Down
72 changes: 72 additions & 0 deletions test/unit/core/config/type-cast/index.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
38 changes: 0 additions & 38 deletions test/unit/core/plugins/swagger-js/withCredentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down