Skip to content

Commit c0fd7c5

Browse files
frristclaude
andcommitted
review(s3frontend): named gate wrappers; withTags becomes applyTagsIfPresent
Per review on #81: the require-lock boolean disappears from call sites behind four thin wrappers — mutateVersionLock / mutateVersionTags and resolveLockTarget / resolveTagTarget — so each operation family sets its own gate and a bare true/false never appears at a call site. withTags is renamed applyTagsIfPresent: the old name implied a non-mutating derive (the Go WithX convention) while the function mutates its argument in place; the new name and its comment state the mutation outright. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5a4e857 commit c0fd7c5

5 files changed

Lines changed: 50 additions & 19 deletions

File tree

s3frontend/copy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func (b *Backend) CopyObject(ctx context.Context, input s3response.CopyObjectInp
178178
// Commit to the destination via the write rule: splice + reference index.
179179
// The new claims use the DESTINATION bucket/space; the same digests gain
180180
// another reference.
181-
node, effState, err := b.commitVersion(ctx, bucketState, dstKey, dstMf, withTags(initState, dstTags), nil)
181+
node, effState, err := b.commitVersion(ctx, bucketState, dstKey, dstMf, applyTagsIfPresent(initState, dstTags), nil)
182182
if err != nil {
183183
return s3response.CopyObjectOutput{}, err
184184
}

s3frontend/multipart.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ func (b *Backend) CompleteMultipartUpload(ctx context.Context, input *s3.Complet
579579
// Validated at CreateMultipartUpload; a parse failure here is a bug.
580580
return s3response.CompleteMultipartUploadResult{}, "", fmt.Errorf("s3frontend: parse session tagging: %w", err)
581581
}
582-
initState = withTags(initState, tags)
582+
initState = applyTagsIfPresent(initState, tags)
583583

584584
// Commit through the §5 write rule (docs/s3-versioning.md): seq allocation,
585585
// supersession per the bucket's versioning state, and the post-commit

s3frontend/object.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (b *Backend) PutObject(ctx context.Context, input s3response.PutObjectInput
7373
if err != nil {
7474
return s3response.PutObjectOutput{}, err
7575
}
76-
initState = withTags(initState, tags)
76+
initState = applyTagsIfPresent(initState, tags)
7777

7878
// PRECONDITIONS (no lock): If-Match / If-None-Match. Evaluated here to fail
7979
// fast before ingest, then RE-CHECKED under the per-bucket lock at commit so

s3frontend/objectlock.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ func lockStateFromHeaders(st *registry.State, mode types.ObjectLockMode, retainU
9393
// docs/s3-object-tagging.md §2): bucket, key existence, the lock-enabled gate
9494
// when the operation requires it (the lock methods do; tagging has no gate),
9595
// then versionId grammar and resolution (via resolveVersion) and the
96-
// delete-marker sentinel. Key
96+
// delete-marker sentinel. Call through resolveLockTarget /
97+
// resolveTagTarget, which set the gate for their operation family. Key
9798
// existence outranks the lock-enabled check — a missing key on a bucket
9899
// without lock is NoSuchKey, never the missing-configuration error
99100
// (GetObjectRetention_non_existing_object pins it, matching posix).
@@ -127,6 +128,13 @@ func (b *Backend) resolveStateTarget(ctx context.Context, bucketName, key, versi
127128
return rv, nil
128129
}
129130

131+
// resolveLockTarget resolves a lock method's target: resolveStateTarget
132+
// with the lock-enabled gate required. The tagging twin is
133+
// resolveTagTarget (objecttag.go).
134+
func (b *Backend) resolveLockTarget(ctx context.Context, bucketName, key, versionID string) (*resolvedVersion, error) {
135+
return b.resolveStateTarget(ctx, bucketName, key, versionID, true)
136+
}
137+
130138
// versionStateOf seeks the resolved version's VersionState block: leaf →
131139
// state tree → block. Nil (with no error) when the key or version carries no
132140
// explicit state — the callers' ErrNoSuchObjectLockConfiguration case.
@@ -153,7 +161,7 @@ func (b *Backend) versionStateOf(ctx context.Context, rv *resolvedVersion) (*msb
153161
// verbatim (§6). An expired retention is returned as stored; expiry is the
154162
// controller's judgment.
155163
func (b *Backend) GetObjectRetention(ctx context.Context, bucket, object, versionId string) ([]byte, error) {
156-
rv, err := b.resolveStateTarget(ctx, bucket, object, versionId, true)
164+
rv, err := b.resolveLockTarget(ctx, bucket, object, versionId)
157165
if err != nil {
158166
return nil, err
159167
}
@@ -171,7 +179,7 @@ func (b *Backend) GetObjectRetention(ctx context.Context, bucket, object, versio
171179
// the §2 no-such-configuration sentinel; an explicit OFF is &false (§4.1's
172180
// tri-valued hold).
173181
func (b *Backend) GetObjectLegalHold(ctx context.Context, bucket, object, versionId string) (*bool, error) {
174-
rv, err := b.resolveStateTarget(ctx, bucket, object, versionId, true)
182+
rv, err := b.resolveLockTarget(ctx, bucket, object, versionId)
175183
if err != nil {
176184
return nil, err
177185
}
@@ -186,12 +194,19 @@ func (b *Backend) GetObjectLegalHold(ctx context.Context, bucket, object, versio
186194
return &on, nil
187195
}
188196

197+
// mutateVersionLock runs a per-version state write for the lock methods:
198+
// mutateVersionState with the lock-enabled gate required. The tagging twin
199+
// is mutateVersionTags (objecttag.go).
200+
func (b *Backend) mutateVersionLock(ctx context.Context, bucketName, key, versionID string, mutate func(*msbucket.VersionState)) error {
201+
return b.mutateVersionState(ctx, bucketName, key, versionID, true, mutate)
202+
}
203+
189204
// PutObjectRetention stores the controller's retention document on the
190205
// resolved version. Mode-transition policy (same-mode replacement,
191206
// COMPLIANCE never weakened, governance bypass) ran in the controller before
192207
// this is called.
193208
func (b *Backend) PutObjectRetention(ctx context.Context, bucket, object, versionId string, retention []byte) error {
194-
return b.mutateVersionState(ctx, bucket, object, versionId, true, func(vs *msbucket.VersionState) {
209+
return b.mutateVersionLock(ctx, bucket, object, versionId, func(vs *msbucket.VersionState) {
195210
vs.Retention = retention
196211
})
197212
}
@@ -202,7 +217,7 @@ func (b *Backend) PutObjectLegalHold(ctx context.Context, bucket, object, versio
202217
if status {
203218
hold = msbucket.LegalHoldOn
204219
}
205-
return b.mutateVersionState(ctx, bucket, object, versionId, true, func(vs *msbucket.VersionState) {
220+
return b.mutateVersionLock(ctx, bucket, object, versionId, func(vs *msbucket.VersionState) {
206221
vs.LegalHold = hold
207222
})
208223
}
@@ -212,7 +227,9 @@ func (b *Backend) PutObjectLegalHold(ctx context.Context, bucket, object, versio
212227
// version's state block (mutate owns its fields and every other field is
213228
// carried, §4.1 rule 3), and the leaf/state-tree/top-MST splice. A
214229
// manifest-arm key upgrades to a leaf on its first state write (§4.1 rule 1);
215-
// an empty merged block is elided rather than stored. The check order runs
230+
// an empty merged block is elided rather than stored. Call through
231+
// mutateVersionLock / mutateVersionTags, which set the gate for their
232+
// operation family. The check order runs
216233
// entirely inside the commit (a missing bucket surfaces through
217234
// mapCommitError); key existence outranks lock-enabled, which outranks the
218235
// versionId grammar, matching posix and the pinning conformance cases.

s3frontend/objecttag.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,28 @@ import (
1111
// tenant beside object lock. The controller parses and validates the
1212
// PutObjectTagging XML (tag count, key/value limits) and hands us a clean
1313
// map; the x-amz-tagging creation-time header is stamped by the write paths
14-
// (§4). Tagging has no bucket gate, so the shared check order runs with
15-
// requireLock false, and absence is a success: a version without tags
16-
// answers the empty set, never a sentinel.
14+
// (§4). Tagging has no bucket gate — its wrappers below run the shared
15+
// check order without the lock-enabled step — and absence is a success: a
16+
// version without tags answers the empty set, never a sentinel.
1717

18-
// withTags merges a creation-time tag set into a version's initial state,
19-
// allocating the state when only tags are present (docs/s3-object-tagging.md
20-
// §4). Returns vs unchanged when there are no tags.
21-
func withTags(vs *msbucket.VersionState, tags map[string]string) *msbucket.VersionState {
18+
// resolveTagTarget resolves a tagging method's target: resolveStateTarget
19+
// with no bucket gate (docs/s3-object-tagging.md §2).
20+
func (b *Backend) resolveTagTarget(ctx context.Context, bucketName, key, versionID string) (*resolvedVersion, error) {
21+
return b.resolveStateTarget(ctx, bucketName, key, versionID, false)
22+
}
23+
24+
// mutateVersionTags runs a per-version state write for the tagging methods:
25+
// mutateVersionState with no bucket gate.
26+
func (b *Backend) mutateVersionTags(ctx context.Context, bucketName, key, versionID string, mutate func(*msbucket.VersionState)) error {
27+
return b.mutateVersionState(ctx, bucketName, key, versionID, false, mutate)
28+
}
29+
30+
// applyTagsIfPresent merges a creation-time tag set into a version's initial
31+
// state (docs/s3-object-tagging.md §4). When tags are present it MUTATES vs
32+
// in place — allocating it when nil — and returns it; when there are no tags
33+
// it returns vs untouched. Callers pass freshly built state, never a shared
34+
// value.
35+
func applyTagsIfPresent(vs *msbucket.VersionState, tags map[string]string) *msbucket.VersionState {
2236
if len(tags) == 0 {
2337
return vs
2438
}
@@ -32,7 +46,7 @@ func withTags(vs *msbucket.VersionState, tags map[string]string) *msbucket.Versi
3246
// GetObjectTagging returns the resolved version's tag set; the empty map
3347
// when it carries none (the controller renders an empty <TagSet/>).
3448
func (b *Backend) GetObjectTagging(ctx context.Context, bucket, object, versionId string) (map[string]string, error) {
35-
rv, err := b.resolveStateTarget(ctx, bucket, object, versionId, false)
49+
rv, err := b.resolveTagTarget(ctx, bucket, object, versionId)
3650
if err != nil {
3751
return nil, err
3852
}
@@ -54,15 +68,15 @@ func (b *Backend) PutObjectTagging(ctx context.Context, bucket, object, versionI
5468
if len(tags) == 0 {
5569
tags = nil
5670
}
57-
return b.mutateVersionState(ctx, bucket, object, versionId, false, func(vs *msbucket.VersionState) {
71+
return b.mutateVersionTags(ctx, bucket, object, versionId, func(vs *msbucket.VersionState) {
5872
vs.Tags = tags
5973
})
6074
}
6175

6276
// DeleteObjectTagging clears the resolved version's tag set. Idempotent: a
6377
// version without tags is a success no-op.
6478
func (b *Backend) DeleteObjectTagging(ctx context.Context, bucket, object, versionId string) error {
65-
return b.mutateVersionState(ctx, bucket, object, versionId, false, func(vs *msbucket.VersionState) {
79+
return b.mutateVersionTags(ctx, bucket, object, versionId, func(vs *msbucket.VersionState) {
6680
vs.Tags = nil
6781
})
6882
}

0 commit comments

Comments
 (0)