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