Normalize PURLs constructed via New/MakePURL/MakePURLString - #30
Merged
Conversation
Parse runs packageurl-go's Normalize (per-type lowercasing, PyPI underscore-to-dash, etc) but New and the string-builder helpers did not, so pkg:pypi/Django and pkg:pypi/django were treated as distinct depending on which entry point produced them. New now calls Normalize on the constructed struct. MakePURLString and BuildPURLString run the same normalization on a stack-allocated struct before the fast string builder, passing repository_url through so registry-dependent rules (mlflow) apply. Normalize errors are discarded and whatever fields it wrote are kept, so New and the string path stay in step even for inputs that fail per-type validation. Fixes #21
There was a problem hiding this comment.
Pull request overview
Aligns all PURL construction entry points (New, MakePURLString, BuildPURLString) with Parse by applying packageurl-go’s per-type normalization rules (e.g., PyPI underscore→dash, per-ecosystem lowercasing, mlflow registry-dependent casing), preventing logically identical packages from producing distinct PURL strings depending on which constructor was used.
Changes:
- Normalize
New(...)results viaPackageURL.Normalize()(ignoring the returned error, keeping any in-place adjustments). - Add
normalizeComponents(...)and apply it to the fast string-building paths (BuildPURLString,MakePURLString) while passingrepository_urlfor registry-dependent normalization. - Add/extend tests to cover normalization behavior and ensure parity between “struct path” and “string path”, including a case where
Normalize()returns an error after making partial adjustments.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| purl.go | Calls Normalize() in New so constructed PURLs match Parse canonicalization. |
| purl_test.go | Adds normalization-specific cases and a New vs Parse round-trip equivalence test. |
| makepurl.go | Introduces normalizeComponents and applies it before the fast string builder (including registry-dependent normalization). |
| makepurl_test.go | Adds normalization coverage for composer/pypi/golang/mlflow and expands parity tests. |
| ecosystem.go | Normalizes components in MakePURLString to match MakePURL/Parse canonical output. |
| ecosystem_test.go | Adds normalization cases and verifies MakePURLString matches MakePURL even when Normalize() errors. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
andrew
added a commit
to git-pkgs/proxy
that referenced
this pull request
Aug 17, 2026
MakePURL/MakePURLString/New now apply the same per-type normalization as Parse (git-pkgs/purl#30), so canonicalPackagePURL no longer needs its own Normalize call and DB writes/lookups produce canonical keys. Existing rows written under a non-canonical purl (mixed-case pypi, composer, etc) become cache misses on lookup and re-populate under the canonical key on the next fetch; the old rows are left in place. Closes #207
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parseruns packageurl-go'sNormalize(per-type lowercasing, PyPI underscore-to-dash, etc) butNewand the string-builder helpers did not, sopkg:pypi/Djangoandpkg:pypi/djangowere distinct depending on which entry point produced them.Newnow callsNormalizeon the constructed struct.MakePURLStringandBuildPURLStringrun the same normalization on a stack-allocated struct before the fast string builder, passingrepository_urlthrough so registry-dependent rules (mlflow) apply. Normalize errors are discarded and whatever fields it wrote are kept, soNewand the string path stay in step even for inputs that fail per-type validation.This changes output for callers that persist non-canonical strings; git-pkgs/proxy#207 tracks the migration there.
BenchmarkMakePURLStringgoes from 1 alloc/call to 2 (npm: 242ns to 405ns) because upstreamNormalizesplits an empty subpath. Still well under the pre-#26 numbers.Fixes #21