Skip to content

Conversation

jorgejr568
Copy link

@jorgejr568 jorgejr568 commented Sep 7, 2025

Enhanced specPath handling for absolute and relative URLs in OpenAPI integration

Summary

This PR improves the handling of specPath configuration in the Elysia OpenAPI plugin to prevent browser URL resolution issues when dealing with complex path hierarchies.

Changes Made

Core Logic Enhancement (src/index.ts):

  • Added intelligent specPath URL determination logic through a new getSpecUrl() function
  • Default behavior: For the standard pattern where specPath follows path + '/json', continues using relative paths (maintains backward compatibility)
  • Custom specPath: For user-defined absolute specPath values, preserves the absolute path to prevent browser URL resolution issues
  • This approach prevents path duplication issues that can occur when browsers resolve relative URLs in complex nested path structures

Test Coverage (test/index.test.ts):

  • Added comprehensive test cases to verify both scenarios:
    • Custom absolute specPath usage (e.g., /api/v1/openapi.json) maintains absolute path in data-url attribute
    • Default specPath pattern continues using relative paths for backward compatibility
  • Minor import organization improvements

Why This Change?

When using custom absolute specPath values, the previous logic would always convert them to relative paths, which could cause browser URL resolution problems in complex path hierarchies. This enhancement ensures that:

  1. Default usage remains unchanged (backward compatible)
  2. Custom absolute paths work correctly without duplication issues
  3. The Swagger UI can properly locate the OpenAPI specification file

Testing

  • ✅ All existing tests pass
  • ✅ New test cases cover both absolute and relative specPath scenarios
  • ✅ Verified proper data-url attribute generation in both cases

Summary by CodeRabbit

  • Bug Fixes

    • Improved OpenAPI spec URL handling in the docs UI: default spec paths now resolve to relative URLs while custom spec paths remain absolute, preventing duplicated path segments and broken links across nested routes. Applies to both Swagger and Scalar views. No public API changes.
  • Tests

    • Added tests to verify absolute custom paths and relative default paths so the docs UI loads specs correctly in both scenarios.

Copy link

coderabbitai bot commented Sep 7, 2025

Walkthrough

Introduces getSpecUrl in src/index.ts to centralize OpenAPI spec URL computation; uses relative URLs for default ${path}/json and non-leading-slash inputs, preserves leading-slash custom specPath as absolute. Updates SwaggerUIRender and ScalarRender to use specUrl. Adds tests covering both cases.

Changes

Cohort / File(s) Summary
Core logic: spec URL computation
src/index.ts
Adds getSpecUrl helper and specUrl value; replaces previous relative-path derivation. Ensures default ${path}/json and non-leading-slash specPath resolve relative, while leading-slash custom specPath remains absolute. Applied in SwaggerUIRender and ScalarRender.
Tests: spec URL resolution
test/index.test.ts
Adds tests asserting data-url is absolute for custom leading-slash specPath and relative for default ${path}/json; minor import reordering.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Docs Renderer
  participant S as Server
  Note over UI: compute specUrl via getSpecUrl(path, specPath)

  UI->>UI: getSpecUrl(path, specPath)
  alt specPath does not start with "/"
    Note right of UI #DDEBF7: return specPath as-is (relative)
  else specPath == `${path}/json`
    Note right of UI #FDEBD0: strip leading "/" → relative
  else
    Note right of UI #FADBD8: keep specPath unchanged (absolute)
  end

  UI->>S: render docs HTML with data-url=specUrl
  S-->>UI: HTML
  UI->>S: client GET specUrl
  S-->>UI: 200 OpenAPI JSON
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I nibble paths with careful cheer,
A slash trimmed here, a route held near.
Defaults go small, customs stay grand,
Spec URLs now follow the plan.
Hop, fetch, and serve — a tidy land. 🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jorgejr568 jorgejr568 marked this pull request as ready for review September 7, 2025 14:47
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/index.ts (3)

41-60: Rename “relativePath” to “specUrl” for clarity.

The value can be absolute or relative; the current name misleads.

Apply:

- const relativePath = getSpecUrl()
+ const specUrl = getSpecUrl()

and update usages:

- url: relativePath,
+ url: specUrl,

(Do this in both SwaggerUIRender and ScalarRender option objects.)


43-57: Handle absolute URLs (http/https, protocol-relative) explicitly.

If users pass 'https://…' or '//…', return as-is. Right now they’re treated as “not starting with /”, which works by accident but is brittle.

 const getSpecUrl = () => {
-    if (!specPath.startsWith('/')) {
+    // Absolute URL (e.g., https://example.com/openapi.json or //cdn/openapi.json)
+    if (/^[a-z][a-z0-9+.-]*:/.test(specPath) || specPath.startsWith('//')) {
+        return specPath
+    }
+    if (!specPath.startsWith('/')) {
         // Already relative
         return specPath
     }

49-56: Normalize slashes when comparing with the default ${path}/json.

Trailing slashes or double slashes (e.g., path='/api/docs/') can make equality checks fail and yield odd URLs like 'api/docs//json'.

-    const defaultSpecPath = `${path}/json`
-    if (specPath === defaultSpecPath) {
+    const normalize = (s: string) =>
+        s.replace(/\/+/g, '/').replace(/(^\/+)|(\/+$)/g, (_, a, b) => (a ? '/' : ''))
+    const defaultSpecPath = normalize(`${path}/json`)
+    if (normalize(specPath) === defaultSpecPath) {
         return specPath.startsWith('/') ? specPath.slice(1) : specPath
     }
test/index.test.ts (2)

276-294: Good coverage for absolute custom specPath.

Prevents duplication and asserts endpoint availability.

Add a negative assertion to catch regressions that reintroduce duplication:

-        expect(res.includes('data-url="/api/v1/openapi.json"')).toBe(true)
+        expect(res.includes('data-url="/api/v1/openapi.json"')).toBe(true)
+        expect(res.includes('data-url="/api/v1/api/v1/openapi.json"')).toBe(false)

296-314: Add a quick guard to ensure the default stays relative (no leading slash).

Minor safety check.

-        expect(res.includes('data-url="api/docs/json"')).toBe(true)
+        expect(res.includes('data-url="api/docs/json"')).toBe(true)
+        expect(res.includes('data-url="/api/docs/json"')).toBe(false)

Also consider a test for absolute URLs (http/https) to confirm they’re preserved as-is once handled in getSpecUrl.

I can add that test block if you want.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59a142b and ab4d5bc.

📒 Files selected for processing (2)
  • src/index.ts (1 hunks)
  • test/index.test.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/index.test.ts (1)
src/index.ts (1)
  • openapi (17-146)
🔇 Additional comments (3)
src/index.ts (1)

41-60: Spec URL computation logic looks solid and fixes the path duplication bug.

Clear split between default and custom specPath behaviors; preserves absolute custom paths and keeps default relative. Nice.

test/index.test.ts (2)

2-2: Import re-ordering is fine.


6-6: bun:test import placement LGTM.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/index.ts (1)

41-57: Default-relative URL may resolve incorrectly in browsers with nested paths; consider absolute for the default or document the dependency on client normalization

Returning api/docs/json (relative) from /api/docs can resolve to /api/api/docs/json under WHATWG rules if the client doesn’t normalize against the origin. Either return an absolute path for the default as well, or add a clear comment noting the reliance on Swagger UI/Scalar normalization. Also, normalize trailing slashes in path to avoid //json.

Option A (safer resolution; would require updating tests that assert a relative URL):

-    const defaultSpecPath = `${path}/json`
-    if (specPath === defaultSpecPath) {
-      return specPath.startsWith('/') ? specPath.slice(1) : specPath
-    }
+    const defaultSpecPath = `${path}/json`
+    if (specPath === defaultSpecPath) {
+      // Use absolute to avoid browser-relative resolution surprises.
+      return defaultSpecPath
+    }

Option B (preserve current behavior but harden):

-    const defaultSpecPath = `${path}/json`
+    const normalize = (s: string) => s.replace(/\/+$/, '')
+    const defaultSpecPath = `${normalize(path)}/json`
     if (specPath === defaultSpecPath) {
-      return specPath.startsWith('/') ? specPath.slice(1) : specPath
+      // Keep relative for backward-compat; note: some browsers/libs resolve this against the current path.
+      return defaultSpecPath.slice(1)
     }

To verify the resolution behavior locally:

#!/bin/bash
python - <<'PY'
from urllib.parse import urljoin
for base, rel in [
  ("http://localhost/api/docs", "api/docs/json"),
  ("http://localhost/openapi", "openapi/json"),
]:
    print(base, "+", rel, "=>", urljoin(base, rel))
PY
🧹 Nitpick comments (1)
src/index.ts (1)

41-57: Minor simplification

Inside the specPath === defaultSpecPath branch you re-check startsWith('/') which is always true for the default. This can be simplified.

-    if (specPath === defaultSpecPath) {
-      return specPath.startsWith('/') ? specPath.slice(1) : specPath
-    }
+    if (specPath === defaultSpecPath) {
+      return defaultSpecPath.slice(1)
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab4d5bc and 82ec350.

📒 Files selected for processing (1)
  • src/index.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/index.ts (1)
src/scalar/index.ts (1)
  • ScalarRender (126-162)
🔇 Additional comments (1)
src/index.ts (1)

73-73: Centralizing URL usage looks good

Using the computed specUrl in both Swagger UI and Scalar renderers keeps behavior consistent.

Also applies to: 80-81

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.

1 participant