package object import ( "errors" "fmt" "slices" "strings" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs" v2session "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" ) // Object represents in-memory structure of the FrostFS object. // Type is compatible with FrostFS API V2 protocol. // // Instance can be created depending on scenario: // - InitCreation (an object to be placed in container); // - New (blank instance, usually needed for decoding); // - NewFromV2 (when working under FrostFS API V2 protocol). type Object object.Object // RequiredFields contains the minimum set of object data that must be set // by the FrostFS user at the stage of creation. type RequiredFields struct { // Identifier of the FrostFS container associated with the object. Container cid.ID // Object owner's user ID in the FrostFS system. Owner user.ID } // InitCreation initializes the object instance with minimum set of required fields. // Object is expected (but not required) to be blank. Object must not be nil. func InitCreation(dst *Object, rf RequiredFields) { dst.SetContainerID(rf.Container) dst.SetOwnerID(rf.Owner) } // NewFromV2 wraps v2 Object message to Object. func NewFromV2(oV2 *object.Object) *Object { return (*Object)(oV2) } // New creates and initializes blank Object. // // Works similar as NewFromV2(new(Object)). func New() *Object { return NewFromV2(new(object.Object)) } // ToV2 converts Object to v2 Object message. func (o *Object) ToV2() *object.Object { return (*object.Object)(o) } // MarshalHeaderJSON marshals object's header // into JSON format. func (o *Object) MarshalHeaderJSON() ([]byte, error) { return (*object.Object)(o).GetHeader().MarshalJSON() } func (o *Object) setHeaderField(setter func(*object.Header)) { obj := (*object.Object)(o) h := obj.GetHeader() if h == nil { h = new(object.Header) obj.SetHeader(h) } setter(h) } func (o *Object) setSplitFields(setter func(*object.SplitHeader)) { o.setHeaderField(func(h *object.Header) { split := h.GetSplit() if split == nil { split = new(object.SplitHeader) h.SetSplit(split) } setter(split) }) } // ID returns object identifier. func (o *Object) ID() (v oid.ID, isSet bool) { v2 := (*object.Object)(o) if id := v2.GetObjectID(); id != nil { _ = v.ReadFromV2(*v2.GetObjectID()) isSet = true } return } // SetID sets object identifier. func (o *Object) SetID(v oid.ID) { var v2 refs.ObjectID v.WriteToV2(&v2) (*object.Object)(o). SetObjectID(&v2) } // Signature returns signature of the object identifier. func (o *Object) Signature() *frostfscrypto.Signature { sigv2 := (*object.Object)(o).GetSignature() if sigv2 == nil { return nil } var sig frostfscrypto.Signature _ = sig.ReadFromV2(*sigv2) // FIXME(@cthulhu-rider): #226 handle error return &sig } // SetSignature sets signature of the object identifier. func (o *Object) SetSignature(v *frostfscrypto.Signature) { var sigv2 *refs.Signature if v != nil { sigv2 = new(refs.Signature) v.WriteToV2(sigv2) } (*object.Object)(o).SetSignature(sigv2) } // Payload returns payload bytes. func (o *Object) Payload() []byte { return (*object.Object)(o).GetPayload() } // SetPayload sets payload bytes. func (o *Object) SetPayload(v []byte) { (*object.Object)(o).SetPayload(v) } // Version returns version of the object. func (o *Object) Version() *version.Version { var ver version.Version if verV2 := (*object.Object)(o).GetHeader().GetVersion(); verV2 != nil { _ = ver.ReadFromV2(*verV2) // FIXME(@cthulhu-rider): #226 handle error } return &ver } // SetVersion sets version of the object. func (o *Object) SetVersion(v *version.Version) { var verV2 refs.Version v.WriteToV2(&verV2) o.setHeaderField(func(h *object.Header) { h.SetVersion(&verV2) }) } // PayloadSize returns payload length of the object. func (o *Object) PayloadSize() uint64 { return (*object.Object)(o). GetHeader(). GetPayloadLength() } // SetPayloadSize sets payload length of the object. func (o *Object) SetPayloadSize(v uint64) { o.setHeaderField(func(h *object.Header) { h.SetPayloadLength(v) }) } // ContainerID returns identifier of the related container. func (o *Object) ContainerID() (v cid.ID, isSet bool) { v2 := (*object.Object)(o) cidV2 := v2.GetHeader().GetContainerID() if cidV2 != nil { _ = v.ReadFromV2(*cidV2) isSet = true } return } // SetContainerID sets identifier of the related container. func (o *Object) SetContainerID(v cid.ID) { var cidV2 refs.ContainerID v.WriteToV2(&cidV2) o.setHeaderField(func(h *object.Header) { h.SetContainerID(&cidV2) }) } // OwnerID returns identifier of the object owner and True. func (o *Object) OwnerID() user.ID { var id user.ID m := (*object.Object)(o).GetHeader().GetOwnerID() if m != nil { _ = id.ReadFromV2(*m) } return id } // SetOwnerID sets identifier of the object owner. func (o *Object) SetOwnerID(v user.ID) { o.setHeaderField(func(h *object.Header) { var m refs.OwnerID v.WriteToV2(&m) h.SetOwnerID(&m) }) } // CreationEpoch returns epoch number in which object was created. func (o *Object) CreationEpoch() uint64 { return (*object.Object)(o). GetHeader(). GetCreationEpoch() } // SetCreationEpoch sets epoch number in which object was created. func (o *Object) SetCreationEpoch(v uint64) { o.setHeaderField(func(h *object.Header) { h.SetCreationEpoch(v) }) } // PayloadChecksum returns checksum of the object payload and // bool that indicates checksum presence in the object. // // Zero Object does not have payload checksum. // // See also SetPayloadChecksum. func (o *Object) PayloadChecksum() (checksum.Checksum, bool) { var v checksum.Checksum v2 := (*object.Object)(o) if hash := v2.GetHeader().GetPayloadHash(); hash != nil { _ = v.ReadFromV2(*hash) // FIXME(@cthulhu-rider): #226 handle error return v, true } return v, false } // SetPayloadChecksum sets checksum of the object payload. // // See also PayloadChecksum. func (o *Object) SetPayloadChecksum(v checksum.Checksum) { var v2 refs.Checksum v.WriteToV2(&v2) o.setHeaderField(func(h *object.Header) { h.SetPayloadHash(&v2) }) } // PayloadHomomorphicHash returns homomorphic hash of the object // payload and bool that indicates checksum presence in the object. // // Zero Object does not have payload homomorphic checksum. // // See also SetPayloadHomomorphicHash. func (o *Object) PayloadHomomorphicHash() (checksum.Checksum, bool) { var v checksum.Checksum v2 := (*object.Object)(o) if hash := v2.GetHeader().GetHomomorphicHash(); hash != nil { _ = v.ReadFromV2(*hash) // FIXME(@cthulhu-rider): #226 handle error return v, true } return v, false } // SetPayloadHomomorphicHash sets homomorphic hash of the object payload. // // See also PayloadHomomorphicHash. func (o *Object) SetPayloadHomomorphicHash(v checksum.Checksum) { var v2 refs.Checksum v.WriteToV2(&v2) o.setHeaderField(func(h *object.Header) { h.SetHomomorphicHash(&v2) }) } // Attributes returns object attributes. func (o *Object) Attributes() []Attribute { attrs := (*object.Object)(o). GetHeader(). GetAttributes() res := make([]Attribute, len(attrs)) for i := range attrs { res[i] = *NewAttributeFromV2(&attrs[i]) } return res } // UserAttributes returns object user attributes. func (o *Object) UserAttributes() []Attribute { attrs := (*object.Object)(o). GetHeader(). GetAttributes() res := make([]Attribute, 0, len(attrs)) for _, attr := range attrs { if !strings.HasPrefix(attr.GetKey(), container.SysAttributePrefix) { res = append(res, *NewAttributeFromV2(&attr)) } } return slices.Clip(res) } // SetAttributes sets object attributes. func (o *Object) SetAttributes(v ...Attribute) { attrs := make([]object.Attribute, len(v)) for i := range v { attrs[i] = *v[i].ToV2() } o.setHeaderField(func(h *object.Header) { h.SetAttributes(attrs) }) } // PreviousID returns identifier of the previous sibling object. func (o *Object) PreviousID() (v oid.ID, isSet bool) { v2 := (*object.Object)(o) v2Prev := v2.GetHeader().GetSplit().GetPrevious() if v2Prev != nil { _ = v.ReadFromV2(*v2Prev) isSet = true } return } // SetPreviousID sets identifier of the previous sibling object. func (o *Object) SetPreviousID(v oid.ID) { var v2 refs.ObjectID v.WriteToV2(&v2) o.setSplitFields(func(split *object.SplitHeader) { split.SetPrevious(&v2) }) } // Children return list of the identifiers of the child objects. func (o *Object) Children() []oid.ID { v2 := (*object.Object)(o) ids := v2.GetHeader().GetSplit().GetChildren() var ( id oid.ID res = make([]oid.ID, len(ids)) ) for i := range ids { _ = id.ReadFromV2(ids[i]) res[i] = id } return res } func (o *Object) GetECHeader() *ECHeader { v2 := (*object.Object)(o).GetHeader().GetEC() var ec ECHeader _ = ec.ReadFromV2(v2) // Errors is checked on unmarshal. return &ec } // SetChildren sets list of the identifiers of the child objects. func (o *Object) SetChildren(v ...oid.ID) { var ( v2 refs.ObjectID ids = make([]refs.ObjectID, len(v)) ) for i := range v { v[i].WriteToV2(&v2) ids[i] = v2 } o.setSplitFields(func(split *object.SplitHeader) { split.SetChildren(ids) }) } // NotificationInfo groups information about object notification // that can be written to object. // // Topic is an optional field. type NotificationInfo struct { ni object.NotificationInfo } // Epoch returns object notification tick // epoch. func (n NotificationInfo) Epoch() uint64 { return n.ni.Epoch() } // SetEpoch sets object notification tick // epoch. func (n *NotificationInfo) SetEpoch(epoch uint64) { n.ni.SetEpoch(epoch) } // Topic return optional object notification // topic. func (n NotificationInfo) Topic() string { return n.ni.Topic() } // SetTopic sets optional object notification // topic. func (n *NotificationInfo) SetTopic(topic string) { n.ni.SetTopic(topic) } // NotificationInfo returns notification info // read from the object structure. // Returns any error that appeared during notification // information parsing. func (o *Object) NotificationInfo() (*NotificationInfo, error) { ni, err := object.GetNotificationInfo((*object.Object)(o)) if err != nil { return nil, err } return &NotificationInfo{ ni: *ni, }, nil } // SetNotification writes NotificationInfo to the object structure. func (o *Object) SetNotification(ni NotificationInfo) { object.WriteNotificationInfo((*object.Object)(o), ni.ni) } // SplitID return split identity of split object. If object is not split // returns nil. func (o *Object) SplitID() *SplitID { return NewSplitIDFromV2( (*object.Object)(o). GetHeader(). GetSplit(). GetSplitID(), ) } // SetSplitID sets split identifier for the split object. func (o *Object) SetSplitID(id *SplitID) { o.setSplitFields(func(split *object.SplitHeader) { split.SetSplitID(id.ToV2()) }) } // ParentID returns identifier of the parent object. func (o *Object) ParentID() (v oid.ID, isSet bool) { v2 := (*object.Object)(o) v2Par := v2.GetHeader().GetSplit().GetParent() if v2Par != nil { _ = v.ReadFromV2(*v2Par) isSet = true } return } // SetParentID sets identifier of the parent object. func (o *Object) SetParentID(v oid.ID) { var v2 refs.ObjectID v.WriteToV2(&v2) o.setSplitFields(func(split *object.SplitHeader) { split.SetParent(&v2) }) } // Parent returns parent object w/o payload. func (o *Object) Parent() *Object { h := (*object.Object)(o). GetHeader(). GetSplit() parSig := h.GetParentSignature() parHdr := h.GetParentHeader() if parSig == nil && parHdr == nil { return nil } oV2 := new(object.Object) oV2.SetObjectID(h.GetParent()) oV2.SetSignature(parSig) oV2.SetHeader(parHdr) return NewFromV2(oV2) } // SetParent sets parent object w/o payload. func (o *Object) SetParent(v *Object) { o.setSplitFields(func(split *object.SplitHeader) { split.SetParent((*object.Object)(v).GetObjectID()) split.SetParentSignature((*object.Object)(v).GetSignature()) split.SetParentHeader((*object.Object)(v).GetHeader()) }) } func (o *Object) initRelations() { o.setHeaderField(func(h *object.Header) { h.SetSplit(new(object.SplitHeader)) }) } func (o *Object) resetRelations() { o.setHeaderField(func(h *object.Header) { h.SetSplit(nil) }) } // SessionToken returns token of the session // within which object was created. func (o *Object) SessionToken() *session.Object { tokv2 := (*object.Object)(o).GetHeader().GetSessionToken() if tokv2 == nil { return nil } var res session.Object _ = res.ReadFromV2(*tokv2) return &res } // SetSessionToken sets token of the session // within which object was created. func (o *Object) SetSessionToken(v *session.Object) { o.setHeaderField(func(h *object.Header) { var tokv2 *v2session.Token if v != nil { tokv2 = new(v2session.Token) v.WriteToV2(tokv2) } h.SetSessionToken(tokv2) }) } // Type returns type of the object. func (o *Object) Type() Type { return TypeFromV2( (*object.Object)(o). GetHeader(). GetObjectType(), ) } // SetType sets type of the object. func (o *Object) SetType(v Type) { o.setHeaderField(func(h *object.Header) { h.SetObjectType(v.ToV2()) }) } // CutPayload returns Object w/ empty payload. // // Changes of non-payload fields affect source object. func (o *Object) CutPayload() *Object { ov2 := new(object.Object) *ov2 = *(*object.Object)(o) ov2.SetPayload(nil) ov2.SetMarshalData(nil) return (*Object)(ov2) } func (o *Object) HasParent() bool { return (*object.Object)(o). GetHeader(). GetSplit() != nil } // ResetRelations removes all fields of links with other objects. func (o *Object) ResetRelations() { o.resetRelations() } // InitRelations initializes relation field. func (o *Object) InitRelations() { o.initRelations() } // Marshal marshals object into a protobuf binary form. func (o *Object) Marshal() ([]byte, error) { return (*object.Object)(o).StableMarshal(nil), nil } // Unmarshal unmarshals protobuf binary representation of object. func (o *Object) Unmarshal(data []byte) error { err := (*object.Object)(o).Unmarshal(data) if err != nil { return err } return formatCheck((*object.Object)(o)) } // MarshalJSON encodes object to protobuf JSON format. func (o *Object) MarshalJSON() ([]byte, error) { return (*object.Object)(o).MarshalJSON() } // UnmarshalJSON decodes object from protobuf JSON format. func (o *Object) UnmarshalJSON(data []byte) error { err := (*object.Object)(o).UnmarshalJSON(data) if err != nil { return err } return formatCheck((*object.Object)(o)) } var ( errOIDNotSet = errors.New("object ID is not set") errCIDNotSet = errors.New("container ID is not set") ) func formatCheck(v2 *object.Object) error { var ( oID oid.ID cID cid.ID ) oidV2 := v2.GetObjectID() if oidV2 == nil { return errOIDNotSet } err := oID.ReadFromV2(*oidV2) if err != nil { return fmt.Errorf("could not convert V2 object ID: %w", err) } cidV2 := v2.GetHeader().GetContainerID() if cidV2 == nil { return errCIDNotSet } err = cID.ReadFromV2(*cidV2) if err != nil { return fmt.Errorf("could not convert V2 container ID: %w", err) } if prev := v2.GetHeader().GetSplit().GetPrevious(); prev != nil { err = oID.ReadFromV2(*prev) if err != nil { return fmt.Errorf("could not convert previous object ID: %w", err) } } if parent := v2.GetHeader().GetSplit().GetParent(); parent != nil { err = oID.ReadFromV2(*parent) if err != nil { return fmt.Errorf("could not convert parent object ID: %w", err) } } return nil }