Skip to content

Resolve parameters embedded in path segments - #1669

Merged
sserrata merged 4 commits into
PaloAltoNetworks:mainfrom
fly1d:fix/custom-verb-parameters
Nov 2, 2026
Merged

sserrata merged 4 commits into
PaloAltoNetworks:mainfrom
fly1d:fix/custom-verb-parameters

Conversation

@fly1d

@fly1d fly1d commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Refs #621. The binding fix in #1311 does not resolve parameters embedded in a segment, such as /records/{id}:publish.

Pass the original api.path explicitly to request and snippet builders. Use the authored path for embedded parameters and literal collection tokens; ordinary standalone parameters retain the SDK's :name representation and bindings. Inserted /, ? and # are encoded. Bindings are cleared only on the materialized clone, and the original request remains unchanged. Unclosed parameters warn and retain the converted path; adjacent extra braces keep their literal text.

Include the main-branch changes through #1677, preserving its body-error handling in both consumers. An explicit string annotation fixes type inference in the new upstream Jest project; the emitted builder JavaScript is unchanged by that annotation.

Validation on Node 26.5.0/macOS: full yarn test passes 353 tests across 16 suites. All three packages build without cache; strict changed-file ESLint, Prettier and diff checks pass. The existing path-parameter fixtures remain unchanged.

Required full yarn lint still exits 2 at the existing Cypress cypress/globals configuration error. No browser or hosted CI matrix result is claimed.

@sserrata sserrata left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The embedded path parameter cases are handled, but the internal variable names appear in user-visible output. Please address the inline items. Consider doing this in the plugin's Postman conversion or building the path from api.path, so that the theme does not need to undo the converter's {{name}} format.

if (!substituted || resolved === "") return resolved;

// Bind once so a leading colon in the value cannot be interpreted again.
let key = `__openapi_path_${index}`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every resolved embedded segment becomes :__openapi_path_N. Raw URL output (getRaw(), toString(true), toJSON(), code snippets) then shows /records/:__openapi_path_1 and not /records/9527:publish. Use the internal variable only when the resolved value starts with :.

);
postman.url.path = postman.url.path?.map((segment, index) => {
let substituted = false;
const resolved = segment.replace(/\{\{([^{}]+)\}\}/g, (token, name) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex replaces every {{name}} token whose name matches a path param. A literal collection-variable token is also replaced, and a converter change can stop the fix with no error.


// Bind once so a leading colon in the value cannot be interpreted again.
let key = `__openapi_path_${index}`;
while (postman.url.variables.has(key)) key += "_";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The collision check looks only at url.variables. A native :__openapi_path_N segment that has no variable gets the injected value. Use a key that cannot collide.

let substituted = false;
const resolved = segment.replace(/\{\{([^{}]+)\}\}/g, (token, name) => {
const value = values.get(name);
if (typeof value !== "string") return token;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Embedded params with no value stay as {{id}}, but native params fall back to the example. The URL and the snippets then show both formats.

substituted = true;
return value;
});
if (!substituted || resolved === "") return resolved;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty value in a middle segment gives /records//items. The test covers only the trailing segment.

* ========================================================================== */

// eslint-disable-next-line import/no-extraneous-dependencies
import { processOpenapiFile } from "docusaurus-plugin-openapi-docs/src/openapi/openapi";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The theme test now imports plugin source at runtime and uses toBeInstanceOf(sdk.Request) across two package copies. It runs a full conversion for each of about 21 cases. Use inline sdk.Request fixtures, or cache the conversion by template.

@sserrata sserrata left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. The internal variable names are now gone. One behavior change must be reverted before merge:

fallback !== null &&
String(fallback) !== ""
? String(fallback)
: `{{${name}}}`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior change for all endpoints: pathTemplate is now always passed, so an unfilled path param shows as {{petId}} in place of :petId. This happens in snippets and in the sent URL (/pets/%7B%7BpetId%7D%7D). It affects every existing site. Please keep the earlier :name output for unfilled params, and change only the embedded-parameter case.

}
const value = values.get(name);
if (value !== undefined && value !== null && String(value) !== "") {
return String(value);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value goes into the segment without encoding. With /records/{id}:publish and id = a?b, the path becomes /records/a and the query becomes b:publish. A # moves the rest into the fragment. Encode reserved characters (/, ?, #) in the value.

postman.url.path = path.split("/").map((segment) =>
segment.replace(/\{([^{}]+)\}/g, (token, name, offset) => {
// Only OpenAPI placeholders are path parameters; collection tokens are literal.
if (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The adjacent-brace check leaves {id} unresolved in /a/{{id} or /a/{id}}, and the user gets no warning.


if (pathTemplate === undefined) return;

const values = new Map(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When pathTemplate is set, the variables are assimilated, read back and cleared, and fallbackValues (line 113) is built even when it is not used. Build one value map and resolve from it.

request ??
new sdk.Request({ url: "http://example.com/pets/:petId", method: "GET" });
return buildPostmanRequest(postman, {
pathTemplate: request && requestTemplates.get(request),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests get pathTemplate through a hidden WeakMap. Pass it explicitly in the options so that the dependency is visible. The & { pathTemplate?: string } on line 15 is also not necessary.

@fly1d fly1d changed the title fix(theme): resolve embedded path parameters Resolve parameters embedded in path segments Oct 29, 2026

@sserrata sserrata left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. All review items are addressed, and unfilled path params keep the earlier :name output.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 1b8a7d2):

https://docusaurus-openapi-36b86--pr1669-cnv47t9r.web.app

(expires Mon, 09 Nov 2026 18:50:39 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: bf293780ee827f578864d92193b8c2866acd459f

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

📸 Visual Diff Report

Snapshot comparison against https://docusaurus-openapi.tryingpan.dev/

Total ✅ Match ❌ Diff ⏭ Skip
228 228 0 0

✅ No visual regressions detected.

Updated at 2026-11-02T18:57:28.213Z · run 41172014882

@sserrata

Copy link
Copy Markdown
Member

This PR now conflicts with main after #1677 merged (CodeSnippets/index.tsx, Request/index.tsx). Please rebase onto main. Thanks.

@sserrata
sserrata merged commit da2980f into PaloAltoNetworks:main Nov 2, 2026
11 of 12 checks passed
cursor Bot pushed a commit to sean0407/docusaurus-openapi-docs that referenced this pull request Nov 3, 2026
Keep both the scalar query serialization fix and the pathTemplate handling from PaloAltoNetworks#1669. setPathParams now takes normalized path params plus an optional path template, and the call site passes normalizeParams(pathParams) and pathTemplate. Also clear the two import/order warnings with eslint --fix.

This branch was successfully deployed

2 active deployments
preview — 1b8a7d2d Deployed Nov 2, 2026 by fly1d via Visual Diff #2446
default — 1b8a7d2d Deployed Oct 31, 2026 by fly1d via Analyze Unsafe (javascript) #2446
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants