[#251] object/address: Refactor and document package functionality

Merge `address` package into `oid` one. Bring `session.Object`
implementation into conformity with the NeoFS API protocol.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-05-25 12:03:22 +03:00 committed by LeL
parent bef4618cd6
commit f0a5eb6dbc
15 changed files with 429 additions and 468 deletions

View file

@ -1,192 +0,0 @@
package address
import (
"errors"
"fmt"
"strings"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
oid "github.com/nspcc-dev/neofs-sdk-go/object/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() (v cid.ID, isSet bool) {
v2 := (*refs.Address)(a)
cidV2 := v2.GetContainerID()
if cidV2 != nil {
_ = v.ReadFromV2(*cidV2)
isSet = true
}
return
}
// SetContainerID sets container identifier.
func (a *Address) SetContainerID(id cid.ID) {
var cidV2 refs.ContainerID
id.WriteToV2(&cidV2)
(*refs.Address)(a).SetContainerID(&cidV2)
}
// ObjectID returns object identifier.
func (a *Address) ObjectID() (v oid.ID, isSet bool) {
v2 := (*refs.Address)(a)
oidV2 := v2.GetObjectID()
if oidV2 != nil {
_ = v.ReadFromV2(*oidV2)
isSet = true
}
return
}
// SetObjectID sets object identifier.
func (a *Address) SetObjectID(id oid.ID) {
var oidV2 refs.ObjectID
id.WriteToV2(&oidV2)
(*refs.Address)(a).SetObjectID(&oidV2)
}
// Parse converts base58 string representation into Address.
func (a *Address) Parse(s string) error {
var (
err error
oid oid.ID
id cid.ID
parts = strings.Split(s, addressSeparator)
)
if len(parts) != addressParts {
return errInvalidAddressString
} else if err = id.DecodeString(parts[0]); err != nil {
return err
} else if err = oid.DecodeString(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 {
var cidStr, oidStr string
if cID, set := a.ContainerID(); set {
cidStr = cID.String()
}
if oID, set := a.ObjectID(); set {
oidStr = oID.String()
}
return strings.Join([]string{cidStr, oidStr}, addressSeparator)
}
// Marshal marshals Address into a protobuf binary form.
func (a *Address) Marshal() ([]byte, error) {
return (*refs.Address)(a).StableMarshal(nil)
}
var errCIDNotSet = errors.New("container ID is not set")
var errOIDNotSet = errors.New("object ID is not set")
// Unmarshal unmarshals protobuf binary representation of Address.
func (a *Address) Unmarshal(data []byte) error {
err := (*refs.Address)(a).Unmarshal(data)
if err != nil {
return err
}
v2 := a.ToV2()
return checkFormat(v2)
}
// 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 {
v2 := (*refs.Address)(a)
err := v2.UnmarshalJSON(data)
if err != nil {
return err
}
return checkFormat(v2)
}
func checkFormat(v2 *refs.Address) error {
var (
cID cid.ID
oID oid.ID
)
cidV2 := v2.GetContainerID()
if cidV2 == nil {
return errCIDNotSet
}
err := cID.ReadFromV2(*cidV2)
if err != nil {
return fmt.Errorf("could not convert V2 container ID: %w", err)
}
oidV2 := v2.GetObjectID()
if oidV2 == nil {
return errOIDNotSet
}
err = oID.ReadFromV2(*oidV2)
if err != nil {
return fmt.Errorf("could not convert V2 object ID: %w", err)
}
return nil
}

View file

@ -1,129 +0,0 @@
package address
import (
"strings"
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
"github.com/stretchr/testify/require"
)
func TestAddress_SetContainerID(t *testing.T) {
a := NewAddress()
id := cidtest.ID()
a.SetContainerID(id)
cID, set := a.ContainerID()
require.True(t, set)
require.Equal(t, id, cID)
}
func TestAddress_SetObjectID(t *testing.T) {
a := NewAddress()
oid := oidtest.ID()
a.SetObjectID(oid)
oID, set := a.ObjectID()
require.True(t, set)
require.Equal(t, oid, oID)
}
func TestAddress_Parse(t *testing.T) {
cid := cidtest.ID()
oid := oidtest.ID()
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))
oID, set := a.ObjectID()
require.True(t, set)
require.Equal(t, oid, oID)
cID, set := a.ContainerID()
require.True(t, set)
require.Equal(t, cid, cID)
})
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(oidtest.ID())
a.SetContainerID(cidtest.ID())
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
_, set := a.ContainerID()
require.False(t, set)
_, set = a.ObjectID()
require.False(t, set)
// convert to v2 message
aV2 := a.ToV2()
require.Nil(t, aV2.GetContainerID())
require.Nil(t, aV2.GetObjectID())
})
}

View file

@ -1,17 +0,0 @@
package address
import (
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/object/address"
oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
)
// Address returns random object.Address.
func Address() *address.Address {
x := address.NewAddress()
x.SetContainerID(cidtest.ID())
x.SetObjectID(oidtest.ID())
return x
}

148
object/id/address.go Normal file
View file

@ -0,0 +1,148 @@
package oid
import (
"errors"
"fmt"
"strings"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
// Address represents global object identifier in NeoFS network. Each object
// belongs to exactly one container and is uniquely addressed within the container.
//
// Address is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Address
// message. See ReadFromV2 / WriteToV2 methods.
//
// Instances can be created using built-in var declaration.
//
// Note that direct typecast is not safe and may result in loss of compatibility:
// _ = Address(refs.Address{}) // not recommended
type Address struct {
cnr cid.ID
obj ID
}
// ReadFromV2 reads Address from the refs.Address message. Returns an error if
// the message is malformed according to the NeoFS API V2 protocol.
//
// See also WriteToV2.
func (x *Address) ReadFromV2(m refs.Address) error {
cnr := m.GetContainerID()
if cnr == nil {
return errors.New("missing container ID")
}
obj := m.GetObjectID()
if obj == nil {
return errors.New("missing object ID")
}
err := x.cnr.ReadFromV2(*cnr)
if err != nil {
return fmt.Errorf("invalid container ID: %w", err)
}
err = x.obj.ReadFromV2(*obj)
if err != nil {
return fmt.Errorf("invalid object ID: %w", err)
}
return nil
}
// WriteToV2 writes Address to the refs.Address message.
// The message must not be nil.
//
// See also ReadFromV2.
func (x Address) WriteToV2(m *refs.Address) {
var obj refs.ObjectID
x.obj.WriteToV2(&obj)
var cnr refs.ContainerID
x.cnr.WriteToV2(&cnr)
m.SetObjectID(&obj)
m.SetContainerID(&cnr)
}
// Container sets unique identifier of the NeoFS object container.
//
// Zero Address has zero container ID, which is incorrect according to NeoFS
// API protocol.
//
// See also SetContainer.
func (x Address) Container() cid.ID {
return x.cnr
}
// SetContainer sets unique identifier of the NeoFS object container.
//
// See also Container.
func (x *Address) SetContainer(id cid.ID) {
x.cnr = id
}
// Object returns unique identifier of the object in the container
// identified by Container().
//
// Zero Address has zero object ID, which is incorrect according to NeoFS
// API protocol.
//
// See also SetObject.
func (x Address) Object() ID {
return x.obj
}
// SetObject sets unique identifier of the object in the container
// identified by Container().
//
// See also Object.
func (x *Address) SetObject(id ID) {
x.obj = id
}
// delimiter of container and object IDs in Address protocol string.
const idDelimiter = "/"
// EncodeToString encodes Address into NeoFS API protocol string: concatenation
// of the string-encoded container and object IDs delimited by a slash.
//
// See also DecodeString.
func (x Address) EncodeToString() string {
return x.cnr.EncodeToString() + "/" + x.obj.EncodeToString()
}
// DecodeString decodes string into Address according to NeoFS API protocol. Returns
// an error if s is malformed.
//
// See also DecodeString.
func (x *Address) DecodeString(s string) error {
indDelimiter := strings.Index(s, idDelimiter)
if indDelimiter < 0 {
return errors.New("missing delimiter")
}
err := x.cnr.DecodeString(s[:indDelimiter])
if err != nil {
return fmt.Errorf("decode container string: %w", err)
}
err = x.obj.DecodeString(s[indDelimiter+1:])
if err != nil {
return fmt.Errorf("decode object string: %w", err)
}
return nil
}
// String implements fmt.Stringer.
//
// String is designed to be human-readable, and its format MAY differ between
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
// be used to encode Address into NeoFS protocol string.
func (x Address) String() string {
return x.EncodeToString()
}

95
object/id/address_test.go Normal file
View file

@ -0,0 +1,95 @@
package oid_test
import (
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
"github.com/stretchr/testify/require"
)
func TestAddress_SetContainer(t *testing.T) {
var x oid.Address
require.Zero(t, x.Container())
cnr := cidtest.ID()
x.SetContainer(cnr)
require.Equal(t, cnr, x.Container())
}
func TestAddress_SetObject(t *testing.T) {
var x oid.Address
require.Zero(t, x.Object())
obj := oidtest.ID()
x.SetObject(obj)
require.Equal(t, obj, x.Object())
}
func TestAddress_ReadFromV2(t *testing.T) {
var x oid.Address
var xV2 refs.Address
require.Error(t, x.ReadFromV2(xV2))
var cnrV2 refs.ContainerID
xV2.SetContainerID(&cnrV2)
require.Error(t, x.ReadFromV2(xV2))
cnr := cidtest.ID()
cnr.WriteToV2(&cnrV2)
require.Error(t, x.ReadFromV2(xV2))
var objV2 refs.ObjectID
xV2.SetObjectID(&objV2)
require.Error(t, x.ReadFromV2(xV2))
obj := oidtest.ID()
obj.WriteToV2(&objV2)
require.NoError(t, x.ReadFromV2(xV2))
require.Equal(t, cnr, x.Container())
require.Equal(t, obj, x.Object())
var xV2To refs.Address
x.WriteToV2(&xV2To)
require.Equal(t, xV2, xV2To)
}
func TestAddress_DecodeString(t *testing.T) {
var x, x2 oid.Address
require.NoError(t, x2.DecodeString(x.EncodeToString()))
require.Equal(t, x, x2)
cnr := cidtest.ID()
obj := oidtest.ID()
x.SetContainer(cnr)
x.SetObject(obj)
require.NoError(t, x2.DecodeString(x.EncodeToString()))
require.Equal(t, x, x2)
strCnr := cnr.EncodeToString()
strObj := obj.EncodeToString()
require.Error(t, x2.DecodeString(""))
require.Error(t, x2.DecodeString("/"))
require.Error(t, x2.DecodeString(strCnr))
require.Error(t, x2.DecodeString(strCnr+"/"))
require.Error(t, x2.DecodeString("/"+strCnr))
require.Error(t, x2.DecodeString(strCnr+strObj))
require.Error(t, x2.DecodeString(strCnr+"\\"+strObj))
require.NoError(t, x2.DecodeString(strCnr+"/"+strObj))
}

View file

@ -1,6 +1,9 @@
/*
Package oid provides primitives to work with object identification in NeoFS.
Address type is used for global object identity inside the NeoFS network,
while ID represents identity within a fixed container.
Using package types in an application is recommended to potentially work with
different protocol versions with which these types are compatible.

View file

@ -19,7 +19,7 @@ import (
// Instances can be created using built-in var declaration.
//
// Note that direct typecast is not safe and may result in loss of compatibility:
// _ = ObjectID([32]byte{}) // not recommended
// _ = ID([32]byte{}) // not recommended
type ID [sha256.Size]byte
// ReadFromV2 reads ID from the refs.ObjectID message. Returns an error if

View file

@ -6,7 +6,7 @@ Note that importing the package into source files is highly discouraged.
Random instance generation functions can be useful when testing expects any value, e.g.:
import oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
dec := oidtest.ID()
value := oidtest.ID()
// test the value
*/

View file

@ -4,10 +4,11 @@ import (
"crypto/sha256"
"math/rand"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
)
// ID returns random object.ID.
// ID returns random oid.ID.
func ID() oid.ID {
checksum := [sha256.Size]byte{}
@ -16,7 +17,7 @@ func ID() oid.ID {
return idWithChecksum(checksum)
}
// idWithChecksum returns object.ID initialized
// idWithChecksum returns oid.ID initialized
// with specified checksum.
func idWithChecksum(cs [sha256.Size]byte) oid.ID {
var id oid.ID
@ -24,3 +25,13 @@ func idWithChecksum(cs [sha256.Size]byte) oid.ID {
return id
}
// Address returns random object.Address.
func Address() oid.Address {
var x oid.Address
x.SetContainer(cidtest.ID())
x.SetObject(ID())
return x
}