Skip to content

Return an empty TagSet from GetObjectTagging instead of no body - #3150

Merged
afranken merged 1 commit into
adobe:mainfrom
avikivity:fix-empty-tagging
Sep 18, 2026
Merged

afranken merged 1 commit into
adobe:mainfrom
avikivity:fix-empty-tagging

Conversation

@avikivity

Copy link
Copy Markdown
Contributor

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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@avikivity
avikivity requested a review from afranken as a code owner September 5, 2026 18:18
Copilot AI lite review requested due to automatic review settings September 5, 2026 18:18

Copilot AI 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.

🟡 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(...)) from ObjectTaggingController.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.

avikivity added a commit to avikivity/S3Mock that referenced this pull request Sep 8, 2026
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>
@avikivity

Copy link
Copy Markdown
Contributor Author

Thanks for the review — addressed and force-pushed (now a single commit, 906f025).

NON_EMPTY could suppress <TagSet> — this one does not hold. Verified against a running server rather than by inspection; the body is:

<?xml version="1.0" encoding="UTF-8"?><Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><TagSet/></Tagging>

Jackson treats only nulls and empty containers/strings as empty, never a POJO, so tagSet is always written. It is the inner tags list that gets suppressed, which is precisely what produces <TagSet/>. No @JsonInclude(ALWAYS) needed.

Controller test round-trips through the same mapper — fair, fixed. It now asserts the literal wire format above, so it would fail if <TagSet> were ever dropped.

Missing integration test — added two to ObjectTaggingIT: one for an object that was never tagged, one after DeleteObjectTagging. Both read the raw HTTP response, since the AWS SDK tolerates an empty body and so cannot distinguish "empty TagSet" from "no document". They are marked @S3VerifiedFailure following PlainHttpIT, as unsigned raw requests cannot run against real S3.

Also added the CHANGELOG.md entry under 5.3.0.

Verified: make lint clean, 716 unit tests green, ObjectTaggingIT 7/7.

@avikivity

Copy link
Copy Markdown
Contributor Author

Note: my responses are actually from Claude.

@avikivity

Copy link
Copy Markdown
Contributor Author

@afranken please review

@afranken afranken left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍

@afranken
afranken merged commit 70f9e33 into adobe:main Sep 18, 2026
6 checks passed
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.

[Bug]: GetObjectTagging does not return required TagSet

3 participants