package object import ( "errors" "fmt" object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" objectgrpc "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object/grpc" refsgrpc "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs/grpc" sessiongrpc "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session/grpc" "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/util/slices" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" "github.com/golang/protobuf/proto" "google.golang.org/protobuf/encoding/protojson" ) // Object represents in-memory structure of the FrostFS objectgrpc. // 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 struct { object *objectgrpc.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 objectgrpc. 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(obj *objectgrpc.Object) *Object { objSDK := &Object{ object: new(objectgrpc.Object), } proto.Merge(objSDK.object, obj) return objSDK } // New creates and initializes blank Object. // // Works similar as NewFromV2(new(Object)). func New() *Object { return NewFromV2(new(objectgrpc.Object)) } // ToV2 converts Object to v2 Object message. func (o *Object) ToV2() *objectgrpc.Object { return o.object } // MarshalHeaderJSON marshals object's header // into JSON format. func (o *Object) MarshalHeaderJSON() ([]byte, error) { return protojson.MarshalOptions{ EmitUnpopulated: true, }.Marshal( o.object.GetHeader(), ) } func (o *Object) setHeaderField(setter func(*objectgrpc.Header)) { h := o.object.GetHeader() if h == nil { h = new(objectgrpc.Header) o.object.SetHeader(h) } setter(h) } func (o *Object) setSplitFields(setter func(*objectgrpc.Header_Split)) { o.setHeaderField(func(h *objectgrpc.Header) { split := h.GetSplit() if split == nil { split = new(objectgrpc.Header_Split) h.SetSplit(split) } setter(split) }) } // ID returns object identifier. func (o *Object) ID() (v oid.ID, isSet bool) { v2 := o.object 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 refsgrpc.ObjectID v.WriteToV2(&v2) o.object.SetObjectId(&v2) } // Signature returns signature of the object identifier. func (o *Object) Signature() *frostfscrypto.Signature { sigv2 := o.object.GetSignature() if sigv2 == nil { return nil } sig := frostfscrypto.NewSignature() _ = 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 *refsgrpc.Signature if v != nil { sigv2 = new(refsgrpc.Signature) v.WriteToV2(sigv2) } o.object.SetSignature(sigv2) } // Payload returns payload bytes. func (o *Object) Payload() []byte { return o.object.GetPayload() } // SetPayload sets payload bytes. func (o *Object) SetPayload(v []byte) { o.object.SetPayload(v) } // Version returns version of the objectgrpc. func (o *Object) Version() *version.Version { ver := version.NewVersion() if verV2 := o.object.GetHeader().GetVersion(); verV2 != nil { ver.ReadFromV2(verV2) } return &ver } // SetVersion sets version of the objectgrpc. func (o *Object) SetVersion(v version.Version) { var verV2 refsgrpc.Version v.WriteToV2(&verV2) o.setHeaderField(func(h *objectgrpc.Header) { h.SetVersion(&verV2) }) } // PayloadSize returns payload length of the objectgrpc. func (o *Object) PayloadSize() uint64 { return o.object. GetHeader(). GetPayloadLength() } // SetPayloadSize sets payload length of the objectgrpc. func (o *Object) SetPayloadSize(v uint64) { o.setHeaderField(func(h *objectgrpc.Header) { h.SetPayloadLength(v) }) } // ContainerID returns identifier of the related container. func (o *Object) ContainerID() (v cid.ID, isSet bool) { v2 := o.object 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 refsgrpc.ContainerID v.WriteToV2(&cidV2) o.setHeaderField(func(h *objectgrpc.Header) { h.SetContainerId(&cidV2) }) } func (o *Object) Equal(o1 *Object) bool { return proto.Equal(o.object, o1.object) } // OwnerID returns identifier of the object owner. func (o *Object) OwnerID() *user.ID { var id user.ID m := o.object.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 *objectgrpc.Header) { var m refsgrpc.OwnerID v.WriteToV2(&m) h.SetOwnerId(&m) }) } // CreationEpoch returns epoch number in which object was created. func (o *Object) CreationEpoch() uint64 { return o.object. GetHeader(). GetCreationEpoch() } // SetCreationEpoch sets epoch number in which object was created. func (o *Object) SetCreationEpoch(v uint64) { o.setHeaderField(func(h *objectgrpc.Header) { h.SetCreationEpoch(v) }) } // PayloadChecksum returns checksum of the object payload and // bool that indicates checksum presence in the objectgrpc. // // Zero Object does not have payload checksum. // // See also SetPayloadChecksum. func (o *Object) PayloadChecksum() (checksum.Checksum, bool) { v := checksum.NewChecksum() v2 := o.object 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 refsgrpc.Checksum v.WriteToV2(&v2) o.setHeaderField(func(h *objectgrpc.Header) { h.SetPayloadHash(&v2) }) } // PayloadHomomorphicHash returns homomorphic hash of the object // payload and bool that indicates checksum presence in the objectgrpc. // // Zero Object does not have payload homomorphic checksum. // // See also SetPayloadHomomorphicHash. func (o *Object) PayloadHomomorphicHash() (checksum.Checksum, bool) { v := checksum.NewChecksum() v2 := o.object 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 refsgrpc.Checksum v.WriteToV2(&v2) o.setHeaderField(func(h *objectgrpc.Header) { h.SetHomomorphicHash(&v2) }) } // Attributes returns object attributes. func (o *Object) Attributes() []Attribute { attrs := o.object. GetHeader(). GetAttributes() res := make([]Attribute, len(attrs)) for i := range attrs { res[i] = *NewAttributeFromV2(attrs[i]) } return res } // SetAttributes sets object attributes. func (o *Object) SetAttributes(v ...Attribute) { attrs := slices.MakePreallocPointerSlice[objectgrpc.Header_Attribute](len(v)) for i := range v { attrs[i] = v[i].ToV2() } o.setHeaderField(func(h *objectgrpc.Header) { h.SetAttributes(attrs) }) } // PreviousID returns identifier of the previous sibling objectgrpc. func (o *Object) PreviousID() (v oid.ID, isSet bool) { v2 := o.object v2Prev := v2.GetHeader().GetSplit().GetPrevious() if v2Prev != nil { _ = v.ReadFromV2(v2Prev) isSet = true } return } // SetPreviousID sets identifier of the previous sibling objectgrpc. func (o *Object) SetPreviousID(v oid.ID) { var v2 refsgrpc.ObjectID v.WriteToV2(&v2) o.setSplitFields(func(split *objectgrpc.Header_Split) { split.SetPrevious(&v2) }) } // Children return list of the identifiers of the child objects. func (o *Object) Children() []oid.ID { v2 := o.object 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 } // SetChildren sets list of the identifiers of the child objects. func (o *Object) SetChildren(v ...oid.ID) { ids := make([]*refsgrpc.ObjectID, len(v)) for i := range v { var v2 refsgrpc.ObjectID v[i].WriteToV2(&v2) ids[i] = &v2 } o.setSplitFields(func(split *objectgrpc.Header_Split) { split.SetChildren(ids) }) } // NotificationInfo groups information about object notification // that can be written to objectgrpc. // // 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(o.object) 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(o.object, ni.ni) } // SplitID return split identity of split objectgrpc. If object is not split // returns nil. func (o *Object) SplitID() *SplitID { return NewSplitIDFromV2( o.object. GetHeader(). GetSplit(). GetSplitId(), ) } // SetSplitID sets split identifier for the split objectgrpc. func (o *Object) SetSplitID(id *SplitID) { o.setSplitFields(func(split *objectgrpc.Header_Split) { split.SetSplitId(id.ToV2()) }) } // ParentID returns identifier of the parent objectgrpc. func (o *Object) ParentID() (v oid.ID, isSet bool) { v2 := o.object v2Par := v2.GetHeader().GetSplit().GetParent() if v2Par != nil { _ = v.ReadFromV2(v2Par) isSet = true } return } // SetParentID sets identifier of the parent objectgrpc. func (o *Object) SetParentID(v oid.ID) { var v2 refsgrpc.ObjectID v.WriteToV2(&v2) o.setSplitFields(func(split *objectgrpc.Header_Split) { split.SetParent(&v2) }) } // Parent returns parent object w/o payload. func (o *Object) Parent() *Object { h := o.object. GetHeader(). GetSplit() parSig := h.GetParentSignature() parHdr := h.GetParentHeader() if parSig == nil && parHdr == nil { return nil } oV2 := new(objectgrpc.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 *objectgrpc.Header_Split) { split.SetParent(v.object.GetObjectId()) split.SetParentSignature(v.object.GetSignature()) split.SetParentHeader(v.object.GetHeader()) }) } func (o *Object) initRelations() { o.setHeaderField(func(h *objectgrpc.Header) { h.SetSplit(new(objectgrpc.Header_Split)) }) } func (o *Object) resetRelations() { o.setHeaderField(func(h *objectgrpc.Header) { h.SetSplit(nil) }) } // SessionToken returns token of the session // within which object was created. func (o *Object) SessionToken() *session.Object { tokv2 := o.object.GetHeader().GetSessionToken() if tokv2 == nil { return nil } res := session.NewObject() _ = 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 *objectgrpc.Header) { var tokv2 *sessiongrpc.SessionToken if v != nil { tokv2 = new(sessiongrpc.SessionToken) v.WriteToV2(tokv2) } h.SetSessionToken(tokv2) }) } // Type returns type of the object. func (o *Object) Type() Type { return TypeFromV2( o.object. GetHeader(). GetObjectType(), ) } // SetType sets type of the objectgrpc. func (o *Object) SetType(v Type) { o.setHeaderField(func(h *objectgrpc.Header) { h.SetObjectType(v.ToV2()) }) } // CutPayload returns Object with empty payload. func (o *Object) CutPayload() *Object { ov2 := New() proto.Merge(ov2.object, o.object) ov2.object.SetPayload(nil) return ov2 } func (o *Object) HasParent() bool { return o.object. 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 o.object.StableMarshal(nil), nil } // Unmarshal unmarshals protobuf binary representation of object. func (o *Object) Unmarshal(data []byte) error { if o.object == nil { o.object = new(objectgrpc.Object) } err := proto.Unmarshal(data, o.object) if err != nil { return err } return formatCheck(o.object) } // MarshalJSON encodes object to protobuf JSON format. func (o *Object) MarshalJSON() ([]byte, error) { return protojson.MarshalOptions{ EmitUnpopulated: true, }.Marshal( o.object, ) } // UnmarshalJSON decodes object from protobuf JSON format. func (o *Object) UnmarshalJSON(data []byte) error { err := protojson.Unmarshal(data, o.object) if err != nil { return err } return formatCheck(o.object) } var errOIDNotSet = errors.New("object ID is not set") var errCIDNotSet = errors.New("container ID is not set") func formatCheck(v2 *objectgrpc.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 }