diff --git a/session/container_test.go b/session/container_test.go index bcd809fb..800025f2 100644 --- a/session/container_test.go +++ b/session/container_test.go @@ -57,7 +57,7 @@ func TestContainerContext_ApplyTo(t *testing.T) { }) } -func TestFilter_ToV2(t *testing.T) { +func TestContextFilter_ToV2(t *testing.T) { t.Run("nil", func(t *testing.T) { var x *session.ContainerContext diff --git a/session/object.go b/session/object.go new file mode 100644 index 00000000..e9be63a0 --- /dev/null +++ b/session/object.go @@ -0,0 +1,166 @@ +package session + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/object/address" +) + +// ObjectContext represents NeoFS API v2-compatible +// context of the object session. +// +// It is a wrapper over session.ObjectSessionContext +// which allows abstracting from details of the message +// structure. +type ObjectContext session.ObjectSessionContext + +// NewObjectContext creates and returns blank ObjectContext. +// +// Defaults: +// - not bound to any operation; +// - nil object address. +func NewObjectContext() *ObjectContext { + v2 := new(session.ObjectSessionContext) + + return ObjectContextFromV2(v2) +} + +// ObjectContextFromV2 wraps session.ObjectSessionContext +// into ObjectContext. +func ObjectContextFromV2(v *session.ObjectSessionContext) *ObjectContext { + return (*ObjectContext)(v) +} + +// ToV2 converts ObjectContext to session.ObjectSessionContext +// message structure. +func (x *ObjectContext) ToV2() *session.ObjectSessionContext { + return (*session.ObjectSessionContext)(x) +} + +// ApplyTo specifies which object the ObjectContext applies to. +func (x *ObjectContext) ApplyTo(id *address.Address) { + v2 := (*session.ObjectSessionContext)(x) + + v2.SetAddress(id.ToV2()) +} + +// Address returns identifier of the object +// to which the ObjectContext applies. +func (x *ObjectContext) Address() *address.Address { + v2 := (*session.ObjectSessionContext)(x) + + return address.NewAddressFromV2(v2.GetAddress()) +} + +func (x *ObjectContext) forVerb(v session.ObjectSessionVerb) { + (*session.ObjectSessionContext)(x). + SetVerb(v) +} + +func (x *ObjectContext) isForVerb(v session.ObjectSessionVerb) bool { + return (*session.ObjectSessionContext)(x). + GetVerb() == v +} + +// ForPut binds the ObjectContext to +// PUT operation. +func (x *ObjectContext) ForPut() { + x.forVerb(session.ObjectVerbPut) +} + +// IsForPut checks if ObjectContext is bound to +// PUT operation. +func (x *ObjectContext) IsForPut() bool { + return x.isForVerb(session.ObjectVerbPut) +} + +// ForDelete binds the ObjectContext to +// DELETE operation. +func (x *ObjectContext) ForDelete() { + x.forVerb(session.ObjectVerbDelete) +} + +// IsForDelete checks if ObjectContext is bound to +// DELETE operation. +func (x *ObjectContext) IsForDelete() bool { + return x.isForVerb(session.ObjectVerbDelete) +} + +// ForGet binds the ObjectContext to +// GET operation. +func (x *ObjectContext) ForGet() { + x.forVerb(session.ObjectVerbGet) +} + +// IsForGet checks if ObjectContext is bound to +// GET operation. +func (x *ObjectContext) IsForGet() bool { + return x.isForVerb(session.ObjectVerbGet) +} + +// ForHead binds the ObjectContext to +// HEAD operation. +func (x *ObjectContext) ForHead() { + x.forVerb(session.ObjectVerbHead) +} + +// IsForHead checks if ObjectContext is bound to +// HEAD operation. +func (x *ObjectContext) IsForHead() bool { + return x.isForVerb(session.ObjectVerbHead) +} + +// ForSearch binds the ObjectContext to +// SEARCH operation. +func (x *ObjectContext) ForSearch() { + x.forVerb(session.ObjectVerbSearch) +} + +// IsForSearch checks if ObjectContext is bound to +// SEARCH operation. +func (x *ObjectContext) IsForSearch() bool { + return x.isForVerb(session.ObjectVerbSearch) +} + +// ForRange binds the ObjectContext to +// RANGE operation. +func (x *ObjectContext) ForRange() { + x.forVerb(session.ObjectVerbRange) +} + +// IsForRange checks if ObjectContext is bound to +// RANGE operation. +func (x *ObjectContext) IsForRange() bool { + return x.isForVerb(session.ObjectVerbRange) +} + +// ForRangeHash binds the ObjectContext to +// RANGEHASH operation. +func (x *ObjectContext) ForRangeHash() { + x.forVerb(session.ObjectVerbRangeHash) +} + +// IsForRangeHash checks if ObjectContext is bound to +// RANGEHASH operation. +func (x *ObjectContext) IsForRangeHash() bool { + return x.isForVerb(session.ObjectVerbRangeHash) +} + +// Marshal marshals ObjectContext into a protobuf binary form. +func (x *ObjectContext) Marshal() ([]byte, error) { + return x.ToV2().StableMarshal(nil) +} + +// Unmarshal unmarshals protobuf binary representation of ObjectContext. +func (x *ObjectContext) Unmarshal(data []byte) error { + return x.ToV2().Unmarshal(data) +} + +// MarshalJSON encodes ObjectContext to protobuf JSON format. +func (x *ObjectContext) MarshalJSON() ([]byte, error) { + return x.ToV2().MarshalJSON() +} + +// UnmarshalJSON decodes ObjectContext from protobuf JSON format. +func (x *ObjectContext) UnmarshalJSON(data []byte) error { + return x.ToV2().UnmarshalJSON(data) +} diff --git a/session/object_test.go b/session/object_test.go new file mode 100644 index 00000000..0e256625 --- /dev/null +++ b/session/object_test.go @@ -0,0 +1,123 @@ +package session_test + +import ( + "testing" + + v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + objecttest "github.com/nspcc-dev/neofs-sdk-go/object/address/test" + "github.com/nspcc-dev/neofs-sdk-go/session" + sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" + "github.com/stretchr/testify/require" +) + +func TestObjectContextVerbs(t *testing.T) { + c := session.NewObjectContext() + + assert := func(setter func(), getter func() bool, verb v2session.ObjectSessionVerb) { + setter() + + require.True(t, getter()) + + require.Equal(t, verb, c.ToV2().GetVerb()) + } + + t.Run("PUT", func(t *testing.T) { + assert(c.ForPut, c.IsForPut, v2session.ObjectVerbPut) + }) + + t.Run("DELETE", func(t *testing.T) { + assert(c.ForDelete, c.IsForDelete, v2session.ObjectVerbDelete) + }) + + t.Run("GET", func(t *testing.T) { + assert(c.ForGet, c.IsForGet, v2session.ObjectVerbGet) + }) + + t.Run("SEARCH", func(t *testing.T) { + assert(c.ForSearch, c.IsForSearch, v2session.ObjectVerbSearch) + }) + + t.Run("RANGE", func(t *testing.T) { + assert(c.ForRange, c.IsForRange, v2session.ObjectVerbRange) + }) + + t.Run("RANGEHASH", func(t *testing.T) { + assert(c.ForRangeHash, c.IsForRangeHash, v2session.ObjectVerbRangeHash) + }) + + t.Run("HEAD", func(t *testing.T) { + assert(c.ForHead, c.IsForHead, v2session.ObjectVerbHead) + }) +} + +func TestObjectContext_ApplyTo(t *testing.T) { + c := session.NewObjectContext() + id := objecttest.Address() + + t.Run("method", func(t *testing.T) { + c.ApplyTo(id) + + require.Equal(t, id, c.Address()) + + c.ApplyTo(nil) + + require.Nil(t, c.Address()) + }) +} + +func TestObjectFilter_ToV2(t *testing.T) { + t.Run("nil", func(t *testing.T) { + var x *session.ObjectContext + + require.Nil(t, x.ToV2()) + }) + + t.Run("default values", func(t *testing.T) { + c := session.NewObjectContext() + + // check initial values + require.Nil(t, c.Address()) + + for _, op := range []func() bool{ + c.IsForPut, + c.IsForDelete, + c.IsForGet, + c.IsForHead, + c.IsForRange, + c.IsForRangeHash, + c.IsForSearch, + } { + require.False(t, op()) + } + + // convert to v2 message + cV2 := c.ToV2() + + require.Equal(t, v2session.ObjectVerbUnknown, cV2.GetVerb()) + require.Nil(t, cV2.GetAddress()) + }) +} + +func TestObjectContextEncoding(t *testing.T) { + c := sessiontest.ObjectContext() + + t.Run("binary", func(t *testing.T) { + data, err := c.Marshal() + require.NoError(t, err) + + c2 := session.NewObjectContext() + require.NoError(t, c2.Unmarshal(data)) + + require.Equal(t, c, c2) + }) + + t.Run("json", func(t *testing.T) { + data, err := c.MarshalJSON() + require.NoError(t, err) + + c2 := session.NewObjectContext() + require.NoError(t, c2.UnmarshalJSON(data)) + + require.Equal(t, c, c2) + }) +} diff --git a/session/session.go b/session/session.go index c38e0d76..119eca6f 100644 --- a/session/session.go +++ b/session/session.go @@ -237,6 +237,8 @@ func (t *Token) Context() interface{} { return nil case *session.ContainerSessionContext: return ContainerContextFromV2(c) + case *session.ObjectSessionContext: + return ObjectContextFromV2(c) } } diff --git a/session/test/object.go b/session/test/object.go new file mode 100644 index 00000000..d6c95148 --- /dev/null +++ b/session/test/object.go @@ -0,0 +1,30 @@ +package sessiontest + +import ( + "math/rand" + + oidtest "github.com/nspcc-dev/neofs-sdk-go/object/address/test" + "github.com/nspcc-dev/neofs-sdk-go/session" +) + +// ObjectContext returns session.ObjectContext +// which applies to random operation on a random object. +func ObjectContext() *session.ObjectContext { + c := session.NewObjectContext() + + setters := []func(){ + c.ForPut, + c.ForDelete, + c.ForHead, + c.ForRange, + c.ForRangeHash, + c.ForSearch, + c.ForGet, + } + + setters[rand.Uint32()%uint32(len(setters))]() + + c.ApplyTo(oidtest.Address()) + + return c +}