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 flavors/swagger-ui-react/index.jsx
Original file line number Diff line number Diff line change
@@ -142,7 +142,7 @@ SwaggerUI.defaultProps = {
deepLinking: false,
showExtensions: false,
showCommonExtensions: false,
filter: null,
filter: false,
char0n marked this conversation as resolved.
Show resolved Hide resolved
requestSnippetsEnabled: false,
requestSnippets: {
generators: {
@@ -164,7 +164,7 @@ SwaggerUI.defaultProps = {
},
tryItOutEnabled: false,
displayRequestDuration: false,
withCredentials: undefined,
withCredentials: false,
persistAuthorization: false,
oauth2RedirectUrl: undefined,
}
4 changes: 2 additions & 2 deletions src/core/components/live-response.jsx
Original file line number Diff line number Diff line change
@@ -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>
4 changes: 1 addition & 3 deletions src/core/components/operation-tag.jsx
Original file line number Diff line number Diff line change
@@ -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}
>
<DeepLink
enabled={isDeepLinkingEnabled}
enabled={deepLinking}
isShown={showTag}
path={createDeepLinkPath(tag)}
text={tag} />
6 changes: 3 additions & 3 deletions src/core/config/defaults.js
Original file line number Diff line number Diff line change
@@ -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,
@@ -30,7 +30,7 @@ const defaultOptions = Object.freeze({
defaultModelsExpandDepth: 1,
showExtensions: false,
showCommonExtensions: false,
withCredentials: undefined,
withCredentials: false,
requestSnippetsEnabled: false,
requestSnippets: {
generators: {
4 changes: 2 additions & 2 deletions src/core/config/factorization/store.js
Original file line number Diff line number Diff line change
@@ -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,
5 changes: 3 additions & 2 deletions src/core/config/merge.js
Original file line number Diff line number Diff line change
@@ -7,12 +7,13 @@
*
* 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([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")
@@ -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) => {
char0n marked this conversation as resolved.
Show resolved Hide resolved
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
115 changes: 115 additions & 0 deletions src/core/config/type-cast/mappings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* @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 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 },
layout: { typeCaster: stringTypeCaster },
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: booleanTypeCaster,
defaultValue: defaultOptions.withCredentials,
},
}

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
char0n marked this conversation as resolved.
Show resolved Hide resolved

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
*/
import booleanTypeCaster from "./boolean"

const filterTypeCaster = (value) => {
const defaultValue = String(value)
return booleanTypeCaster(value, defaultValue)
}

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
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
@@ -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,
4 changes: 2 additions & 2 deletions src/core/containers/filter.jsx
Original file line number Diff line number Diff line change
@@ -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>
Loading