s3api: extend lifecycle XML types with NoncurrentVersionExpiration, AbortIncompleteMultipartUpload

Add missing S3 lifecycle rule types to the XML data model:
- NoncurrentVersionExpiration with NoncurrentDays and NewerNoncurrentVersions
- NoncurrentVersionTransition with NoncurrentDays and StorageClass
- AbortIncompleteMultipartUpload with DaysAfterInitiation
- Filter.ObjectSizeGreaterThan and ObjectSizeLessThan
- And.ObjectSizeGreaterThan and ObjectSizeLessThan
- Filter.UnmarshalXML to properly parse Tag, And, and size filter elements

Each new type follows the existing set-field pattern for conditional
XML marshaling. No behavior changes - these types are not yet wired
into handlers or the lifecycle worker.
This commit is contained in:
Copilot
2026-03-27 23:43:23 -07:00
parent 7d5cbfd547
commit 0a4ec5ba15
+170 -12
View File
@@ -22,13 +22,16 @@ type Lifecycle struct {
// Rule - a rule for lifecycle configuration.
type Rule struct {
XMLName xml.Name `xml:"Rule"`
ID string `xml:"ID,omitempty"`
Status ruleStatus `xml:"Status"`
Filter Filter `xml:"Filter,omitempty"`
Prefix Prefix `xml:"Prefix,omitempty"`
Expiration Expiration `xml:"Expiration,omitempty"`
Transition Transition `xml:"Transition,omitempty"`
XMLName xml.Name `xml:"Rule"`
ID string `xml:"ID,omitempty"`
Status ruleStatus `xml:"Status"`
Filter Filter `xml:"Filter,omitempty"`
Prefix Prefix `xml:"Prefix,omitempty"`
Expiration Expiration `xml:"Expiration,omitempty"`
Transition Transition `xml:"Transition,omitempty"`
NoncurrentVersionExpiration NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration,omitempty"`
NoncurrentVersionTransition NoncurrentVersionTransition `xml:"NoncurrentVersionTransition,omitempty"`
AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty"`
}
// Filter - a filter for a lifecycle configuration Rule.
@@ -43,6 +46,9 @@ type Filter struct {
Tag Tag
tagSet bool
ObjectSizeGreaterThan int64
ObjectSizeLessThan int64
}
// Prefix holds the prefix xml tag in <Rule> and <Filter>
@@ -80,17 +86,83 @@ func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if err := e.EncodeToken(start); err != nil {
return err
}
if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil {
return err
if f.andSet {
if err := e.EncodeElement(f.And, xml.StartElement{Name: xml.Name{Local: "And"}}); err != nil {
return err
}
} else if f.tagSet {
if err := e.EncodeElement(f.Tag, xml.StartElement{Name: xml.Name{Local: "Tag"}}); err != nil {
return err
}
} else {
if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil {
return err
}
}
if f.ObjectSizeGreaterThan > 0 {
if err := e.EncodeElement(f.ObjectSizeGreaterThan, xml.StartElement{Name: xml.Name{Local: "ObjectSizeGreaterThan"}}); err != nil {
return err
}
}
if f.ObjectSizeLessThan > 0 {
if err := e.EncodeElement(f.ObjectSizeLessThan, xml.StartElement{Name: xml.Name{Local: "ObjectSizeLessThan"}}); err != nil {
return err
}
}
return e.EncodeToken(xml.EndElement{Name: start.Name})
}
// UnmarshalXML decodes Filter from XML, handling all child elements.
func (f *Filter) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
f.set = true
for {
tok, err := d.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case xml.StartElement:
switch t.Name.Local {
case "Prefix":
if err := d.DecodeElement(&f.Prefix, &t); err != nil {
return err
}
case "Tag":
f.tagSet = true
if err := d.DecodeElement(&f.Tag, &t); err != nil {
return err
}
case "And":
f.andSet = true
if err := d.DecodeElement(&f.And, &t); err != nil {
return err
}
case "ObjectSizeGreaterThan":
if err := d.DecodeElement(&f.ObjectSizeGreaterThan, &t); err != nil {
return err
}
case "ObjectSizeLessThan":
if err := d.DecodeElement(&f.ObjectSizeLessThan, &t); err != nil {
return err
}
default:
if err := d.Skip(); err != nil {
return err
}
}
case xml.EndElement:
return nil
}
}
}
// And - a tag to combine a prefix and multiple tags for lifecycle configuration rule.
type And struct {
XMLName xml.Name `xml:"And"`
Prefix Prefix `xml:"Prefix,omitempty"`
Tags []Tag `xml:"Tag,omitempty"`
XMLName xml.Name `xml:"And"`
Prefix Prefix `xml:"Prefix,omitempty"`
Tags []Tag `xml:"Tag,omitempty"`
ObjectSizeGreaterThan int64 `xml:"ObjectSizeGreaterThan,omitempty"`
ObjectSizeLessThan int64 `xml:"ObjectSizeLessThan,omitempty"`
}
// Expiration - expiration actions for a rule in lifecycle configuration.
@@ -162,3 +234,89 @@ func (t Transition) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
// TransitionDays is a type alias to unmarshal Days in Transition
type TransitionDays int
// NoncurrentVersionExpiration - expiration actions for non-current object versions.
type NoncurrentVersionExpiration struct {
XMLName xml.Name `xml:"NoncurrentVersionExpiration"`
NoncurrentDays int `xml:"NoncurrentDays,omitempty"`
NewerNoncurrentVersions int `xml:"NewerNoncurrentVersions,omitempty"`
set bool
}
// MarshalXML encodes NoncurrentVersionExpiration field into an XML form.
func (n NoncurrentVersionExpiration) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !n.set {
return nil
}
type wrapper NoncurrentVersionExpiration
return enc.EncodeElement(wrapper(n), start)
}
func (n *NoncurrentVersionExpiration) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type wrapper NoncurrentVersionExpiration
var w wrapper
if err := d.DecodeElement(&w, &start); err != nil {
return err
}
*n = NoncurrentVersionExpiration(w)
n.set = true
return nil
}
// NoncurrentVersionTransition - transition actions for non-current object versions.
type NoncurrentVersionTransition struct {
XMLName xml.Name `xml:"NoncurrentVersionTransition"`
NoncurrentDays int `xml:"NoncurrentDays,omitempty"`
StorageClass string `xml:"StorageClass,omitempty"`
set bool
}
// MarshalXML encodes NoncurrentVersionTransition field into an XML form.
func (n NoncurrentVersionTransition) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !n.set {
return nil
}
type wrapper NoncurrentVersionTransition
return enc.EncodeElement(wrapper(n), start)
}
func (n *NoncurrentVersionTransition) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type wrapper NoncurrentVersionTransition
var w wrapper
if err := d.DecodeElement(&w, &start); err != nil {
return err
}
*n = NoncurrentVersionTransition(w)
n.set = true
return nil
}
// AbortIncompleteMultipartUpload - abort action for incomplete multipart uploads.
type AbortIncompleteMultipartUpload struct {
XMLName xml.Name `xml:"AbortIncompleteMultipartUpload"`
DaysAfterInitiation int `xml:"DaysAfterInitiation,omitempty"`
set bool
}
// MarshalXML encodes AbortIncompleteMultipartUpload field into an XML form.
func (a AbortIncompleteMultipartUpload) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !a.set {
return nil
}
type wrapper AbortIncompleteMultipartUpload
return enc.EncodeElement(wrapper(a), start)
}
func (a *AbortIncompleteMultipartUpload) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type wrapper AbortIncompleteMultipartUpload
var w wrapper
if err := d.DecodeElement(&w, &start); err != nil {
return err
}
*a = AbortIncompleteMultipartUpload(w)
a.set = true
return nil
}