Skip to content

Commit

Permalink
refactor: consolidate all config related code into config module (#9811)
Browse files Browse the repository at this point in the history
Refs #9806

Co-authored-by: Vladimír Gorej <vladimir.gorej@gmail.com>
  • Loading branch information
glowcloud and char0n authored Apr 15, 2024
1 parent fb075df commit 68eb346
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 190 deletions.
91 changes: 91 additions & 0 deletions src/core/config/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* @prettier
*/
import ApisPreset from "core/presets/apis"

const defaultOptions = Object.freeze({
dom_id: null,
domNode: null,
spec: {},
url: "",
urls: null,
layout: "BaseLayout",
docExpansion: "list",
maxDisplayedTags: null,
filter: null,
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,
configs: {},
custom: {},
displayOperationId: false,
displayRequestDuration: false,
deepLinking: false,
tryItOutEnabled: false,
requestInterceptor: (a) => a,
responseInterceptor: (a) => a,
showMutatedRequest: true,
defaultModelRendering: "example",
defaultModelExpandDepth: 1,
defaultModelsExpandDepth: 1,
showExtensions: false,
showCommonExtensions: false,
withCredentials: undefined,
requestSnippetsEnabled: false,
requestSnippets: {
generators: {
curl_bash: {
title: "cURL (bash)",
syntax: "bash",
},
curl_powershell: {
title: "cURL (PowerShell)",
syntax: "powershell",
},
curl_cmd: {
title: "cURL (CMD)",
syntax: "bash",
},
},
defaultExpanded: true,
languages: null, // e.g. only show curl bash = ["curl_bash"]
},
supportedSubmitMethods: [
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
],
queryConfigEnabled: false,

// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.
presets: [ApisPreset],

// Plugins; ( loaded after presets )
plugins: [],

pluginsOptions: {
// Behavior during plugin registration. Can be :
// - legacy (default) : the current behavior for backward compatibility – last plugin takes precedence over the others
// - chain : chain wrapComponents when targeting the same core component
pluginLoadType: "legacy",
},

initialState: {},

// Inline Plugin
fn: {},
components: {},

syntaxHighlight: {
activated: true,
theme: "agate",
},
})

export default defaultOptions
11 changes: 11 additions & 0 deletions src/core/config/factorization/inline-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @prettier
*/

const InlinePluginFactorization = (options) => () => ({
fn: options.fn,
components: options.components,
state: options.state,
})

export default InlinePluginFactorization
45 changes: 45 additions & 0 deletions src/core/config/factorization/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @prettier
*/
import merge from "../merge"

const storeFactorization = (options) => {
const state = merge(
{
layout: {
layout: options.layout,
filter: options.filter,
},
spec: {
spec: "",
url: options.url,
},
requestSnippets: options.requestSnippets,
},
options.initialState
)

if (options.initialState) {
/**
* If the user sets a key as `undefined`, that signals to us that we
* should delete the key entirely.
* known usage: Swagger-Editor validate plugin tests
*/
for (const [key, value] of Object.entries(options.initialState)) {
if (value === undefined) {
delete state[key]
}
}
}

return {
system: {
configs: options.configs,
},
plugins: options.presets,
pluginsOptions: options.pluginsOptions,
state,
}
}

export default storeFactorization
7 changes: 7 additions & 0 deletions src/core/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { default as inlinePluginOptionsFactorization } from "./factorization/inline-plugin"
export { default as storeOptionsFactorization } from "./factorization/store"
export { default as optionsFromQuery } from "./sources/query"
export { default as optionsFromURL } from "./sources/url"
export { default as optionsFromSystem } from "./sources/system"
export { default as defaultOptions } from "./defaults"
export { default as mergeOptions } from "./merge"
40 changes: 40 additions & 0 deletions src/core/config/merge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @prettier
*
* We're currently stuck with using deep-extend as it handles the following case:
*
* deepExtend({ a: 1 }, { a: undefined }) => { a: undefined }
*
* 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.
*
* TODO(vladimir.gorej@gmail.com): remove deep-extend in favor of lodash.merge
*/
import deepExtend from "deep-extend"

const merge = (target, ...sources) => {
let domNode = Symbol.for("domNode")
const sourcesWithoutDomNode = []

for (const source of sources) {
if (Object.hasOwn(source, "domNode")) {
domNode = source.domNode
const sourceWithoutDomNode = { ...source }
delete sourceWithoutDomNode.domNode
sourcesWithoutDomNode.push(sourceWithoutDomNode)
} else {
sourcesWithoutDomNode.push(source)
}
}

const merged = deepExtend(target, ...sourcesWithoutDomNode)

if (domNode !== Symbol.for("domNode")) {
merged.domNode = domNode
}

return merged
}

export default merge
15 changes: 15 additions & 0 deletions src/core/config/sources/query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @prettier
*/
import { parseSearch } from "core/utils"

/**
* Receives options from the query string of the URL where SwaggerUI
* is being served.
*/

const optionsFromQuery = () => (options) => {
return options.queryConfigEnabled ? parseSearch() : {}
}

export default optionsFromQuery
16 changes: 16 additions & 0 deletions src/core/config/sources/system.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @prettier
*
* Receives options from a System.
* These options are baked-in to the System during the compile time.
*/

const optionsFromSystem =
({ system }) =>
() => {
if (typeof system.specSelectors?.getLocalConfig !== "function") return {}

return system.specSelectors.getLocalConfig()
}

export default optionsFromSystem
33 changes: 33 additions & 0 deletions src/core/config/sources/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @prettier
* Receives options from a remote URL.
*/

const optionsFromURL =
({ url, system }) =>
async (options) => {
if (!url) return {}
if (typeof system.specActions?.getConfigByUrl !== "function") return {}
let resolve
const deferred = new Promise((res) => {
resolve = res
})
const callback = (fetchedOptions) => {
// receives null on remote URL fetch failure
resolve(fetchedOptions)
}

system.specActions.getConfigByUrl(
{
url,
loadRemoteConfig: true,
requestInterceptor: options.requestInterceptor,
responseInterceptor: options.responseInterceptor,
},
callback
)

return deferred
}

export default optionsFromURL
Loading

0 comments on commit 68eb346

Please sign in to comment.