Return an empty TagSet from GetObjectTagging instead of no body - #3150
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new behavior should be locked in with an integration test, and the current XML inclusion settings may still omit the required <TagSet> element when empty.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Ensures GetObjectTagging always returns a Tagging XML document even when an object has no tags, aligning S3Mock’s HTTP/XML contract with AWS S3 and avoiding client parse failures on empty bodies.
Changes:
- Always build and return
Tagging(TagSet(...))fromObjectTaggingController.getObjectTagging()(instead of returning a 200 with no body when tags are absent). - Add a controller-slice test for the “no tags” case.
File summaries
| File | Description |
|---|---|
server/src/main/kotlin/com/adobe/testing/s3mock/s3/controller/ObjectTaggingController.kt |
Always returns a Tagging response for GetObjectTagging, even when the object has no tags. |
server/src/test/kotlin/com/adobe/testing/s3mock/s3/controller/ObjectTaggingControllerTest.kt |
Adds a test covering GetObjectTagging behavior when no tags were ever set. |
Review details
Suppressed comments (1)
server/src/main/kotlin/com/adobe/testing/s3mock/s3/controller/ObjectTaggingController.kt:76
- This changes an observable S3 HTTP/XML behavior (GetObjectTagging for untagged objects). Per INVARIANTS.md “Definition of Done”, this should also be covered by an integration test in integration-tests/ (AWS SDK v2 against the container) to lock in the contract for real clients.
// S3 always answers with a Tagging document, carrying an empty TagSet when
// the object has no tags. Returning no body at all makes clients that expect
// the documented XML fail to parse the response.
val tagging = Tagging(TagSet(s3ObjectMetadata.tags.orEmpty()))
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Address review feedback on adobe#3150. The concern that the global NON_EMPTY property inclusion could suppress <TagSet> does not hold: Jackson treats only nulls and empty containers/strings as empty, never a POJO, so the element is always written. The response body is verified to be <Tagging xmlns="..."><TagSet/></Tagging> The controller test asserted that by round-tripping through the same XmlMapper the controller uses, so it would have passed even if <TagSet> had been dropped. Assert the literal wire format instead. Add integration tests for the observable HTTP behaviour, for an object that was never tagged and for one whose tags were deleted. Both read the raw response rather than going through the AWS SDK, which tolerates an empty body and so cannot tell "no tags" from "no document". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
getObjectTagging() built its Tagging document only when the object actually carried tags, so an object with no tags produced a 200 with an empty body. S3 always answers with a Tagging document, carrying an empty TagSet when there is nothing to report, and a client that parses the documented XML fails on the empty body rather than seeing "no tags". The same applies after DeleteObjectTagging, which leaves the object with no tags at all. Build the document unconditionally and let an absent tag list serialize as <TagSet/>. The global NON_EMPTY property inclusion does not get in the way: Jackson treats only nulls and empty containers as empty, never a POJO, so TagSet itself is always written and it is the tag list inside it that is suppressed. Cover the observable HTTP behaviour with integration tests, for an object that was never tagged and for one whose tags were deleted. Both read the raw response rather than going through the AWS SDK, which tolerates an empty body and so cannot tell "no tags" from "no document". The controller test asserts the literal wire format rather than round-tripping through the same XmlMapper the controller uses, which would have passed even if TagSet had been dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
efc717f to
906f025
Compare
|
Thanks for the review — addressed and force-pushed (now a single commit,
Jackson treats only nulls and empty containers/strings as empty, never a POJO, so Controller test round-trips through the same mapper — fair, fixed. It now asserts the literal wire format above, so it would fail if Missing integration test — added two to Also added the Verified: |
|
Note: my responses are actually from Claude. |
|
@afranken please review |
Description
getObjectTagging() built its Tagging document only when the object actually carried tags, so an object with no tags produced a 200 with an empty body. S3 always answers with a Tagging document, carrying an empty TagSet when there is nothing to report, and a client that parses the documented XML fails on the empty body rather than seeing "no tags".
The same applies after DeleteObjectTagging, which leaves the object with no tags at all.
Build the document unconditionally and let an absent tag list serialize as .
Related Issue
Fixes #3149
Motivation and Context
ScyllaDB is considering switching its test suite from minio to S3Mock, and this stands in the way.
How Has This Been Tested?
Ran the ScyllaDB test suite againt S3Mock with this change.
Screenshots (if appropriate):
Types of changes
Checklist: