forked from TrueCloudLab/frostfs-sdk-go
[#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:
parent
bdb99877f6
commit
39d3317ef6
28 changed files with 3268 additions and 0 deletions
117
object/address.go
Normal file
117
object/address.go
Normal 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
119
object/address_test.go
Normal 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
73
object/attribute.go
Normal 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
82
object/attribute_test.go
Normal 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
19
object/error.go
Normal 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
33
object/error_test.go
Normal 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
175
object/fmt.go
Normal 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
81
object/fmt_test.go
Normal 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
92
object/id.go
Normal 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
142
object/id_test.go
Normal 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
45
object/object.go
Normal 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
51
object/range.go
Normal 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
58
object/range_test.go
Normal 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
162
object/raw.go
Normal 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
319
object/raw_test.go
Normal 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
388
object/rw.go
Normal 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
299
object/search.go
Normal 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
208
object/search_test.go
Normal 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
80
object/splitid.go
Normal 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
63
object/splitid_test.go
Normal 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
66
object/splitinfo.go
Normal 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
88
object/splitinfo_test.go
Normal 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
143
object/test/generate.go
Normal 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
114
object/tombstone.go
Normal 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
92
object/tombstone_test.go
Normal 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
61
object/type.go
Normal 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
79
object/type_test.go
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
19
object/wellknown_attributes.go
Normal file
19
object/wellknown_attributes.go
Normal 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"
|
||||||
|
)
|
Loading…
Reference in a new issue