fix: add UserMetadataStripped to list results - #2256
harshavardhana merged 6 commits into
Conversation
📝 WalkthroughWalkthroughObject listings and version listings now expose ChangesUser metadata listing
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ListQuery
participant stripUserMetadata
participant ObjectInfo
Client->>ListQuery: request metadata-enabled listing
ListQuery->>stripUserMetadata: transform raw UserMetadata
stripUserMetadata->>ObjectInfo: set UserMetadataStripped
ObjectInfo-->>Client: return raw and stripped metadata
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
ListObjects with WithMetadata returns UserMetadata exactly as stored on the object (X-Amz-Meta-* prefixed keys plus system entries), while StatObject returns the decoded form. Add a separate UserMetadataDecoded field to ObjectInfo and Version, populated on the V2 and versions list paths, without changing UserMetadata. Fixes minio#2054
06d1701 to
72ba76d
Compare
Replaces repeated "X-Amz-Meta-" string literals in utils.go with a single named constant, as requested in PR minio#2256 review.
List responses carry the stored metadata values verbatim, unlike HEAD/GET response headers which the server MIME-encodes. Decoding here could corrupt a stored value that literally begins with "=?", and decoding is the application's responsibility since the SDK never encodes these values itself. UserMetadataDecoded now only strips the X-Amz-Meta- prefix from keys.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
api-list_test.go (1)
92-106: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssert that the client requests metadata.
The fixture returns
<UserMetadata>unconditionally, so this test would still pass ifWithMetadata: truestopped addingmetadata=trueto the request. Check the query parameter in the handler before returning metadata.Proposed fix
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/xml") + if r.URL.Query().Get("metadata") != "true" { + http.Error(w, "metadata query parameter missing", http.StatusBadRequest) + return + } if _, versioned := r.URL.Query()["versions"]; versioned {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api-list_test.go` around lines 92 - 106, The httptest server handler currently returns user metadata regardless of the request, so it does not verify the metadata query parameter. Update the handler in the test server setup to inspect the request query and only include userMetadataXML when metadata=true is present, while preserving the existing versioned and unversioned response branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@api-list_test.go`:
- Around line 92-106: The httptest server handler currently returns user
metadata regardless of the request, so it does not verify the metadata query
parameter. Update the handler in the test server setup to inspect the request
query and only include userMetadataXML when metadata=true is present, while
preserving the existing versioned and unversioned response branches.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d259e99a-96f5-4363-bd61-a116a1da5239
📒 Files selected for processing (4)
api-datatypes.goapi-list_test.goapi-s3-datatypes.goutils.go
The field no longer decodes values; it only strips the X-Amz-Meta- prefix from keys. The field is new in this PR, so the rename is not a breaking change.
…sermetadata-decoded # Conflicts: # api-datatypes.go
Merge redundant lines so each remaining line states a contract the code cannot show.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
api-datatypes.go (1)
187-195: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCorrect the
HeadersMIME-decoding contract.
ToObjectInfoassignsHeaders: hdirectly, soHeaderscontains the raw response headers. RFC 2047 values in this field are not MIME-decoded; remove the “MIME-decoded, matching Metadata” language so the public contract does not mislead callers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api-datatypes.go` around lines 187 - 195, The Headers documentation in ObjectInfo incorrectly promises MIME-decoded RFC 2047 values. Update the Headers comment to describe the field as raw response headers without claiming MIME decoding or parity with Metadata, while preserving the existing scope, aliasing, and read-only guidance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@api-datatypes.go`:
- Around line 187-195: The Headers documentation in ObjectInfo incorrectly
promises MIME-decoded RFC 2047 values. Update the Headers comment to describe
the field as raw response headers without claiming MIME decoding or parity with
Metadata, while preserving the existing scope, aliasing, and read-only guidance.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 24e22d7d-a360-4a0e-8695-3aabb5ed2165
📒 Files selected for processing (5)
api-datatypes.goapi-list.goapi-list_test.goapi-s3-datatypes.goutils.go
|
PTAL @rraulinio |
|
PR looks good to merge @harshavardhana - let me know |
Description
Adds
UserMetadataStripped StringMaptoObjectInfoandVersion, populated when listing withListObjectsOptions{WithMetadata: true}(V2 and versioned listings). It carries the keyed formStatObject/GetObjectreturn inUserMetadata:X-Amz-Meta-*keys with the prefix stripped, system entries (content-type,expires, …) dropped, values passed through verbatim — list responses carry the stored values, so the SDK applies no decoding. The existingUserMetadatain list results is unchanged — it keeps the exact values stored on the object, per maintainer direction in #2054 ("a separate field would be needed").stripUserMetadata(utils.go); keys are canonicalized before the prefix match so server casing does not matter.listObjectsV2QueryandlistObjectVersionsQuery; copied into theObjectInfoemitted by versioned listings.UserMetadatafields corrected (they claimed keys were stripped; list results carry raw keys).TestListObjectsUserMetadataStrippedasserts, for both list paths, thatUserMetadatastays byte-identical raw ANDUserMetadataStrippedequals the stripped map — including that an RFC 2047-looking value is passed through verbatim, NOT MIME-decoded.Review updates (2026-07-22), per @harshavardhana's review:
X-Amz-Meta-lifted toconst amzMetaPrefix(fc9556e).UserMetadataDecoded→UserMetadataStrippedto match the decode removal (0fcdf85). The field is new in this PR (zero occurrences in upstream master), so the rename is not a breaking change.ObjectInfoconflict with the newHeadersfield (89bf1d2).Fixes #2054
Motivation and Context
ListObjects(WithMetadata: true)andStatObjectreturn the same user metadata in incompatible shapes, forcing callers to probe both key forms (see #2054). ChangingUserMetadatain place would break existing consumers, so the stripped form is exposed as a separate additive field.How to test this PR?
Server test matrix (summary): the same live reproduction of #2054 was run against both server lines, each built from source at master on 2026-07-17. Output was identical on both servers, for both the failing (master) client and the fixed (PR) client. (This A/B predates the 2026-07-22 rename — the field was then
UserMetadataDecodedand included a decode step; the observed values are unchanged by its removal because list XML already carries the stored values verbatim, as the tables show.)miniohq/aistor@85ddefa, built from sourceLicense: MinIO Enterprise License)UserMetadataunchanged, stripped field matchesStatObjectminio/minio@7aac2a2, built from sourceClient-side A/B against each of those servers, running the issue's reproduction program (object stored with
Hello: Worldand a Unicode value that forces RFC 2047 on the PUT wire) — the only variable is the client library:master(c30a92d) — failingStatObjectUserMetadatamap[Hello:World Unicode:smörgåsbord]map[Hello:World Unicode:smörgåsbord]ListObjectsV2UserMetadatamap[X-Amz-Meta-Hello:World X-Amz-Meta-Unicode:smörgåsbord content-type:application/octet-stream]ListObjectsV2 stripped fieldmap[Hello:World Unicode:smörgåsbord]ListObjectsversionsUserMetadatamap[X-Amz-Meta-Hello:World X-Amz-Meta-Unicode:smörgåsbord content-type:application/octet-stream]ListObjectsversions stripped fieldmap[Hello:World Unicode:smörgåsbord]Testing performed, in full:
UserMetadatabyte-identical before/after on both; stripped field correct on both list paths on both servers.go test -run TestListObjectsUserMetadataStripped -count=1 .— PASS at head 0b12391 (canned-XML httptest covering V2 + versions paths, raw-unchanged + stripped-map + verbatim-RFC-2047 assertions).go test -short -count=1 .— package suite PASS at merge commit 89bf1d2 (post-merge with upstream master).go test -race -count=1 -short ./...— full module suite PASS (12 packages ok, pre-review head 72ba76d).make lint(golangci-lint) 0 issues,go vetclean,gofumpt -dclean (pre-review head;go vet+gofumptre-run clean on every review-round commit).Types of changes
Checklist:
commit-idorPR #here)Summary by CodeRabbit
userMetadataStrippedfield to object and version listing results, returning prefix-stripped user metadata (values preserved) in JSON and omitting it from XML.