Skip to content

refactor(deps): replace url-parse with the native URL API (PER-16497) - #122

Closed
Kyzgor wants to merge 8 commits into
permitio:mainfrom
Kyzgor:fix/106-remove-url-parse-dependency
Closed

Kyzgor wants to merge 8 commits into
permitio:mainfrom
Kyzgor:fix/106-remove-url-parse-dependency

Conversation

@Kyzgor

@Kyzgor Kyzgor commented Mar 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Replaces url-parse with the built-in WHATWG URL when building the OPA URL from the pdp option.
  • Removes url-parse and @types/url-parse, plus their dependencies querystringify and requires-port, from package.json and yarn.lock.
  • A new buildOpaBaseUrl() keeps the existing behavior: the port is set to 8181 and v1/data/permit/ is appended to the PDP path.
  • An invalid pdp value now throws a PermitError that names the option but not the value. The native URL error contained the whole URL, credentials included, and could end up in logs.
  • Adds 10 unit tests, including request-level checks that useOpa checks reach the right OPA URL through both the default client and an injected opaAxiosInstance.
  • Up to date with main (merged, not rebased); the retry support from Add opt-in HTTP retry support (REST + PDP) #120 in the enforcer is unchanged.

Linear

Closes #106.

Details

OPA URL (src/enforcement/enforcer.ts)

pdp OPA base URL
http://localhost:7766 or http://localhost:7766/ http://localhost:8181/v1/data/permit/
https://pdp.example.com https://pdp.example.com:8181/v1/data/permit/
https://pdp.example.com:1234 https://pdp.example.com:8181/v1/data/permit/
http://localhost:7766/prefix/ http://localhost:8181/prefix/v1/data/permit/
http://localhost:7766/prefix http://localhost:8181/prefixv1/data/permit/ (same as before; pinned by a test)
https://pdp.example.com/a/.. https://pdp.example.com:8181/v1/data/permit/ (URL resolves dot segments; url-parse kept them)

For absolute http(s) URLs in canonical form, the result is the same string url-parse produced.

Behavior change

A pdp value without a scheme, such as localhost or //localhost:7766, now fails in new Permit() with:

PermitError: Invalid PDP URL in the "pdp" option: expected an absolute http(s) URL, e.g. "http://localhost:7766".

url-parse accepted these values silently and produced an OPA URL that couldn't be used. localhost:7766 is still parsed with localhost: as the scheme, so pass a full URL.

Tests (src/tests/unit/enforcer.spec.ts)

  • Located under tests/unit, so yarn test:unit runs them.
  • They cover every row of the table above, the invalid-URL error (and that it doesn't contain credentials), and the URL that a useOpa check posts to, with and without an injected opaAxiosInstance.

Lockfile

yarn.lock removes only the four package blocks. A full yarn install rewrites about 787 lines, and it does the same on unchanged main, so the full lockfile refresh stays with PER-16497.

Testing

  • yarn build: passes
  • yarn lint: 0 errors (7 existing warnings)
  • yarn test:unit: 58 passed on Node 24 and Node 22
  • yarn test:module-imports: 9 passed
  • yarn install --frozen-lockfile: passes

Notes

Original change by @Kyzgor; the merge with main, the error handling and the test commits were added by the maintainers.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PSoip6dghQ62bLQ6GBMwTA

@Kyzgor
Kyzgor force-pushed the fix/106-remove-url-parse-dependency branch from 0053425 to aa6d88b Compare March 8, 2026 20:59
@Kyzgor

Kyzgor commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up on CI: the security/snyk (permit) check is in an ERROR state with the message "You have used your limit of private tests" — i.e. an org Snyk quota/billing condition on fork PRs, not a vulnerability finding. This PR actually removes a runtime dependency (url-parse) and its types, adding none. Could a maintainer re-run Snyk from a trusted context (or reset the quota)? Happy to help if anything is needed.

@zeevmoney zeevmoney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clean, behavior-equivalent refactor with a wired-in unit test — nice. Two minor notes.

[MEDIUM] The yarn.lock diff also pulls in ~43 unrelated @esbuild/* and @rollup/* entries resolved against registry.yarnpkg.com while the rest of the lockfile uses registry.npmjs.org. That's regeneration noise that mixes registry hosts and inflates the diff. Consider regenerating from main with the pinned yarn 1.22.22 so only the url-parse/@types/url-parse removals remain.

Comment thread src/enforcement/enforcer.ts Outdated
* @returns The OPA base URL string (e.g. `http://localhost:8181/v1/data/permit/`).
*/
export function buildOpaBaseUrl(pdp: string): string {
const opaBaseUrl = new URL(pdp);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] Scheme-less PDP input now throws instead of returning a (bad) string

new URL('localhost:7766') throws TypeError [ERR_INVALID_URL], whereas url-parse returned a malformed string without throwing. Scheme-less PDP is unsupported either way, but the failure mode changed from silent-bad-string to a throw in the Enforcer constructor.

Suggestion: Add a @throws note to buildOpaBaseUrl (optionally a t.throws test) so callers know a scheme-less pdp throws.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Resolved. Kyzgor added a @throws note in 7c38c64 and a t.throws test in 575f1b3. e5abd86 then changed the error from the raw TypeError to a PermitError. A pdp of localhost or //localhost:7766 now fails in new Permit() with:

PermitError: Invalid PDP URL in the "pdp" option: expected an absolute http(s) URL, e.g. "http://localhost:7766".

The JSDoc says @throws {PermitError}, and buildOpaBaseUrl: a PDP without a scheme throws a PermitError naming the pdp option covers both inputs.

localhost:7766 is still misparsed rather than rejected. URL reads localhost: as the scheme, so the function returns localhost:7766 unchanged. The JSDoc and the PR description say to pass a full URL.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Refactors OPA client base URL construction to use Node’s native WHATWG URL API instead of url-parse, and adds unit coverage to prevent regressions in the URL-building logic.

Changes:

  • Replaced url-parse usage with a small helper (buildOpaBaseUrl) based on the native URL API.
  • Added an AVA unit test suite covering default and edge-case PDP URL inputs.
  • Removed url-parse / @types/url-parse from dependencies and regenerated the lockfile; added a test:unit script.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/enforcement/enforcer.ts Removes url-parse, introduces buildOpaBaseUrl(), and uses it to configure the OPA axios baseURL.
src/tests/enforcer.spec.ts Adds unit tests validating the exact OPA base URL string for several PDP inputs.
package.json Removes url-parse deps and adds a test:unit script to run the new test.
yarn.lock Lockfile regeneration reflecting dependency removal and resulting resolution changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +128 to +133
export function buildOpaBaseUrl(pdp: string): string {
const opaBaseUrl = new URL(pdp);
opaBaseUrl.port = '8181';
opaBaseUrl.pathname = `${opaBaseUrl.pathname}v1/data/permit/`;
return opaBaseUrl.toString();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Resolved in e5abd86. buildOpaBaseUrl catches the URL error and throws:

PermitError: Invalid PDP URL in the "pdp" option: expected an absolute http(s) URL, e.g. "http://localhost:7766".

The message names the option but not the value. The native TypeError message included the whole URL, credentials included, which could end up in logs. Covered by buildOpaBaseUrl: a PDP without a scheme throws a PermitError naming the pdp option and new Permit does not expose credentials from an invalid PDP URL.

One gap remains: localhost:7766 is still misparsed rather than rejected. URL accepts it with localhost: as the scheme, so no error is thrown and the OPA base URL is localhost:7766. Rejecting non-http(s) schemes would add new config validation, so it is left for a follow-up on #106. The JSDoc documents the current behavior.

Comment on lines +120 to +127
/**
* Builds the OPA client base URL from the configured PDP URL by forcing the OPA
* port (8181) and appending the OPA data path. Uses the native WHATWG `URL`
* (Node >= 10), replacing the previous `url-parse` dependency (#106).
*
* @param pdp - The configured PDP base URL (e.g. `http://localhost:7766`).
* @returns The OPA base URL string (e.g. `http://localhost:8181/v1/data/permit/`).
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Resolved in 7c38c64. The @returns tag on buildOpaBaseUrl now says a PDP path with no trailing slash is glued to the data path (/prefix -> /prefixv1/data/permit/), kept from url-parse. buildOpaBaseUrl: a path prefix preserves the existing concatenation behaviour pins both /prefix and /prefix/, so changing this behavior will fail a test.

Kyzgor added 2 commits June 23, 2026 22:48
url-parse was used only to build the OPA client base URL. Node's native
WHATWG URL (available since v10; engines.node already requires >=10) does
the same, so extract a buildOpaBaseUrl() helper and drop url-parse and
@types/url-parse.

yarn.lock is pruned of url-parse and its now-orphaned transitive
dependencies (querystringify, requires-port) only; every other entry is
left byte-for-byte unchanged.
Lock the exact OPA base URL produced for the default PDP, trailing-slash,
explicit-port, https, and path-prefix inputs so the url-parse -> native URL
refactor is proven behaviour-equivalent on valid input and any regression
fails here; assert a scheme-less PDP (bare host or //host:port) throws; and
assert the Enforcer wires the OPA client baseURL to buildOpaBaseUrl(pdp).
@Kyzgor
Kyzgor force-pushed the fix/106-remove-url-parse-dependency branch from a42b43d to 575f1b3 Compare June 23, 2026 22:37
@Kyzgor

Kyzgor commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — pushed an update:

  • Lockfile: regenerating with yarn 1.22.22 didn't give a clean diff; a full install pulls in the esbuild/rollup platform packages the committed lockfile predates (the registry.yarnpkg.com host churn you flagged). So I pruned it by hand: the yarn.lock diff is now just the url-parse/@types/url-parse removals plus their orphaned querystringify/requires-port, and yarn install --frozen-lockfile is happy with it.
  • Scheme-less PDP: added a @throws note and a t.throws test, and the equivalence table now shows real old-vs-new output side by side. One thing I confirmed: a bare host or //host:port throws, but localhost:7766 is misparsed (read as <scheme>:<path>), not rejected — same footgun url-parse had.
  • PermitError: left it out here on purpose — the constructor does no config validation today, so a typed pdp error is a new surface rather than part of a dependency swap. Happy to do it as a follow-up on url-parser is an unnecessary dependency #106 that also covers the host:port case.

Rebased on main; the red check is just the fork Snyk quota error.

zeevmoney and others added 6 commits September 29, 2026 23:02
Resolve the test:unit conflict so the script runs main's unit suite
(build/tests/unit/**) and this branch's enforcer spec.

src/enforcement/enforcer.ts and yarn.lock merged without conflicts:
main's dedicated PDP axios instance and PDP/OPA retry interceptors are
kept, with buildOpaBaseUrl on top. The merged yarn.lock is main's
lockfile without the url-parse, @types/url-parse, querystringify and
requires-port entries, the same four blocks yarn removes when it
regenerates the lockfile for this manifest change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's test:unit glob (build/tests/unit/**/*.spec.js) now picks up the
OPA base URL tests, so the script matches main again and both the retry
and enforcer suites run from one place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
new URL() throws a TypeError whose enumerable input property holds the
whole PDP URL, so a caller logging the new Permit() failure with pino
wrote any user:password in the URL to its logs. url-parse never threw
here, so this path is new with the native URL switch.

buildOpaBaseUrl now throws a PermitError that names the pdp option and
the expected format, and does not carry the configured value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The only wiring test used the default PDP and read back the baseURL of an
injected instance, so a constructor that ignored config.pdp, or an
SDK-created OPA client that ignored the derived base URL, still passed.

Run a useOpa check against a non-default https PDP with a port and path
through both the SDK-created client and an injected opaAxiosInstance,
and assert the request URL at the adapter boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec header said the native URL refactor was proven equivalent to
url-parse. That holds for canonical absolute http(s) PDP URLs only: the
WHATWG parser resolves dot segments, so https://host/a/.. now yields
/v1/data/permit/ instead of /a/..v1/data/permit/.

Narrow the comment and pin the dot-segment behaviour with a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zeevmoney zeevmoney changed the title refactor(deps): replace url-parse with native URL API refactor(deps): replace url-parse with the native URL API (PER-16497) Sep 29, 2026
zeevmoney added a commit that referenced this pull request Sep 29, 2026
Brings in #122 by @Kyzgor (PER-16497, closes #106).
It builds the OPA base URL with the WHATWG URL instead of url-parse,
removes url-parse and @types/url-parse, and makes new Permit() throw a
PermitError that names the pdp option (without its value) when pdp is
empty or not an absolute URL. That throw is a breaking change for
REST-only apps with an empty or invalid pdp, so it ships with this PR.

Conflict resolution: kept this branch's @types/node and lockfile, and
dropped the url-parse, querystringify and requires-port entries. Ported
the AVA spec to Vitest as src/tests/unit/enforcement/opa-base-url.spec.ts.

Co-authored-by: Kyzgor <connordgordon95@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSoip6dghQ62bLQ6GBMwTA
@zeevmoney

Copy link
Copy Markdown
Contributor

Thank you for this contribution, @Kyzgor! Replacing url-parse with the native URL, with the equivalence tests to back it, is a good change and we're keeping all of it.

I'm closing this PR because it has been merged into #134 and will ship from there. It goes in with #134 rather than on its own because it is a breaking change. new Permit() now throws a PermitError when pdp is empty or not an absolute URL, for example an empty PERMIT_PDP_URL. In 2.7.6 the SDK still constructed in that case and REST calls kept working. #134 already carries the other behavior changes planned for that release, so this belongs with them.

What happened to your work:

Thanks again!

@zeevmoney zeevmoney closed this Sep 29, 2026
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.

url-parser is an unnecessary dependency

3 participants