Skip to content

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

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

Merged
merged 9 commits into from
Apr 18, 2024
2 changes: 1 addition & 1 deletion flavors/swagger-ui-react/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ SwaggerUI.defaultProps = {
deepLinking: false,
showExtensions: false,
showCommonExtensions: false,
filter: null,
filter: false,
requestSnippetsEnabled: false,
requestSnippets: {
generators: {
Expand Down
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
4 changes: 2 additions & 2 deletions src/core/config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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
24 changes: 24 additions & 0 deletions src/core/config/type-cast/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @prettier
*/
import has from "lodash/has"
import get from "lodash/get"
import set from "lodash/fp/set"

import typeCasters from "./mappings"

const typeCast = (options) => {
return Object.entries(typeCasters).reduce(
(acc, [optionPath, { typeCaster, defaultValue }]) => {
if (has(acc, optionPath)) {
const uncasted = get(acc, optionPath)
const casted = typeCaster(uncasted, defaultValue)
acc = set(optionPath, casted, acc)
}
return acc
},
{ ...options }
)
}

export default typeCast
112 changes: 112 additions & 0 deletions src/core/config/type-cast/mappings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* @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"
import defaultOptions from "../defaults"

const typeCasters = {
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
7 changes: 7 additions & 0 deletions src/core/config/type-cast/type-casters/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @prettier
*/
const arrayTypeCaster = (value, defaultValue = []) =>
Array.isArray(value) ? value : defaultValue

export default arrayTypeCaster
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, defaultValue = false) =>
value === true || value === "true" || value === 1 || value === "1"
? true
: value === false || value === "false" || value === 0 || value === "0"
? false
: defaultValue

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

export default domNodeTypeCaster
11 changes: 11 additions & 0 deletions src/core/config/type-cast/type-casters/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +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)

export default filterTypeCaster
6 changes: 6 additions & 0 deletions src/core/config/type-cast/type-casters/nullable-array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @prettier
*/
const nullableArrayTypeCaster = (value) => (Array.isArray(value) ? value : null)

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

export default nullableStringTypeCaster
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, defaultValue = -1) => {
const parsedValue = parseInt(value, 10)
return Number.isNaN(parsedValue) ? defaultValue : parsedValue
}

export default numberTypeCaster
9 changes: 9 additions & 0 deletions src/core/config/type-cast/type-casters/object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @prettier
*/
import isPlainObject from "lodash/isPlainObject"

const objectTypeCaster = (value, defaultValue = {}) =>
isPlainObject(value) ? value : defaultValue

export default objectTypeCaster
6 changes: 6 additions & 0 deletions src/core/config/type-cast/type-casters/string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @prettier
*/
const stringTypeCaster = (value) => String(value)

export default stringTypeCaster
14 changes: 14 additions & 0 deletions src/core/config/type-cast/type-casters/syntax-highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @prettier
*/
import isPlainObject from "lodash/isPlainObject"

const syntaxHighlightTypeCaster = (value, defaultValue) => {
return isPlainObject(value)
? value
: value === false || value === "false" || value === 0 || value === "0"
? { activated: false }
: defaultValue
}

export default syntaxHighlightTypeCaster
11 changes: 11 additions & 0 deletions src/core/config/type-cast/type-casters/undefined-boolean.js
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions src/core/config/type-cast/type-casters/undefined-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @prettier
*/
const undefinedStringTypeCaster = (value) =>
value === undefined || value === "undefined" ? undefined : String(value)

export default undefinedStringTypeCaster
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 === false ? 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={typeof filter === "string" ? filter : ""}
disabled={isLoading}/>
</Col>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/core/plugins/layout/spec-extensions/wrap-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ 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)
}
}
// Limit to [max] items, if specified
if (maxDisplayedTags && !isNaN(maxDisplayedTags) && maxDisplayedTags >= 0) {
if (maxDisplayedTags >= 0) {
taggedOps = taggedOps.slice(0, maxDisplayedTags)
}

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
}
}
Loading