[#64] object: move package from neofs-api-go

Also, remove deprecated method.

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2021-11-08 13:04:45 +03:00 committed by Alex Vanin
parent bdb99877f6
commit 39d3317ef6
28 changed files with 3268 additions and 0 deletions

117
object/address.go Normal file
View file

@ -0,0 +1,117 @@
package object
import (
"errors"
"strings"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
// Address represents v2-compatible object address.
type Address refs.Address
var errInvalidAddressString = errors.New("incorrect format of the string object address")
const (
addressParts = 2
addressSeparator = "/"
)
// NewAddressFromV2 converts v2 Address message to Address.
//
// Nil refs.Address converts to nil.
func NewAddressFromV2(aV2 *refs.Address) *Address {
return (*Address)(aV2)
}
// NewAddress creates and initializes blank Address.
//
// Works similar as NewAddressFromV2(new(Address)).
//
// Defaults:
// - cid: nil;
// - oid: nil.
func NewAddress() *Address {
return NewAddressFromV2(new(refs.Address))
}
// ToV2 converts Address to v2 Address message.
//
// Nil Address converts to nil.
func (a *Address) ToV2() *refs.Address {
return (*refs.Address)(a)
}
// ContainerID returns container identifier.
func (a *Address) ContainerID() *cid.ID {
return cid.NewFromV2(
(*refs.Address)(a).GetContainerID())
}
// SetContainerID sets container identifier.
func (a *Address) SetContainerID(id *cid.ID) {
(*refs.Address)(a).SetContainerID(id.ToV2())
}
// ObjectID returns object identifier.
func (a *Address) ObjectID() *ID {
return NewIDFromV2(
(*refs.Address)(a).GetObjectID())
}
// SetObjectID sets object identifier.
func (a *Address) SetObjectID(id *ID) {
(*refs.Address)(a).SetObjectID(id.ToV2())
}
// Parse converts base58 string representation into Address.
func (a *Address) Parse(s string) error {
var (
err error
oid = NewID()
id = cid.New()
parts = strings.Split(s, addressSeparator)
)
if len(parts) != addressParts {
return errInvalidAddressString
} else if err = id.Parse(parts[0]); err != nil {
return err
} else if err = oid.Parse(parts[1]); err != nil {
return err
}
a.SetObjectID(oid)
a.SetContainerID(id)
return nil
}
// String returns string representation of Object.Address.
func (a *Address) String() string {
return strings.Join([]string{
a.ContainerID().String(),
a.ObjectID().String(),
}, addressSeparator)
}
// Marshal marshals Address into a protobuf binary form.
func (a *Address) Marshal() ([]byte, error) {
return (*refs.Address)(a).StableMarshal(nil)
}
// Unmarshal unmarshals protobuf binary representation of Address.
func (a *Address) Unmarshal(data []byte) error {
return (*refs.Address)(a).Unmarshal(data)
}
// MarshalJSON encodes Address to protobuf JSON format.
func (a *Address) MarshalJSON() ([]byte, error) {
return (*refs.Address)(a).MarshalJSON()
}
// UnmarshalJSON decodes Address from protobuf JSON format.
func (a *Address) UnmarshalJSON(data []byte) error {
return (*refs.Address)(a).UnmarshalJSON(data)
}

119
object/address_test.go Normal file
View file

@ -0,0 +1,119 @@
package object
import (
"strings"
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/stretchr/testify/require"
)
func TestAddress_SetContainerID(t *testing.T) {
a := NewAddress()
id := cidtest.GenerateID()
a.SetContainerID(id)
require.Equal(t, id, a.ContainerID())
}
func TestAddress_SetObjectID(t *testing.T) {
a := NewAddress()
oid := randID(t)
a.SetObjectID(oid)
require.Equal(t, oid, a.ObjectID())
}
func TestAddress_Parse(t *testing.T) {
cid := cidtest.GenerateID()
oid := NewID()
oid.SetSHA256(randSHA256Checksum(t))
t.Run("should parse successful", func(t *testing.T) {
s := strings.Join([]string{cid.String(), oid.String()}, addressSeparator)
a := NewAddress()
require.NoError(t, a.Parse(s))
require.Equal(t, oid, a.ObjectID())
require.Equal(t, cid, a.ContainerID())
})
t.Run("should fail for bad address", func(t *testing.T) {
s := strings.Join([]string{cid.String()}, addressSeparator)
require.EqualError(t, NewAddress().Parse(s), errInvalidAddressString.Error())
})
t.Run("should fail on container.ID", func(t *testing.T) {
s := strings.Join([]string{"1", "2"}, addressSeparator)
require.Error(t, NewAddress().Parse(s))
})
t.Run("should fail on object.ID", func(t *testing.T) {
s := strings.Join([]string{cid.String(), "2"}, addressSeparator)
require.Error(t, NewAddress().Parse(s))
})
}
func TestAddressEncoding(t *testing.T) {
a := NewAddress()
a.SetObjectID(randID(t))
a.SetContainerID(cidtest.GenerateID())
t.Run("binary", func(t *testing.T) {
data, err := a.Marshal()
require.NoError(t, err)
a2 := NewAddress()
require.NoError(t, a2.Unmarshal(data))
require.Equal(t, a, a2)
})
t.Run("json", func(t *testing.T) {
data, err := a.MarshalJSON()
require.NoError(t, err)
a2 := NewAddress()
require.NoError(t, a2.UnmarshalJSON(data))
require.Equal(t, a, a2)
})
}
func TestNewAddressFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *refs.Address
require.Nil(t, NewAddressFromV2(x))
})
}
func TestAddress_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *Address
require.Nil(t, x.ToV2())
})
}
func TestNewAddress(t *testing.T) {
t.Run("default values", func(t *testing.T) {
a := NewAddress()
// check initial values
require.Nil(t, a.ContainerID())
require.Nil(t, a.ObjectID())
// convert to v2 message
aV2 := a.ToV2()
require.Nil(t, aV2.GetContainerID())
require.Nil(t, aV2.GetObjectID())
})
}

73
object/attribute.go Normal file
View file

@ -0,0 +1,73 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/object"
)
// Attribute represents v2-compatible object attribute.
type Attribute object.Attribute
// NewAttributeFromV2 wraps v2 Attribute message to Attribute.
//
// Nil object.Attribute converts to nil.
func NewAttributeFromV2(aV2 *object.Attribute) *Attribute {
return (*Attribute)(aV2)
}
// NewAttribute creates and initializes blank Attribute.
//
// Works similar as NewAttributeFromV2(new(Attribute)).
//
// Defaults:
// - key: "";
// - value: "".
func NewAttribute() *Attribute {
return NewAttributeFromV2(new(object.Attribute))
}
// Key returns key to the object attribute.
func (a *Attribute) Key() string {
return (*object.Attribute)(a).GetKey()
}
// SetKey sets key to the object attribute.
func (a *Attribute) SetKey(v string) {
(*object.Attribute)(a).SetKey(v)
}
// Value return value of the object attribute.
func (a *Attribute) Value() string {
return (*object.Attribute)(a).GetValue()
}
// SetValue sets value of the object attribute.
func (a *Attribute) SetValue(v string) {
(*object.Attribute)(a).SetValue(v)
}
// ToV2 converts Attribute to v2 Attribute message.
//
// Nil Attribute converts to nil.
func (a *Attribute) ToV2() *object.Attribute {
return (*object.Attribute)(a)
}
// Marshal marshals Attribute into a protobuf binary form.
func (a *Attribute) Marshal() ([]byte, error) {
return (*object.Attribute)(a).StableMarshal(nil)
}
// Unmarshal unmarshals protobuf binary representation of Attribute.
func (a *Attribute) Unmarshal(data []byte) error {
return (*object.Attribute)(a).Unmarshal(data)
}
// MarshalJSON encodes Attribute to protobuf JSON format.
func (a *Attribute) MarshalJSON() ([]byte, error) {
return (*object.Attribute)(a).MarshalJSON()
}
// UnmarshalJSON decodes Attribute from protobuf JSON format.
func (a *Attribute) UnmarshalJSON(data []byte) error {
return (*object.Attribute)(a).UnmarshalJSON(data)
}

82
object/attribute_test.go Normal file
View file

@ -0,0 +1,82 @@
package object
import (
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/stretchr/testify/require"
)
func TestAttribute(t *testing.T) {
key, val := "some key", "some value"
a := NewAttribute()
a.SetKey(key)
a.SetValue(val)
require.Equal(t, key, a.Key())
require.Equal(t, val, a.Value())
aV2 := a.ToV2()
require.Equal(t, key, aV2.GetKey())
require.Equal(t, val, aV2.GetValue())
}
func TestAttributeEncoding(t *testing.T) {
a := NewAttribute()
a.SetKey("key")
a.SetValue("value")
t.Run("binary", func(t *testing.T) {
data, err := a.Marshal()
require.NoError(t, err)
a2 := NewAttribute()
require.NoError(t, a2.Unmarshal(data))
require.Equal(t, a, a2)
})
t.Run("json", func(t *testing.T) {
data, err := a.MarshalJSON()
require.NoError(t, err)
a2 := NewAttribute()
require.NoError(t, a2.UnmarshalJSON(data))
require.Equal(t, a, a2)
})
}
func TestNewAttributeFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *object.Attribute
require.Nil(t, NewAttributeFromV2(x))
})
}
func TestAttribute_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *Attribute
require.Nil(t, x.ToV2())
})
}
func TestNewAttribute(t *testing.T) {
t.Run("default values", func(t *testing.T) {
a := NewAttribute()
// check initial values
require.Empty(t, a.Key())
require.Empty(t, a.Value())
// convert to v2 message
aV2 := a.ToV2()
require.Empty(t, aV2.GetKey())
require.Empty(t, aV2.GetValue())
})
}

19
object/error.go Normal file
View file

@ -0,0 +1,19 @@
package object
type SplitInfoError struct {
si *SplitInfo
}
const splitInfoErrorMsg = "object not found, split info has been provided"
func (s *SplitInfoError) Error() string {
return splitInfoErrorMsg
}
func (s *SplitInfoError) SplitInfo() *SplitInfo {
return s.si
}
func NewSplitInfoError(v *SplitInfo) *SplitInfoError {
return &SplitInfoError{si: v}
}

33
object/error_test.go Normal file
View file

@ -0,0 +1,33 @@
package object_test
import (
"errors"
"testing"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/stretchr/testify/require"
)
func TestNewSplitInfoError(t *testing.T) {
var (
si = generateSplitInfo()
err error = object.NewSplitInfoError(si)
expectedErr *object.SplitInfoError
)
require.True(t, errors.As(err, &expectedErr))
siErr, ok := err.(*object.SplitInfoError)
require.True(t, ok)
require.Equal(t, si, siErr.SplitInfo())
}
func generateSplitInfo() *object.SplitInfo {
si := object.NewSplitInfo()
si.SetSplitID(object.NewSplitID())
si.SetLastPart(generateID())
si.SetLink(generateID())
return si
}

175
object/fmt.go Normal file
View file

@ -0,0 +1,175 @@
package object
import (
"crypto/ecdsa"
"crypto/sha256"
"errors"
"fmt"
signatureV2 "github.com/nspcc-dev/neofs-api-go/v2/signature"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
"github.com/nspcc-dev/neofs-sdk-go/signature"
sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature"
)
var errCheckSumMismatch = errors.New("payload checksum mismatch")
var errIncorrectID = errors.New("incorrect object identifier")
// CalculatePayloadChecksum calculates and returns checksum of
// object payload bytes.
func CalculatePayloadChecksum(payload []byte) *checksum.Checksum {
res := checksum.New()
res.SetSHA256(sha256.Sum256(payload))
return res
}
// CalculateAndSetPayloadChecksum calculates checksum of current
// object payload and writes it to the object.
func CalculateAndSetPayloadChecksum(obj *RawObject) {
obj.SetPayloadChecksum(
CalculatePayloadChecksum(obj.Payload()),
)
}
// VerifyPayloadChecksum checks if payload checksum in the object
// corresponds to its payload.
func VerifyPayloadChecksum(obj *Object) error {
actual := CalculatePayloadChecksum(obj.Payload())
if !checksum.Equal(obj.PayloadChecksum(), actual) {
return errCheckSumMismatch
}
return nil
}
// CalculateID calculates identifier for the object.
func CalculateID(obj *Object) (*ID, error) {
data, err := obj.ToV2().GetHeader().StableMarshal(nil)
if err != nil {
return nil, err
}
id := NewID()
id.SetSHA256(sha256.Sum256(data))
return id, nil
}
// CalculateAndSetID calculates identifier for the object
// and writes the result to it.
func CalculateAndSetID(obj *RawObject) error {
id, err := CalculateID(obj.Object())
if err != nil {
return err
}
obj.SetID(id)
return nil
}
// VerifyID checks if identifier in the object corresponds to
// its structure.
func VerifyID(obj *Object) error {
id, err := CalculateID(obj)
if err != nil {
return err
}
if !id.Equal(obj.ID()) {
return errIncorrectID
}
return nil
}
func CalculateIDSignature(key *ecdsa.PrivateKey, id *ID) (*signature.Signature, error) {
sig := signature.New()
if err := sigutil.SignDataWithHandler(
key,
signatureV2.StableMarshalerWrapper{
SM: id.ToV2(),
},
func(key, sign []byte) {
sig.SetKey(key)
sig.SetSign(sign)
},
); err != nil {
return nil, err
}
return sig, nil
}
func CalculateAndSetSignature(key *ecdsa.PrivateKey, obj *RawObject) error {
sig, err := CalculateIDSignature(key, obj.ID())
if err != nil {
return err
}
obj.SetSignature(sig)
return nil
}
func VerifyIDSignature(obj *Object) error {
return sigutil.VerifyDataWithSource(
signatureV2.StableMarshalerWrapper{
SM: obj.ID().ToV2(),
},
func() ([]byte, []byte) {
sig := obj.Signature()
return sig.Key(), sig.Sign()
},
)
}
// SetIDWithSignature sets object identifier and signature.
func SetIDWithSignature(key *ecdsa.PrivateKey, obj *RawObject) error {
if err := CalculateAndSetID(obj); err != nil {
return fmt.Errorf("could not set identifier: %w", err)
}
if err := CalculateAndSetSignature(key, obj); err != nil {
return fmt.Errorf("could not set signature: %w", err)
}
return nil
}
// SetVerificationFields calculates and sets all verification fields of the object.
func SetVerificationFields(key *ecdsa.PrivateKey, obj *RawObject) error {
CalculateAndSetPayloadChecksum(obj)
return SetIDWithSignature(key, obj)
}
// CheckVerificationFields checks all verification fields of the object.
func CheckVerificationFields(obj *Object) error {
if err := CheckHeaderVerificationFields(obj); err != nil {
return fmt.Errorf("invalid header structure: %w", err)
}
if err := VerifyPayloadChecksum(obj); err != nil {
return fmt.Errorf("invalid payload checksum: %w", err)
}
return nil
}
// CheckHeaderVerificationFields checks all verification fields except payload.
func CheckHeaderVerificationFields(obj *Object) error {
if err := VerifyIDSignature(obj); err != nil {
return fmt.Errorf("invalid signature: %w", err)
}
if err := VerifyID(obj); err != nil {
return fmt.Errorf("invalid identifier: %w", err)
}
return nil
}

81
object/fmt_test.go Normal file
View file

@ -0,0 +1,81 @@
package object
import (
"crypto/rand"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require"
)
func TestVerificationFields(t *testing.T) {
obj := NewRaw()
payload := make([]byte, 10)
_, _ = rand.Read(payload)
obj.SetPayload(payload)
obj.SetPayloadSize(uint64(len(payload)))
p, err := keys.NewPrivateKey()
require.NoError(t, err)
require.NoError(t, SetVerificationFields(&p.PrivateKey, obj))
require.NoError(t, CheckVerificationFields(obj.Object()))
items := []struct {
corrupt func()
restore func()
}{
{
corrupt: func() {
payload[0]++
},
restore: func() {
payload[0]--
},
},
{
corrupt: func() {
obj.SetPayloadSize(obj.PayloadSize() + 1)
},
restore: func() {
obj.SetPayloadSize(obj.PayloadSize() - 1)
},
},
{
corrupt: func() {
obj.ID().ToV2().GetValue()[0]++
},
restore: func() {
obj.ID().ToV2().GetValue()[0]--
},
},
{
corrupt: func() {
obj.Signature().Key()[0]++
},
restore: func() {
obj.Signature().Key()[0]--
},
},
{
corrupt: func() {
obj.Signature().Sign()[0]++
},
restore: func() {
obj.Signature().Sign()[0]--
},
},
}
for _, item := range items {
item.corrupt()
require.Error(t, CheckVerificationFields(obj.Object()))
item.restore()
require.NoError(t, CheckVerificationFields(obj.Object()))
}
}

92
object/id.go Normal file
View file

@ -0,0 +1,92 @@
package object
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
)
// ID represents v2-compatible object identifier.
type ID refs.ObjectID
var errInvalidIDString = errors.New("incorrect format of the string object ID")
// NewIDFromV2 wraps v2 ObjectID message to ID.
//
// Nil refs.ObjectID converts to nil.
func NewIDFromV2(idV2 *refs.ObjectID) *ID {
return (*ID)(idV2)
}
// NewID creates and initializes blank ID.
//
// Works similar as NewIDFromV2(new(ObjectID)).
//
// Defaults:
// - value: nil.
func NewID() *ID {
return NewIDFromV2(new(refs.ObjectID))
}
// SetSHA256 sets object identifier value to SHA256 checksum.
func (id *ID) SetSHA256(v [sha256.Size]byte) {
(*refs.ObjectID)(id).SetValue(v[:])
}
// Equal returns true if identifiers are identical.
func (id *ID) Equal(id2 *ID) bool {
return bytes.Equal(
(*refs.ObjectID)(id).GetValue(),
(*refs.ObjectID)(id2).GetValue(),
)
}
// ToV2 converts ID to v2 ObjectID message.
//
// Nil ID converts to nil.
func (id *ID) ToV2() *refs.ObjectID {
return (*refs.ObjectID)(id)
}
// Parse converts base58 string representation into ID.
func (id *ID) Parse(s string) error {
data, err := base58.Decode(s)
if err != nil {
return fmt.Errorf("could not parse object.ID from string: %w", err)
} else if len(data) != sha256.Size {
return errInvalidIDString
}
(*refs.ObjectID)(id).SetValue(data)
return nil
}
// String returns base58 string representation of ID.
func (id *ID) String() string {
return base58.Encode((*refs.ObjectID)(id).GetValue())
}
// Marshal marshals ID into a protobuf binary form.
func (id *ID) Marshal() ([]byte, error) {
return (*refs.ObjectID)(id).StableMarshal(nil)
}
// Unmarshal unmarshals protobuf binary representation of ID.
func (id *ID) Unmarshal(data []byte) error {
return (*refs.ObjectID)(id).Unmarshal(data)
}
// MarshalJSON encodes ID to protobuf JSON format.
func (id *ID) MarshalJSON() ([]byte, error) {
return (*refs.ObjectID)(id).MarshalJSON()
}
// UnmarshalJSON decodes ID from protobuf JSON format.
func (id *ID) UnmarshalJSON(data []byte) error {
return (*refs.ObjectID)(id).UnmarshalJSON(data)
}

142
object/id_test.go Normal file
View file

@ -0,0 +1,142 @@
package object
import (
"crypto/rand"
"crypto/sha256"
"strconv"
"testing"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/stretchr/testify/require"
)
func TestIDV2(t *testing.T) {
id := NewID()
checksum := [sha256.Size]byte{}
_, err := rand.Read(checksum[:])
require.NoError(t, err)
id.SetSHA256(checksum)
idV2 := id.ToV2()
require.Equal(t, checksum[:], idV2.GetValue())
}
func TestID_Equal(t *testing.T) {
cs := randSHA256Checksum(t)
id1 := NewID()
id1.SetSHA256(cs)
id2 := NewID()
id2.SetSHA256(cs)
id3 := NewID()
id3.SetSHA256(randSHA256Checksum(t))
require.True(t, id1.Equal(id2))
require.False(t, id1.Equal(id3))
}
func TestID_Parse(t *testing.T) {
t.Run("should parse successful", func(t *testing.T) {
for i := 0; i < 10; i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
cs := randSHA256Checksum(t)
str := base58.Encode(cs[:])
oid := NewID()
require.NoError(t, oid.Parse(str))
require.Equal(t, cs[:], oid.ToV2().GetValue())
})
}
})
t.Run("should failure on parse", func(t *testing.T) {
for i := 0; i < 10; i++ {
j := i
t.Run(strconv.Itoa(j), func(t *testing.T) {
cs := []byte{1, 2, 3, 4, 5, byte(j)}
str := base58.Encode(cs)
oid := NewID()
require.Error(t, oid.Parse(str))
})
}
})
}
func TestID_String(t *testing.T) {
t.Run("nil", func(t *testing.T) {
id := NewID()
require.Empty(t, id.String())
})
t.Run("should be equal", func(t *testing.T) {
for i := 0; i < 10; i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
cs := randSHA256Checksum(t)
str := base58.Encode(cs[:])
oid := NewID()
require.NoError(t, oid.Parse(str))
require.Equal(t, str, oid.String())
})
}
})
}
func TestObjectIDEncoding(t *testing.T) {
id := randID(t)
t.Run("binary", func(t *testing.T) {
data, err := id.Marshal()
require.NoError(t, err)
id2 := NewID()
require.NoError(t, id2.Unmarshal(data))
require.Equal(t, id, id2)
})
t.Run("json", func(t *testing.T) {
data, err := id.MarshalJSON()
require.NoError(t, err)
a2 := NewID()
require.NoError(t, a2.UnmarshalJSON(data))
require.Equal(t, id, a2)
})
}
func TestNewIDFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *refs.ObjectID
require.Nil(t, NewIDFromV2(x))
})
}
func TestID_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *ID
require.Nil(t, x.ToV2())
})
}
func TestNewID(t *testing.T) {
t.Run("default values", func(t *testing.T) {
id := NewID()
// convert to v2 message
idV2 := id.ToV2()
require.Nil(t, idV2.GetValue())
})
}

45
object/object.go Normal file
View file

@ -0,0 +1,45 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/object"
)
// Object represents v2-compatible NeoFS object that provides
// a convenient interface for working in isolation
// from the internal structure of an object.
//
// Object allows to work with the object in read-only
// mode as a reflection of the immutability of objects
// in the system.
type Object struct {
*rwObject
}
// NewFromV2 wraps v2 Object message to Object.
func NewFromV2(oV2 *object.Object) *Object {
return &Object{
rwObject: (*rwObject)(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 {
if o != nil {
return (*object.Object)(o.rwObject)
}
return nil
}
// MarshalHeaderJSON marshals object's header
// into JSON format.
func (o *Object) MarshalHeaderJSON() ([]byte, error) {
return (*object.Object)(o.rwObject).GetHeader().MarshalJSON()
}

51
object/range.go Normal file
View file

@ -0,0 +1,51 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/object"
)
// Range represents v2-compatible object payload range.
type Range object.Range
// NewRangeFromV2 wraps v2 Range message to Range.
//
// Nil object.Range converts to nil.
func NewRangeFromV2(rV2 *object.Range) *Range {
return (*Range)(rV2)
}
// NewRange creates and initializes blank Range.
//
// Defaults:
// - offset: 0;
// - length: 0.
func NewRange() *Range {
return NewRangeFromV2(new(object.Range))
}
// ToV2 converts Range to v2 Range message.
//
// Nil Range converts to nil.
func (r *Range) ToV2() *object.Range {
return (*object.Range)(r)
}
// GetLength returns payload range size.
func (r *Range) GetLength() uint64 {
return (*object.Range)(r).GetLength()
}
// SetLength sets payload range size.
func (r *Range) SetLength(v uint64) {
(*object.Range)(r).SetLength(v)
}
// GetOffset sets payload range offset from start.
func (r *Range) GetOffset() uint64 {
return (*object.Range)(r).GetOffset()
}
// SetOffset gets payload range offset from start.
func (r *Range) SetOffset(v uint64) {
(*object.Range)(r).SetOffset(v)
}

58
object/range_test.go Normal file
View file

@ -0,0 +1,58 @@
package object
import (
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/stretchr/testify/require"
)
func TestRange_SetOffset(t *testing.T) {
r := NewRange()
off := uint64(13)
r.SetOffset(off)
require.Equal(t, off, r.GetOffset())
}
func TestRange_SetLength(t *testing.T) {
r := NewRange()
ln := uint64(7)
r.SetLength(ln)
require.Equal(t, ln, r.GetLength())
}
func TestNewRangeFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *object.Range
require.Nil(t, NewRangeFromV2(x))
})
}
func TestRange_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *Range
require.Nil(t, x.ToV2())
})
}
func TestNewRange(t *testing.T) {
t.Run("default values", func(t *testing.T) {
r := NewRange()
// check initial values
require.Zero(t, r.GetLength())
require.Zero(t, r.GetOffset())
// convert to v2 message
rV2 := r.ToV2()
require.Zero(t, rV2.GetLength())
require.Zero(t, rV2.GetOffset())
})
}

162
object/raw.go Normal file
View file

@ -0,0 +1,162 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/nspcc-dev/neofs-sdk-go/signature"
"github.com/nspcc-dev/neofs-sdk-go/version"
)
// RawObject represents v2-compatible NeoFS object that provides
// a convenient interface to fill in the fields of
// an object in isolation from its internal structure.
type RawObject struct {
*rwObject
}
// NewRawFromV2 wraps v2 Object message to RawObject.
func NewRawFromV2(oV2 *object.Object) *RawObject {
return &RawObject{
rwObject: (*rwObject)(oV2),
}
}
// NewRawFrom wraps Object instance to RawObject.
func NewRawFrom(obj *Object) *RawObject {
return NewRawFromV2(obj.ToV2())
}
// NewRaw creates and initializes blank RawObject.
//
// Works similar as NewRawFromV2(new(Object)).
func NewRaw() *RawObject {
return NewRawFromV2(new(object.Object))
}
// Object returns read-only object instance.
func (o *RawObject) Object() *Object {
if o != nil {
return &Object{
rwObject: o.rwObject,
}
}
return nil
}
// SetID sets object identifier.
func (o *RawObject) SetID(v *ID) {
o.setID(v)
}
// SetSignature sets signature of the object identifier.
func (o *RawObject) SetSignature(v *signature.Signature) {
o.setSignature(v)
}
// SetPayload sets payload bytes.
func (o *RawObject) SetPayload(v []byte) {
o.setPayload(v)
}
// SetVersion sets version of the object.
func (o *RawObject) SetVersion(v *version.Version) {
o.setVersion(v)
}
// SetPayloadSize sets payload length of the object.
func (o *RawObject) SetPayloadSize(v uint64) {
o.setPayloadSize(v)
}
// SetContainerID sets identifier of the related container.
func (o *RawObject) SetContainerID(v *cid.ID) {
o.setContainerID(v)
}
// SetOwnerID sets identifier of the object owner.
func (o *RawObject) SetOwnerID(v *owner.ID) {
o.setOwnerID(v)
}
// SetCreationEpoch sets epoch number in which object was created.
func (o *RawObject) SetCreationEpoch(v uint64) {
o.setCreationEpoch(v)
}
// SetPayloadChecksum sets checksum of the object payload.
func (o *RawObject) SetPayloadChecksum(v *checksum.Checksum) {
o.setPayloadChecksum(v)
}
// SetPayloadHomomorphicHash sets homomorphic hash of the object payload.
func (o *RawObject) SetPayloadHomomorphicHash(v *checksum.Checksum) {
o.setPayloadHomomorphicHash(v)
}
// SetAttributes sets object attributes.
func (o *RawObject) SetAttributes(v ...*Attribute) {
o.setAttributes(v...)
}
// SetPreviousID sets identifier of the previous sibling object.
func (o *RawObject) SetPreviousID(v *ID) {
o.setPreviousID(v)
}
// SetChildren sets list of the identifiers of the child objects.
func (o *RawObject) SetChildren(v ...*ID) {
o.setChildren(v...)
}
// SetSplitID sets split identifier for the split object.
func (o *RawObject) SetSplitID(id *SplitID) {
o.setSplitID(id)
}
// SetParentID sets identifier of the parent object.
func (o *RawObject) SetParentID(v *ID) {
o.setParentID(v)
}
// SetParent sets parent object w/o payload.
func (o *RawObject) SetParent(v *Object) {
o.setParent(v)
}
// SetSessionToken sets token of the session
// within which object was created.
func (o *RawObject) SetSessionToken(v *session.Token) {
o.setSessionToken(v)
}
// SetType sets type of the object.
func (o *RawObject) SetType(v Type) {
o.setType(v)
}
// CutPayload returns RawObject w/ empty payload.
//
// Changes of non-payload fields affect source object.
func (o *RawObject) CutPayload() *RawObject {
if o != nil {
return &RawObject{
rwObject: o.rwObject.cutPayload(),
}
}
return nil
}
// ResetRelations removes all fields of links with other objects.
func (o *RawObject) ResetRelations() {
o.resetRelations()
}
// InitRelations initializes relation field.
func (o *RawObject) InitRelations() {
o.initRelations()
}

319
object/raw_test.go Normal file
View file

@ -0,0 +1,319 @@
package object
import (
"crypto/rand"
"crypto/sha256"
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
"github.com/nspcc-dev/neofs-sdk-go/signature"
"github.com/nspcc-dev/neofs-sdk-go/version"
"github.com/stretchr/testify/require"
)
func randID(t *testing.T) *ID {
id := NewID()
id.SetSHA256(randSHA256Checksum(t))
return id
}
func randSHA256Checksum(t *testing.T) (cs [sha256.Size]byte) {
_, err := rand.Read(cs[:])
require.NoError(t, err)
return
}
func randTZChecksum(t *testing.T) (cs [64]byte) {
_, err := rand.Read(cs[:])
require.NoError(t, err)
return
}
func TestRawObject_SetID(t *testing.T) {
obj := NewRaw()
id := randID(t)
obj.SetID(id)
require.Equal(t, id, obj.ID())
}
func TestRawObject_SetSignature(t *testing.T) {
obj := NewRaw()
sig := signature.New()
sig.SetKey([]byte{1, 2, 3})
sig.SetSign([]byte{4, 5, 6})
obj.SetSignature(sig)
require.Equal(t, sig, obj.Signature())
}
func TestRawObject_SetPayload(t *testing.T) {
obj := NewRaw()
payload := make([]byte, 10)
_, _ = rand.Read(payload)
obj.SetPayload(payload)
require.Equal(t, payload, obj.Payload())
}
func TestRawObject_SetVersion(t *testing.T) {
obj := NewRaw()
ver := version.New()
ver.SetMajor(1)
ver.SetMinor(2)
obj.SetVersion(ver)
require.Equal(t, ver, obj.Version())
}
func TestRawObject_SetPayloadSize(t *testing.T) {
obj := NewRaw()
sz := uint64(133)
obj.SetPayloadSize(sz)
require.Equal(t, sz, obj.PayloadSize())
}
func TestRawObject_SetContainerID(t *testing.T) {
obj := NewRaw()
cid := cidtest.GenerateID()
obj.SetContainerID(cid)
require.Equal(t, cid, obj.ContainerID())
}
func TestRawObject_SetOwnerID(t *testing.T) {
obj := NewRaw()
ownerID := ownertest.GenerateID()
obj.SetOwnerID(ownerID)
require.Equal(t, ownerID, obj.OwnerID())
}
func TestRawObject_SetCreationEpoch(t *testing.T) {
obj := NewRaw()
creat := uint64(228)
obj.setCreationEpoch(creat)
require.Equal(t, creat, obj.CreationEpoch())
}
func TestRawObject_SetPayloadChecksum(t *testing.T) {
obj := NewRaw()
cs := checksum.New()
cs.SetSHA256(randSHA256Checksum(t))
obj.SetPayloadChecksum(cs)
require.Equal(t, cs, obj.PayloadChecksum())
}
func TestRawObject_SetPayloadHomomorphicHash(t *testing.T) {
obj := NewRaw()
cs := checksum.New()
cs.SetTillichZemor(randTZChecksum(t))
obj.SetPayloadHomomorphicHash(cs)
require.Equal(t, cs, obj.PayloadHomomorphicHash())
}
func TestRawObject_SetAttributes(t *testing.T) {
obj := NewRaw()
a1 := NewAttribute()
a1.SetKey("key1")
a1.SetValue("val1")
a2 := NewAttribute()
a2.SetKey("key2")
a2.SetValue("val2")
obj.SetAttributes(a1, a2)
require.Equal(t, []*Attribute{a1, a2}, obj.Attributes())
}
func TestRawObject_SetPreviousID(t *testing.T) {
obj := NewRaw()
prev := randID(t)
obj.SetPreviousID(prev)
require.Equal(t, prev, obj.PreviousID())
}
func TestRawObject_SetChildren(t *testing.T) {
obj := NewRaw()
id1 := randID(t)
id2 := randID(t)
obj.SetChildren(id1, id2)
require.Equal(t, []*ID{id1, id2}, obj.Children())
}
func TestRawObject_SetSplitID(t *testing.T) {
obj := NewRaw()
require.Nil(t, obj.SplitID())
splitID := NewSplitID()
obj.SetSplitID(splitID)
require.Equal(t, obj.SplitID(), splitID)
}
func TestRawObject_SetParent(t *testing.T) {
obj := NewRaw()
require.Nil(t, obj.Parent())
par := NewRaw()
par.SetID(randID(t))
par.SetContainerID(cidtest.GenerateID())
par.SetSignature(signature.New())
parObj := par.Object()
obj.SetParent(parObj)
require.Equal(t, parObj, obj.Parent())
}
func TestRawObject_ToV2(t *testing.T) {
objV2 := new(object.Object)
objV2.SetPayload([]byte{1, 2, 3})
obj := NewRawFromV2(objV2)
require.Equal(t, objV2, obj.ToV2())
}
func TestRawObject_SetSessionToken(t *testing.T) {
obj := NewRaw()
tok := sessiontest.Generate()
obj.SetSessionToken(tok)
require.Equal(t, tok, obj.SessionToken())
}
func TestRawObject_SetType(t *testing.T) {
obj := NewRaw()
typ := TypeStorageGroup
obj.SetType(typ)
require.Equal(t, typ, obj.Type())
}
func TestRawObject_CutPayload(t *testing.T) {
o1 := NewRaw()
p1 := []byte{12, 3}
o1.SetPayload(p1)
sz := uint64(13)
o1.SetPayloadSize(sz)
o2 := o1.CutPayload()
require.Equal(t, sz, o2.PayloadSize())
require.Empty(t, o2.Payload())
sz++
o1.SetPayloadSize(sz)
require.Equal(t, sz, o1.PayloadSize())
require.Equal(t, sz, o2.PayloadSize())
p2 := []byte{4, 5, 6}
o2.SetPayload(p2)
require.Equal(t, p2, o2.Payload())
require.Equal(t, p1, o1.Payload())
}
func TestRawObject_SetParentID(t *testing.T) {
obj := NewRaw()
id := randID(t)
obj.setParentID(id)
require.Equal(t, id, obj.ParentID())
}
func TestRawObject_ResetRelations(t *testing.T) {
obj := NewRaw()
obj.SetPreviousID(randID(t))
obj.ResetRelations()
require.Nil(t, obj.PreviousID())
}
func TestRwObject_HasParent(t *testing.T) {
obj := NewRaw()
obj.InitRelations()
require.True(t, obj.HasParent())
obj.ResetRelations()
require.False(t, obj.HasParent())
}
func TestRWObjectEncoding(t *testing.T) {
o := NewRaw()
o.SetID(randID(t))
t.Run("binary", func(t *testing.T) {
data, err := o.Marshal()
require.NoError(t, err)
o2 := NewRaw()
require.NoError(t, o2.Unmarshal(data))
require.Equal(t, o, o2)
})
t.Run("json", func(t *testing.T) {
data, err := o.MarshalJSON()
require.NoError(t, err)
o2 := NewRaw()
require.NoError(t, o2.UnmarshalJSON(data))
require.Equal(t, o, o2)
})
}

388
object/rw.go Normal file
View file

@ -0,0 +1,388 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/nspcc-dev/neofs-sdk-go/signature"
"github.com/nspcc-dev/neofs-sdk-go/version"
)
// wrapper over v2 Object that provides
// public getter and private setters.
type rwObject object.Object
// ToV2 converts Object to v2 Object message.
func (o *rwObject) ToV2() *object.Object {
return (*object.Object)(o)
}
func (o *rwObject) 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 *rwObject) 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 *rwObject) ID() *ID {
return NewIDFromV2(
(*object.Object)(o).
GetObjectID(),
)
}
func (o *rwObject) setID(v *ID) {
(*object.Object)(o).
SetObjectID(v.ToV2())
}
// Signature returns signature of the object identifier.
func (o *rwObject) Signature() *signature.Signature {
return signature.NewFromV2(
(*object.Object)(o).GetSignature())
}
func (o *rwObject) setSignature(v *signature.Signature) {
(*object.Object)(o).SetSignature(v.ToV2())
}
// Payload returns payload bytes.
func (o *rwObject) Payload() []byte {
return (*object.Object)(o).GetPayload()
}
func (o *rwObject) setPayload(v []byte) {
(*object.Object)(o).SetPayload(v)
}
// Version returns version of the object.
func (o *rwObject) Version() *version.Version {
return version.NewFromV2(
(*object.Object)(o).
GetHeader().
GetVersion(),
)
}
func (o *rwObject) setVersion(v *version.Version) {
o.setHeaderField(func(h *object.Header) {
h.SetVersion(v.ToV2())
})
}
// PayloadSize returns payload length of the object.
func (o *rwObject) PayloadSize() uint64 {
return (*object.Object)(o).
GetHeader().
GetPayloadLength()
}
func (o *rwObject) setPayloadSize(v uint64) {
o.setHeaderField(func(h *object.Header) {
h.SetPayloadLength(v)
})
}
// ContainerID returns identifier of the related container.
func (o *rwObject) ContainerID() *cid.ID {
return cid.NewFromV2(
(*object.Object)(o).
GetHeader().
GetContainerID(),
)
}
func (o *rwObject) setContainerID(v *cid.ID) {
o.setHeaderField(func(h *object.Header) {
h.SetContainerID(v.ToV2())
})
}
// OwnerID returns identifier of the object owner.
func (o *rwObject) OwnerID() *owner.ID {
return owner.NewIDFromV2(
(*object.Object)(o).
GetHeader().
GetOwnerID(),
)
}
func (o *rwObject) setOwnerID(v *owner.ID) {
o.setHeaderField(func(h *object.Header) {
h.SetOwnerID(v.ToV2())
})
}
// CreationEpoch returns epoch number in which object was created.
func (o *rwObject) CreationEpoch() uint64 {
return (*object.Object)(o).
GetHeader().
GetCreationEpoch()
}
func (o *rwObject) setCreationEpoch(v uint64) {
o.setHeaderField(func(h *object.Header) {
h.SetCreationEpoch(v)
})
}
// PayloadChecksum returns checksum of the object payload.
func (o *rwObject) PayloadChecksum() *checksum.Checksum {
return checksum.NewFromV2(
(*object.Object)(o).
GetHeader().
GetPayloadHash(),
)
}
func (o *rwObject) setPayloadChecksum(v *checksum.Checksum) {
o.setHeaderField(func(h *object.Header) {
h.SetPayloadHash(v.ToV2())
})
}
// PayloadHomomorphicHash returns homomorphic hash of the object payload.
func (o *rwObject) PayloadHomomorphicHash() *checksum.Checksum {
return checksum.NewFromV2(
(*object.Object)(o).
GetHeader().
GetHomomorphicHash(),
)
}
func (o *rwObject) setPayloadHomomorphicHash(v *checksum.Checksum) {
o.setHeaderField(func(h *object.Header) {
h.SetHomomorphicHash(v.ToV2())
})
}
// Attributes returns object attributes.
func (o *rwObject) Attributes() []*Attribute {
attrs := (*object.Object)(o).
GetHeader().
GetAttributes()
res := make([]*Attribute, 0, len(attrs))
for i := range attrs {
res = append(res, NewAttributeFromV2(attrs[i]))
}
return res
}
func (o *rwObject) setAttributes(v ...*Attribute) {
attrs := make([]*object.Attribute, 0, len(v))
for i := range v {
attrs = append(attrs, v[i].ToV2())
}
o.setHeaderField(func(h *object.Header) {
h.SetAttributes(attrs)
})
}
// PreviousID returns identifier of the previous sibling object.
func (o *rwObject) PreviousID() *ID {
return NewIDFromV2(
(*object.Object)(o).
GetHeader().
GetSplit().
GetPrevious(),
)
}
func (o *rwObject) setPreviousID(v *ID) {
o.setSplitFields(func(split *object.SplitHeader) {
split.SetPrevious(v.ToV2())
})
}
// Children return list of the identifiers of the child objects.
func (o *rwObject) Children() []*ID {
ids := (*object.Object)(o).
GetHeader().
GetSplit().
GetChildren()
res := make([]*ID, 0, len(ids))
for i := range ids {
res = append(res, NewIDFromV2(ids[i]))
}
return res
}
func (o *rwObject) setChildren(v ...*ID) {
ids := make([]*refs.ObjectID, 0, len(v))
for i := range v {
ids = append(ids, v[i].ToV2())
}
o.setSplitFields(func(split *object.SplitHeader) {
split.SetChildren(ids)
})
}
// SplitID return split identity of split object. If object is not split
// returns nil.
func (o *rwObject) SplitID() *SplitID {
return NewSplitIDFromV2(
(*object.Object)(o).
GetHeader().
GetSplit().
GetSplitID(),
)
}
func (o *rwObject) setSplitID(id *SplitID) {
o.setSplitFields(func(split *object.SplitHeader) {
split.SetSplitID(id.ToV2())
})
}
// ParentID returns identifier of the parent object.
func (o *rwObject) ParentID() *ID {
return NewIDFromV2(
(*object.Object)(o).
GetHeader().
GetSplit().
GetParent(),
)
}
func (o *rwObject) setParentID(v *ID) {
o.setSplitFields(func(split *object.SplitHeader) {
split.SetParent(v.ToV2())
})
}
// Parent returns parent object w/o payload.
func (o *rwObject) 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)
}
func (o *rwObject) setParent(v *Object) {
o.setSplitFields(func(split *object.SplitHeader) {
split.SetParent((*object.Object)(v.rwObject).GetObjectID())
split.SetParentSignature((*object.Object)(v.rwObject).GetSignature())
split.SetParentHeader((*object.Object)(v.rwObject).GetHeader())
})
}
func (o *rwObject) initRelations() {
o.setHeaderField(func(h *object.Header) {
h.SetSplit(new(object.SplitHeader))
})
}
func (o *rwObject) resetRelations() {
o.setHeaderField(func(h *object.Header) {
h.SetSplit(nil)
})
}
// SessionToken returns token of the session
// within which object was created.
func (o *rwObject) SessionToken() *session.Token {
return session.NewTokenFromV2(
(*object.Object)(o).
GetHeader().
GetSessionToken(),
)
}
func (o *rwObject) setSessionToken(v *session.Token) {
o.setHeaderField(func(h *object.Header) {
h.SetSessionToken(v.ToV2())
})
}
// Type returns type of the object.
func (o *rwObject) Type() Type {
return TypeFromV2(
(*object.Object)(o).
GetHeader().
GetObjectType(),
)
}
func (o *rwObject) setType(t Type) {
o.setHeaderField(func(h *object.Header) {
h.SetObjectType(t.ToV2())
})
}
func (o *rwObject) cutPayload() *rwObject {
ov2 := new(object.Object)
*ov2 = *(*object.Object)(o)
ov2.SetPayload(nil)
return (*rwObject)(ov2)
}
func (o *rwObject) HasParent() bool {
return (*object.Object)(o).
GetHeader().
GetSplit() != nil
}
// Marshal marshals object into a protobuf binary form.
func (o *rwObject) Marshal() ([]byte, error) {
return (*object.Object)(o).StableMarshal(nil)
}
// Unmarshal unmarshals protobuf binary representation of object.
func (o *rwObject) Unmarshal(data []byte) error {
return (*object.Object)(o).Unmarshal(data)
}
// MarshalJSON encodes object to protobuf JSON format.
func (o *rwObject) MarshalJSON() ([]byte, error) {
return (*object.Object)(o).MarshalJSON()
}
// UnmarshalJSON decodes object from protobuf JSON format.
func (o *rwObject) UnmarshalJSON(data []byte) error {
return (*object.Object)(o).UnmarshalJSON(data)
}

299
object/search.go Normal file
View file

@ -0,0 +1,299 @@
package object
import (
"encoding/json"
"fmt"
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/version"
)
// SearchMatchType indicates match operation on specified header.
type SearchMatchType uint32
const (
MatchUnknown SearchMatchType = iota
MatchStringEqual
MatchStringNotEqual
MatchNotPresent
MatchCommonPrefix
)
func (m SearchMatchType) ToV2() v2object.MatchType {
switch m {
case MatchStringEqual:
return v2object.MatchStringEqual
case MatchStringNotEqual:
return v2object.MatchStringNotEqual
case MatchNotPresent:
return v2object.MatchNotPresent
case MatchCommonPrefix:
return v2object.MatchCommonPrefix
default:
return v2object.MatchUnknown
}
}
func SearchMatchFromV2(t v2object.MatchType) (m SearchMatchType) {
switch t {
case v2object.MatchStringEqual:
m = MatchStringEqual
case v2object.MatchStringNotEqual:
m = MatchStringNotEqual
case v2object.MatchNotPresent:
m = MatchNotPresent
case v2object.MatchCommonPrefix:
m = MatchCommonPrefix
default:
m = MatchUnknown
}
return m
}
// String returns string representation of SearchMatchType.
//
// String mapping:
// * MatchStringEqual: STRING_EQUAL;
// * MatchStringNotEqual: STRING_NOT_EQUAL;
// * MatchNotPresent: NOT_PRESENT;
// * MatchCommonPrefix: COMMON_PREFIX;
// * MatchUnknown, default: MATCH_TYPE_UNSPECIFIED.
func (m SearchMatchType) String() string {
return m.ToV2().String()
}
// FromString parses SearchMatchType from a string representation.
// It is a reverse action to String().
//
// Returns true if s was parsed successfully.
func (m *SearchMatchType) FromString(s string) bool {
var g v2object.MatchType
ok := g.FromString(s)
if ok {
*m = SearchMatchFromV2(g)
}
return ok
}
type SearchFilter struct {
header filterKey
value fmt.Stringer
op SearchMatchType
}
type staticStringer string
type filterKey struct {
typ filterKeyType
str string
}
// enumeration of reserved filter keys.
type filterKeyType int
type SearchFilters []SearchFilter
const (
_ filterKeyType = iota
fKeyVersion
fKeyObjectID
fKeyContainerID
fKeyOwnerID
fKeyCreationEpoch
fKeyPayloadLength
fKeyPayloadHash
fKeyType
fKeyHomomorphicHash
fKeyParent
fKeySplitID
fKeyPropRoot
fKeyPropPhy
)
func (k filterKey) String() string {
switch k.typ {
default:
return k.str
case fKeyVersion:
return v2object.FilterHeaderVersion
case fKeyObjectID:
return v2object.FilterHeaderObjectID
case fKeyContainerID:
return v2object.FilterHeaderContainerID
case fKeyOwnerID:
return v2object.FilterHeaderOwnerID
case fKeyCreationEpoch:
return v2object.FilterHeaderCreationEpoch
case fKeyPayloadLength:
return v2object.FilterHeaderPayloadLength
case fKeyPayloadHash:
return v2object.FilterHeaderPayloadHash
case fKeyType:
return v2object.FilterHeaderObjectType
case fKeyHomomorphicHash:
return v2object.FilterHeaderHomomorphicHash
case fKeyParent:
return v2object.FilterHeaderParent
case fKeySplitID:
return v2object.FilterHeaderSplitID
case fKeyPropRoot:
return v2object.FilterPropertyRoot
case fKeyPropPhy:
return v2object.FilterPropertyPhy
}
}
func (s staticStringer) String() string {
return string(s)
}
func (f *SearchFilter) Header() string {
return f.header.String()
}
func (f *SearchFilter) Value() string {
return f.value.String()
}
func (f *SearchFilter) Operation() SearchMatchType {
return f.op
}
func NewSearchFilters() SearchFilters {
return SearchFilters{}
}
func NewSearchFiltersFromV2(v2 []*v2object.SearchFilter) SearchFilters {
filters := make(SearchFilters, 0, len(v2))
for i := range v2 {
if v2[i] == nil {
continue
}
filters.AddFilter(
v2[i].GetKey(),
v2[i].GetValue(),
SearchMatchFromV2(v2[i].GetMatchType()),
)
}
return filters
}
func (f *SearchFilters) addFilter(op SearchMatchType, keyTyp filterKeyType, key string, val fmt.Stringer) {
if *f == nil {
*f = make(SearchFilters, 0, 1)
}
*f = append(*f, SearchFilter{
header: filterKey{
typ: keyTyp,
str: key,
},
value: val,
op: op,
})
}
func (f *SearchFilters) AddFilter(header, value string, op SearchMatchType) {
f.addFilter(op, 0, header, staticStringer(value))
}
func (f *SearchFilters) addReservedFilter(op SearchMatchType, keyTyp filterKeyType, val fmt.Stringer) {
f.addFilter(op, keyTyp, "", val)
}
// addFlagFilters adds filters that works like flags: they don't need to have
// specific match type or value. They processed by NeoFS nodes by the fact
// of presence in search query. E.g.: PHY, ROOT.
func (f *SearchFilters) addFlagFilter(keyTyp filterKeyType) {
f.addFilter(MatchUnknown, keyTyp, "", staticStringer(""))
}
func (f *SearchFilters) AddObjectVersionFilter(op SearchMatchType, v *version.Version) {
f.addReservedFilter(op, fKeyVersion, v)
}
func (f *SearchFilters) AddObjectContainerIDFilter(m SearchMatchType, id *cid.ID) {
f.addReservedFilter(m, fKeyContainerID, id)
}
func (f *SearchFilters) AddObjectOwnerIDFilter(m SearchMatchType, id *owner.ID) {
f.addReservedFilter(m, fKeyOwnerID, id)
}
func (f SearchFilters) ToV2() []*v2object.SearchFilter {
result := make([]*v2object.SearchFilter, 0, len(f))
for i := range f {
v2 := new(v2object.SearchFilter)
v2.SetKey(f[i].header.String())
v2.SetValue(f[i].value.String())
v2.SetMatchType(f[i].op.ToV2())
result = append(result, v2)
}
return result
}
func (f *SearchFilters) addRootFilter() {
f.addFlagFilter(fKeyPropRoot)
}
func (f *SearchFilters) AddRootFilter() {
f.addRootFilter()
}
func (f *SearchFilters) addPhyFilter() {
f.addFlagFilter(fKeyPropPhy)
}
func (f *SearchFilters) AddPhyFilter() {
f.addPhyFilter()
}
// AddParentIDFilter adds filter by parent identifier.
func (f *SearchFilters) AddParentIDFilter(m SearchMatchType, id *ID) {
f.addReservedFilter(m, fKeyParent, id)
}
// AddObjectIDFilter adds filter by object identifier.
func (f *SearchFilters) AddObjectIDFilter(m SearchMatchType, id *ID) {
f.addReservedFilter(m, fKeyObjectID, id)
}
func (f *SearchFilters) AddSplitIDFilter(m SearchMatchType, id *SplitID) {
f.addReservedFilter(m, fKeySplitID, id)
}
// AddTypeFilter adds filter by object type.
func (f *SearchFilters) AddTypeFilter(m SearchMatchType, typ Type) {
f.addReservedFilter(m, fKeyType, typ)
}
// MarshalJSON encodes SearchFilters to protobuf JSON format.
func (f *SearchFilters) MarshalJSON() ([]byte, error) {
return json.Marshal(f.ToV2())
}
// UnmarshalJSON decodes SearchFilters from protobuf JSON format.
func (f *SearchFilters) UnmarshalJSON(data []byte) error {
var fsV2 []*v2object.SearchFilter
if err := json.Unmarshal(data, &fsV2); err != nil {
return err
}
*f = NewSearchFiltersFromV2(fsV2)
return nil
}

208
object/search_test.go Normal file
View file

@ -0,0 +1,208 @@
package object_test
import (
"crypto/sha256"
"math/rand"
"testing"
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/stretchr/testify/require"
)
var eqV2Matches = map[object.SearchMatchType]v2object.MatchType{
object.MatchUnknown: v2object.MatchUnknown,
object.MatchStringEqual: v2object.MatchStringEqual,
object.MatchStringNotEqual: v2object.MatchStringNotEqual,
object.MatchNotPresent: v2object.MatchNotPresent,
object.MatchCommonPrefix: v2object.MatchCommonPrefix,
}
func TestMatch(t *testing.T) {
t.Run("known matches", func(t *testing.T) {
for matchType, matchTypeV2 := range eqV2Matches {
require.Equal(t, matchTypeV2, matchType.ToV2())
require.Equal(t, object.SearchMatchFromV2(matchTypeV2), matchType)
}
})
t.Run("unknown matches", func(t *testing.T) {
var unknownMatchType object.SearchMatchType
for matchType := range eqV2Matches {
unknownMatchType += matchType
}
unknownMatchType++
require.Equal(t, unknownMatchType.ToV2(), v2object.MatchUnknown)
var unknownMatchTypeV2 v2object.MatchType
for _, matchTypeV2 := range eqV2Matches {
unknownMatchTypeV2 += matchTypeV2
}
unknownMatchTypeV2++
require.Equal(t, object.SearchMatchFromV2(unknownMatchTypeV2), object.MatchUnknown)
})
}
func TestFilter(t *testing.T) {
inputs := [][]string{
{"user-header", "user-value"},
}
filters := object.NewSearchFilters()
for i := range inputs {
filters.AddFilter(inputs[i][0], inputs[i][1], object.MatchStringEqual)
}
require.Len(t, filters, len(inputs))
for i := range inputs {
require.Equal(t, inputs[i][0], filters[i].Header())
require.Equal(t, inputs[i][1], filters[i].Value())
require.Equal(t, object.MatchStringEqual, filters[i].Operation())
}
v2 := filters.ToV2()
newFilters := object.NewSearchFiltersFromV2(v2)
require.Equal(t, filters, newFilters)
}
func TestSearchFilters_AddRootFilter(t *testing.T) {
fs := new(object.SearchFilters)
fs.AddRootFilter()
require.Len(t, *fs, 1)
f := (*fs)[0]
require.Equal(t, object.MatchUnknown, f.Operation())
require.Equal(t, v2object.FilterPropertyRoot, f.Header())
require.Equal(t, "", f.Value())
}
func TestSearchFilters_AddPhyFilter(t *testing.T) {
fs := new(object.SearchFilters)
fs.AddPhyFilter()
require.Len(t, *fs, 1)
f := (*fs)[0]
require.Equal(t, object.MatchUnknown, f.Operation())
require.Equal(t, v2object.FilterPropertyPhy, f.Header())
require.Equal(t, "", f.Value())
}
func testOID() *object.ID {
cs := [sha256.Size]byte{}
rand.Read(cs[:])
id := object.NewID()
id.SetSHA256(cs)
return id
}
func TestSearchFilters_AddParentIDFilter(t *testing.T) {
par := testOID()
fs := object.SearchFilters{}
fs.AddParentIDFilter(object.MatchStringEqual, par)
fsV2 := fs.ToV2()
require.Len(t, fsV2, 1)
require.Equal(t, v2object.FilterHeaderParent, fsV2[0].GetKey())
require.Equal(t, par.String(), fsV2[0].GetValue())
require.Equal(t, v2object.MatchStringEqual, fsV2[0].GetMatchType())
}
func TestSearchFilters_AddObjectIDFilter(t *testing.T) {
id := testOID()
fs := new(object.SearchFilters)
fs.AddObjectIDFilter(object.MatchStringEqual, id)
t.Run("v2", func(t *testing.T) {
fsV2 := fs.ToV2()
require.Len(t, fsV2, 1)
require.Equal(t, v2object.FilterHeaderObjectID, fsV2[0].GetKey())
require.Equal(t, id.String(), fsV2[0].GetValue())
require.Equal(t, v2object.MatchStringEqual, fsV2[0].GetMatchType())
})
}
func TestSearchFilters_AddSplitIDFilter(t *testing.T) {
id := object.NewSplitID()
fs := new(object.SearchFilters)
fs.AddSplitIDFilter(object.MatchStringEqual, id)
t.Run("v2", func(t *testing.T) {
fsV2 := fs.ToV2()
require.Len(t, fsV2, 1)
require.Equal(t, v2object.FilterHeaderSplitID, fsV2[0].GetKey())
require.Equal(t, id.String(), fsV2[0].GetValue())
require.Equal(t, v2object.MatchStringEqual, fsV2[0].GetMatchType())
})
}
func TestSearchFilters_AddTypeFilter(t *testing.T) {
typ := object.TypeTombstone
fs := new(object.SearchFilters)
fs.AddTypeFilter(object.MatchStringEqual, typ)
t.Run("v2", func(t *testing.T) {
fsV2 := fs.ToV2()
require.Len(t, fsV2, 1)
require.Equal(t, v2object.FilterHeaderObjectType, fsV2[0].GetKey())
require.Equal(t, typ.String(), fsV2[0].GetValue())
require.Equal(t, v2object.MatchStringEqual, fsV2[0].GetMatchType())
})
}
func TestSearchFiltersEncoding(t *testing.T) {
fs := object.NewSearchFilters()
fs.AddFilter("key 1", "value 2", object.MatchStringEqual)
fs.AddFilter("key 2", "value 2", object.MatchStringNotEqual)
fs.AddFilter("key 2", "value 2", object.MatchCommonPrefix)
t.Run("json", func(t *testing.T) {
data, err := fs.MarshalJSON()
require.NoError(t, err)
fs2 := object.NewSearchFilters()
require.NoError(t, fs2.UnmarshalJSON(data))
require.Equal(t, fs, fs2)
})
}
func TestSearchMatchType_String(t *testing.T) {
toPtr := func(v object.SearchMatchType) *object.SearchMatchType {
return &v
}
testEnumStrings(t, new(object.SearchMatchType), []enumStringItem{
{val: toPtr(object.MatchCommonPrefix), str: "COMMON_PREFIX"},
{val: toPtr(object.MatchStringEqual), str: "STRING_EQUAL"},
{val: toPtr(object.MatchStringNotEqual), str: "STRING_NOT_EQUAL"},
{val: toPtr(object.MatchNotPresent), str: "NOT_PRESENT"},
{val: toPtr(object.MatchUnknown), str: "MATCH_TYPE_UNSPECIFIED"},
})
}

80
object/splitid.go Normal file
View file

@ -0,0 +1,80 @@
package object
import (
"github.com/google/uuid"
)
// SplitID is a UUIDv4 used as attribute in split objects.
type SplitID struct {
uuid uuid.UUID
}
// NewSplitID returns UUID representation of splitID attribute.
//
// Defaults:
// - id: random UUID.
func NewSplitID() *SplitID {
return &SplitID{
uuid: uuid.New(),
}
}
// NewSplitIDFromV2 returns parsed UUID from bytes.
// If v is invalid UUIDv4 byte sequence, then function returns nil.
//
// Nil converts to nil.
func NewSplitIDFromV2(v []byte) *SplitID {
if v == nil {
return nil
}
id := uuid.New()
err := id.UnmarshalBinary(v)
if err != nil {
return nil
}
return &SplitID{
uuid: id,
}
}
// Parse converts UUIDv4 string representation into SplitID.
func (id *SplitID) Parse(s string) (err error) {
id.uuid, err = uuid.Parse(s)
if err != nil {
return err
}
return nil
}
// String returns UUIDv4 string representation of SplitID.
func (id *SplitID) String() string {
if id == nil {
return ""
}
return id.uuid.String()
}
// SetUUID sets pre created UUID structure as SplitID.
func (id *SplitID) SetUUID(v uuid.UUID) {
if id != nil {
id.uuid = v
}
}
// ToV2 converts SplitID to a representation of SplitID in neofs-api v2.
//
// Nil SplitID converts to nil.
func (id *SplitID) ToV2() []byte {
if id == nil {
return nil
}
data, _ := id.uuid.MarshalBinary() // err is always nil
return data
}

63
object/splitid_test.go Normal file
View file

@ -0,0 +1,63 @@
package object_test
import (
"testing"
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/stretchr/testify/require"
)
func TestSplitID(t *testing.T) {
id := object.NewSplitID()
t.Run("toV2/fromV2", func(t *testing.T) {
data := id.ToV2()
newID := object.NewSplitIDFromV2(data)
require.NotNil(t, newID)
require.Equal(t, id, newID)
})
t.Run("string/parse", func(t *testing.T) {
idStr := id.String()
newID := object.NewSplitID()
require.NoError(t, newID.Parse(idStr))
require.Equal(t, id, newID)
})
t.Run("set UUID", func(t *testing.T) {
newUUID := uuid.New()
id.SetUUID(newUUID)
require.Equal(t, newUUID.String(), id.String())
})
t.Run("nil value", func(t *testing.T) {
var newID *object.SplitID
require.NotPanics(t, func() {
require.Nil(t, newID.ToV2())
require.Equal(t, "", newID.String())
})
})
}
func TestSplitID_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *object.SplitID
require.Nil(t, x.ToV2())
})
}
func TestNewIDFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x []byte
require.Nil(t, object.NewSplitIDFromV2(x))
})
}

66
object/splitinfo.go Normal file
View file

@ -0,0 +1,66 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/object"
)
type SplitInfo object.SplitInfo
// NewSplitInfoFromV2 wraps v2 SplitInfo message to SplitInfo.
//
// Nil object.SplitInfo converts to nil.
func NewSplitInfoFromV2(v2 *object.SplitInfo) *SplitInfo {
return (*SplitInfo)(v2)
}
// NewSplitInfo creates and initializes blank SplitInfo.
//
// Defaults:
// - splitID: nil;
// - lastPart nil;
// - link: nil.
func NewSplitInfo() *SplitInfo {
return NewSplitInfoFromV2(new(object.SplitInfo))
}
// ToV2 converts SplitInfo to v2 SplitInfo message.
//
// Nil SplitInfo converts to nil.
func (s *SplitInfo) ToV2() *object.SplitInfo {
return (*object.SplitInfo)(s)
}
func (s *SplitInfo) SplitID() *SplitID {
return NewSplitIDFromV2(
(*object.SplitInfo)(s).GetSplitID())
}
func (s *SplitInfo) SetSplitID(v *SplitID) {
(*object.SplitInfo)(s).SetSplitID(v.ToV2())
}
func (s *SplitInfo) LastPart() *ID {
return NewIDFromV2(
(*object.SplitInfo)(s).GetLastPart())
}
func (s *SplitInfo) SetLastPart(v *ID) {
(*object.SplitInfo)(s).SetLastPart(v.ToV2())
}
func (s *SplitInfo) Link() *ID {
return NewIDFromV2(
(*object.SplitInfo)(s).GetLink())
}
func (s *SplitInfo) SetLink(v *ID) {
(*object.SplitInfo)(s).SetLink(v.ToV2())
}
func (s *SplitInfo) Marshal() ([]byte, error) {
return (*object.SplitInfo)(s).StableMarshal(nil)
}
func (s *SplitInfo) Unmarshal(data []byte) error {
return (*object.SplitInfo)(s).Unmarshal(data)
}

88
object/splitinfo_test.go Normal file
View file

@ -0,0 +1,88 @@
package object_test
import (
"crypto/rand"
"testing"
objv2 "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/stretchr/testify/require"
)
func TestSplitInfo(t *testing.T) {
s := object.NewSplitInfo()
splitID := object.NewSplitID()
lastPart := generateID()
link := generateID()
s.SetSplitID(splitID)
require.Equal(t, splitID, s.SplitID())
s.SetLastPart(lastPart)
require.Equal(t, lastPart, s.LastPart())
s.SetLink(link)
require.Equal(t, link, s.Link())
t.Run("to and from v2", func(t *testing.T) {
v2 := s.ToV2()
newS := object.NewSplitInfoFromV2(v2)
require.Equal(t, s, newS)
})
t.Run("marshal and unmarshal", func(t *testing.T) {
data, err := s.Marshal()
require.NoError(t, err)
newS := object.NewSplitInfo()
err = newS.Unmarshal(data)
require.NoError(t, err)
require.Equal(t, s, newS)
})
}
func generateID() *object.ID {
var buf [32]byte
_, _ = rand.Read(buf[:])
id := object.NewID()
id.SetSHA256(buf)
return id
}
func TestNewSplitInfoFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *objv2.SplitInfo
require.Nil(t, object.NewSplitInfoFromV2(x))
})
}
func TestSplitInfo_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *object.SplitInfo
require.Nil(t, x.ToV2())
})
}
func TestNewSplitInfo(t *testing.T) {
t.Run("default values", func(t *testing.T) {
si := object.NewSplitInfo()
// check initial values
require.Nil(t, si.SplitID())
require.Nil(t, si.LastPart())
require.Nil(t, si.Link())
// convert to v2 message
siV2 := si.ToV2()
require.Nil(t, siV2.GetSplitID())
require.Nil(t, siV2.GetLastPart())
require.Nil(t, siV2.GetLink())
})
}

143
object/test/generate.go Normal file
View file

@ -0,0 +1,143 @@
package objecttest
import (
"crypto/sha256"
"math/rand"
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/object"
ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
"github.com/nspcc-dev/neofs-sdk-go/signature"
"github.com/nspcc-dev/neofs-sdk-go/version"
)
// ID returns random object.ID.
func ID() *object.ID {
checksum := [sha256.Size]byte{}
rand.Read(checksum[:])
return IDWithChecksum(checksum)
}
// IDWithChecksum returns object.ID initialized
// with specified checksum.
func IDWithChecksum(cs [sha256.Size]byte) *object.ID {
id := object.NewID()
id.SetSHA256(cs)
return id
}
// Address returns random object.Address.
func Address() *object.Address {
x := object.NewAddress()
x.SetContainerID(cidtest.GenerateID())
x.SetObjectID(ID())
return x
}
// Range returns random object.Range.
func Range() *object.Range {
x := object.NewRange()
x.SetOffset(1024)
x.SetLength(2048)
return x
}
// Attribute returns random object.Attribute.
func Attribute() *object.Attribute {
x := object.NewAttribute()
x.SetKey("key")
x.SetValue("value")
return x
}
// SplitID returns random object.SplitID.
func SplitID() *object.SplitID {
x := object.NewSplitID()
x.SetUUID(uuid.New())
return x
}
func generateRaw(withParent bool) *object.RawObject {
x := object.NewRaw()
x.SetID(ID())
x.SetSessionToken(sessiontest.Generate())
x.SetPayload([]byte{1, 2, 3})
x.SetOwnerID(ownertest.GenerateID())
x.SetContainerID(cidtest.GenerateID())
x.SetType(object.TypeTombstone)
x.SetVersion(version.Current())
x.SetPayloadSize(111)
x.SetCreationEpoch(222)
x.SetPreviousID(ID())
x.SetParentID(ID())
x.SetChildren(ID(), ID())
x.SetAttributes(Attribute(), Attribute())
x.SetSplitID(SplitID())
// TODO reuse generators
x.SetPayloadChecksum(checksum.New())
x.SetPayloadHomomorphicHash(checksum.New())
x.SetSignature(signature.New())
if withParent {
x.SetParent(generateRaw(false).Object())
}
return x
}
// Raw returns random object.RawObject.
func Raw() *object.RawObject {
return generateRaw(true)
}
// Object returns random object.Object.
func Object() *object.Object {
return Raw().Object()
}
// Tombstone returns random object.Tombstone.
func Tombstone() *object.Tombstone {
x := object.NewTombstone()
x.SetSplitID(SplitID())
x.SetExpirationEpoch(13)
x.SetMembers([]*object.ID{ID(), ID()})
return x
}
// SplitInfo returns random object.SplitInfo.
func SplitInfo() *object.SplitInfo {
x := object.NewSplitInfo()
x.SetSplitID(SplitID())
x.SetLink(ID())
x.SetLastPart(ID())
return x
}
// SearchFilters returns random object.SearchFilters.
func SearchFilters() object.SearchFilters {
x := object.NewSearchFilters()
x.AddObjectIDFilter(object.MatchStringEqual, ID())
x.AddObjectContainerIDFilter(object.MatchStringNotEqual, cidtest.GenerateID())
return x
}

114
object/tombstone.go Normal file
View file

@ -0,0 +1,114 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/tombstone"
)
// Tombstone represents v2-compatible tombstone structure.
type Tombstone tombstone.Tombstone
// NewTombstoneFromV2 wraps v2 Tombstone message to Tombstone.
//
// Nil tombstone.Tombstone converts to nil.
func NewTombstoneFromV2(tV2 *tombstone.Tombstone) *Tombstone {
return (*Tombstone)(tV2)
}
// NewTombstone creates and initializes blank Tombstone.
//
// Defaults:
// - exp: 0;
// - splitID: nil;
// - members: nil.
func NewTombstone() *Tombstone {
return NewTombstoneFromV2(new(tombstone.Tombstone))
}
// ToV2 converts Tombstone to v2 Tombstone message.
//
// Nil Tombstone converts to nil.
func (t *Tombstone) ToV2() *tombstone.Tombstone {
return (*tombstone.Tombstone)(t)
}
// ExpirationEpoch return number of tombstone expiration epoch.
func (t *Tombstone) ExpirationEpoch() uint64 {
return (*tombstone.Tombstone)(t).GetExpirationEpoch()
}
// SetExpirationEpoch sets number of tombstone expiration epoch.
func (t *Tombstone) SetExpirationEpoch(v uint64) {
(*tombstone.Tombstone)(t).SetExpirationEpoch(v)
}
// SplitID returns identifier of object split hierarchy.
func (t *Tombstone) SplitID() *SplitID {
return NewSplitIDFromV2(
(*tombstone.Tombstone)(t).GetSplitID())
}
// SetSplitID sets identifier of object split hierarchy.
func (t *Tombstone) SetSplitID(v *SplitID) {
(*tombstone.Tombstone)(t).SetSplitID(v.ToV2())
}
// Members returns list of objects to be deleted.
func (t *Tombstone) Members() []*ID {
msV2 := (*tombstone.Tombstone)(t).
GetMembers()
if msV2 == nil {
return nil
}
ms := make([]*ID, 0, len(msV2))
for i := range msV2 {
ms = append(ms, NewIDFromV2(msV2[i]))
}
return ms
}
// SetMembers sets list of objects to be deleted.
func (t *Tombstone) SetMembers(v []*ID) {
var ms []*refs.ObjectID
if v != nil {
ms = (*tombstone.Tombstone)(t).
GetMembers()
if ln := len(v); cap(ms) >= ln {
ms = ms[:0]
} else {
ms = make([]*refs.ObjectID, 0, ln)
}
for i := range v {
ms = append(ms, v[i].ToV2())
}
}
(*tombstone.Tombstone)(t).SetMembers(ms)
}
// Marshal marshals Tombstone into a protobuf binary form.
func (t *Tombstone) Marshal() ([]byte, error) {
return (*tombstone.Tombstone)(t).StableMarshal(nil)
}
// Unmarshal unmarshals protobuf binary representation of Tombstone.
func (t *Tombstone) Unmarshal(data []byte) error {
return (*tombstone.Tombstone)(t).Unmarshal(data)
}
// MarshalJSON encodes Tombstone to protobuf JSON format.
func (t *Tombstone) MarshalJSON() ([]byte, error) {
return (*tombstone.Tombstone)(t).MarshalJSON()
}
// UnmarshalJSON decodes Tombstone from protobuf JSON format.
func (t *Tombstone) UnmarshalJSON(data []byte) error {
return (*tombstone.Tombstone)(t).UnmarshalJSON(data)
}

92
object/tombstone_test.go Normal file
View file

@ -0,0 +1,92 @@
package object
import (
"crypto/sha256"
"math/rand"
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/tombstone"
"github.com/stretchr/testify/require"
)
func generateIDList(sz int) []*ID {
res := make([]*ID, sz)
cs := [sha256.Size]byte{}
for i := 0; i < sz; i++ {
res[i] = NewID()
rand.Read(cs[:])
res[i].SetSHA256(cs)
}
return res
}
func TestTombstone(t *testing.T) {
ts := NewTombstone()
exp := uint64(13)
ts.SetExpirationEpoch(exp)
require.Equal(t, exp, ts.ExpirationEpoch())
splitID := NewSplitID()
ts.SetSplitID(splitID)
require.Equal(t, splitID, ts.SplitID())
members := generateIDList(3)
ts.SetMembers(members)
require.Equal(t, members, ts.Members())
}
func TestTombstoneEncoding(t *testing.T) {
ts := NewTombstone()
ts.SetExpirationEpoch(13)
ts.SetSplitID(NewSplitID())
ts.SetMembers(generateIDList(5))
t.Run("binary", func(t *testing.T) {
data, err := ts.Marshal()
require.NoError(t, err)
ts2 := NewTombstone()
require.NoError(t, ts2.Unmarshal(data))
require.Equal(t, ts, ts2)
})
t.Run("json", func(t *testing.T) {
data, err := ts.MarshalJSON()
require.NoError(t, err)
ts2 := NewTombstone()
require.NoError(t, ts2.UnmarshalJSON(data))
require.Equal(t, ts, ts2)
})
}
func TestNewTombstoneFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *tombstone.Tombstone
require.Nil(t, NewTombstoneFromV2(x))
})
}
func TestNewTombstone(t *testing.T) {
t.Run("default values", func(t *testing.T) {
ts := NewTombstone()
// check initial values
require.Nil(t, ts.SplitID())
require.Nil(t, ts.Members())
require.Zero(t, ts.ExpirationEpoch())
// convert to v2 message
tsV2 := ts.ToV2()
require.Nil(t, tsV2.GetSplitID())
require.Nil(t, tsV2.GetMembers())
require.Zero(t, tsV2.GetExpirationEpoch())
})
}

61
object/type.go Normal file
View file

@ -0,0 +1,61 @@
package object
import (
"github.com/nspcc-dev/neofs-api-go/v2/object"
)
type Type uint8
const (
TypeRegular Type = iota
TypeTombstone
TypeStorageGroup
)
func (t Type) ToV2() object.Type {
switch t {
case TypeTombstone:
return object.TypeTombstone
case TypeStorageGroup:
return object.TypeStorageGroup
default:
return object.TypeRegular
}
}
func TypeFromV2(t object.Type) Type {
switch t {
case object.TypeTombstone:
return TypeTombstone
case object.TypeStorageGroup:
return TypeStorageGroup
default:
return TypeRegular
}
}
// String returns string representation of Type.
//
// String mapping:
// * TypeTombstone: TOMBSTONE;
// * TypeStorageGroup: STORAGE_GROUP;
// * TypeRegular, default: REGULAR.
func (t Type) String() string {
return t.ToV2().String()
}
// FromString parses Type from a string representation.
// It is a reverse action to String().
//
// Returns true if s was parsed successfully.
func (t *Type) FromString(s string) bool {
var g object.Type
ok := g.FromString(s)
if ok {
*t = TypeFromV2(g)
}
return ok
}

79
object/type_test.go Normal file
View file

@ -0,0 +1,79 @@
package object_test
import (
"testing"
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/stretchr/testify/require"
)
func TestType_ToV2(t *testing.T) {
typs := []struct {
t object.Type
t2 v2object.Type
}{
{
t: object.TypeRegular,
t2: v2object.TypeRegular,
},
{
t: object.TypeTombstone,
t2: v2object.TypeTombstone,
},
{
t: object.TypeStorageGroup,
t2: v2object.TypeStorageGroup,
},
}
for _, item := range typs {
t2 := item.t.ToV2()
require.Equal(t, item.t2, t2)
require.Equal(t, item.t, object.TypeFromV2(item.t2))
}
}
func TestType_String(t *testing.T) {
toPtr := func(v object.Type) *object.Type {
return &v
}
testEnumStrings(t, new(object.Type), []enumStringItem{
{val: toPtr(object.TypeTombstone), str: "TOMBSTONE"},
{val: toPtr(object.TypeStorageGroup), str: "STORAGE_GROUP"},
{val: toPtr(object.TypeRegular), str: "REGULAR"},
})
}
type enumIface interface {
FromString(string) bool
String() string
}
type enumStringItem struct {
val enumIface
str string
}
func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
for _, item := range items {
require.Equal(t, item.str, item.val.String())
s := item.val.String()
require.True(t, e.FromString(s), s)
require.EqualValues(t, item.val, e, item.val)
}
// incorrect strings
for _, str := range []string{
"some string",
"undefined",
} {
require.False(t, e.FromString(str))
}
}

View file

@ -0,0 +1,19 @@
package object
const (
// AttributeName is an attribute key that is commonly used to denote
// human-friendly name.
AttributeName = "Name"
// AttributeFileName is an attribute key that is commonly used to denote
// file name to be associated with the object on saving.
AttributeFileName = "FileName"
// AttributeTimestamp is an attribute key that is commonly used to denote
// user-defined local time of object creation in Unix Timestamp format.
AttributeTimestamp = "Timestamp"
// AttributeTimestamp is an attribute key that is commonly used to denote
// MIME Content Type of object's payload.
AttributeContentType = "Content-Type"
)