Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/core/plugins/spec/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import assocPath from "lodash/fp/assocPath"
import constant from "lodash/constant"

import { paramToValue, isEmptyValue } from "core/utils"
import wrapParameterMacro from "core/utils/wrap-parameter-macro"

// Actions conform to FSA (flux-standard-actions)
// {type: string,payload: Any|Error, meta: obj, error: bool}
Expand Down Expand Up @@ -112,7 +113,7 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio
spec: json,
baseDoc: String(new URL(url, document.baseURI)),
modelPropertyMacro,
parameterMacro,
parameterMacro: wrapParameterMacro(parameterMacro),
requestInterceptor,
responseInterceptor
}).then( ({spec, errors}) => {
Expand Down Expand Up @@ -184,7 +185,7 @@ const debResolveSubtrees = debounce(() => {
const { errors, spec } = await resolveSubtree(specWithCurrentSubtrees, path, {
baseDoc: String(new URL(specSelectors.url(), document.baseURI)),
modelPropertyMacro,
parameterMacro,
parameterMacro: wrapParameterMacro(parameterMacro),
requestInterceptor,
responseInterceptor
})
Expand Down
3 changes: 2 additions & 1 deletion src/core/plugins/swagger-client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Http, { makeHttp, serializeRes } from "swagger-client/es/http"
import { makeResolveSubtree } from "swagger-client/es/subtree-resolver"
import { opId } from "swagger-client/es/helpers"
import { loaded } from "./configs-wrap-actions"
import wrapParameterMacro from "../../utils/wrap-parameter-macro"

export default function({ configs, getConfigs }) {
return {
Expand All @@ -27,7 +28,7 @@ export default function({ configs, getConfigs }) {
const freshConfigs = getConfigs()
const defaultOptions = {
modelPropertyMacro: freshConfigs.modelPropertyMacro,
parameterMacro: freshConfigs.parameterMacro,
parameterMacro: wrapParameterMacro(freshConfigs.parameterMacro),
requestInterceptor: freshConfigs.requestInterceptor,
responseInterceptor: freshConfigs.responseInterceptor,
strategies: [
Expand Down
23 changes: 23 additions & 0 deletions src/core/utils/wrap-parameter-macro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Wraps a user-provided parameterMacro function to handle undefined return values.
*
* When parameterMacro returns undefined (e.g., for parameters the user doesn't
* want to modify), swagger-client would attempt to set the parameter's default
* to undefined, which causes a "Invalid value used as weak map key" error in
* OpenAPI 3.1.x resolution.
*
* This wrapper ensures that undefined return values are replaced with the
* parameter's existing default value, preventing the error.
*/
const wrapParameterMacro = (parameterMacro) => {
if (typeof parameterMacro !== "function") return parameterMacro
return (operation, parameter) => {
const result = parameterMacro(operation, parameter)
if (typeof result === "undefined") {
return parameter.default
}
return result
}
}

export default wrapParameterMacro
93 changes: 93 additions & 0 deletions test/unit/core/utils/wrap-parameter-macro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import wrapParameterMacro from "core/utils/wrap-parameter-macro"

describe("wrapParameterMacro", function () {
it("should return the input unchanged if it is not a function", function () {
expect(wrapParameterMacro(null)).toBe(null)
expect(wrapParameterMacro(undefined)).toBe(undefined)
})

it("should pass through non-undefined return values from the macro", function () {
const macro = (operation, parameter) => "customValue"
const wrapped = wrapParameterMacro(macro)

const result = wrapped({ operationId: "test" }, { name: "param1" })
expect(result).toBe("customValue")
})

it("should return parameter.default when macro returns undefined", function () {
const macro = (operation, parameter) => {
if (parameter.name === "wanted") {
return "overridden"
}
// returns undefined for other parameters
}
const wrapped = wrapParameterMacro(macro)

const resultForWanted = wrapped(
{ operationId: "test" },
{ name: "wanted", default: "original" }
)
expect(resultForWanted).toBe("overridden")

const resultForOther = wrapped(
{ operationId: "test" },
{ name: "other", default: "existingDefault" }
)
expect(resultForOther).toBe("existingDefault")
})

it("should return undefined when macro returns undefined and parameter has no default", function () {
const macro = () => undefined
const wrapped = wrapParameterMacro(macro)

const result = wrapped(
{ operationId: "test" },
{ name: "param1" }
)
expect(result).toBe(undefined)
})

it("should allow null return values to pass through", function () {
const macro = () => null
const wrapped = wrapParameterMacro(macro)

const result = wrapped(
{ operationId: "test" },
{ name: "param1", default: "existingDefault" }
)
expect(result).toBe(null)
})

it("should allow empty string return values to pass through", function () {
const macro = () => ""
const wrapped = wrapParameterMacro(macro)

const result = wrapped(
{ operationId: "test" },
{ name: "param1", default: "existingDefault" }
)
expect(result).toBe("")
})

it("should allow zero return values to pass through", function () {
const macro = () => 0
const wrapped = wrapParameterMacro(macro)

const result = wrapped(
{ operationId: "test" },
{ name: "param1", default: 42 }
)
expect(result).toBe(0)
})

it("should allow false return values to pass through", function () {
const macro = () => false
const wrapped = wrapParameterMacro(macro)

const result = wrapped(
{ operationId: "test" },
{ name: "param1", default: true }
)
expect(result).toBe(false)
})
})