Skip to content

Fix NuGet V2 release-notes fetch — remove rejected $format=json, parse Atom XML

Placeholder ppxd requested to merge fix-nuget-notes-v2-format-json-rejected into main

Summary

Real-world failure reported during release creation:

Nuget feed returned 400

Source: NuGetPackageNotesStrategy.GetNotesV2Async requested release notes via /Packages(Id='X',Version='Y')?$format=json. The default NuGet.Server configuration (and many derivative private feeds) rejects the Format OData query option with:

URI or query string invalid. Query option 'Format' is not allowed.
To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute
or QueryValidationSettings.

The server returns 400 or 404 depending on configuration — either way the operator hits Nuget feed returned <status> on the release page and can't see release notes for IIS packages.

Fix

  • Drop ?$format=json. Atom XML is the OData V2 default and every conformant V2 server returns it without opt-in. URL-encode the single quotes (%27) in OData string literals defensively.
  • New ParseNuGetV2AtomEntry extracts <d:ReleaseNotes>, <d:Description>, <d:Published> via XDocument — same shape NuGetPackageVersionStrategy already uses. Mirrors the existing V3 catalog leaf parser contract (prefer ReleaseNotes, fall back to Description; tolerant Empty when neither present).
  • Removed the obsolete ParseNuGetV2Entry JSON-parsing static helper (internal; no external API touched).

Test plan

  • dotnet build Squid.sln → 0 errors
  • dotnet test NuGetPackageNotesStrategyTests → 19/19 pass, 185ms (4 obsolete JSON cases replaced with 6 XML cases: ReleaseNotes extraction, Description fallback, multi-line preservation, Empty-vs-Failure distinction, malformed XML rejection, OData-error-body robustness)
  • Live E2E against sjfood: new GetNotesAsync_FetchesV2AtomEntry_* drives the strategy with a real package/version pair discovered from the feed. Pinned with the exact failure fragment NuGet feed returned 400 so a regression to ?$format=json fails loudly. 6/6 live E2E pass against https://nuget.sjfood.us/nuget in 62s
  • Full Squid.UnitTests suite → 5497/5497 pass (zero regression)
  • CI: full pipeline
  • Manual smoke (post-merge): retry the IIS release creation on the operator's feed; release notes column populates (or shows Empty if package has no notes) instead of the 400 error

Backward compatibility

  • No public API removed; internal ParseNuGetV2Entry (JSON) replaced by ParseNuGetV2AtomEntry (XML). Same GetNotesAsync external contract.
  • V2 servers that previously supported ?$format=json continue to work — they also serve Atom XML by default; we're just no longer requesting the optional JSON variant.
  • V3 path untouched.

Merge request reports

Loading