New options in PinOptions

This starts handling Metadata and UserAllocations in the PinOptions
object. The Pin protobuf has been modified to embed a PinOptions message which
is defined separately.

Query arguments for the Metadata map are declared by their "meta-" prefix:
"?meta-something=something&meta-something-else-b".

Additional tests have been added, along with an Equals() method for
PinOptions.

License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
This commit is contained in:
Hector Sanjuan 2019-02-20 12:49:30 +00:00
parent f57c5e4066
commit 1003f9335e
4 changed files with 246 additions and 73 deletions

View File

@ -55,19 +55,15 @@ func (Pin_PinType) EnumDescriptor() ([]byte, []int) {
}
type Pin struct {
Cid []byte `protobuf:"bytes,1,opt,name=Cid,proto3" json:"Cid,omitempty"`
Type Pin_PinType `protobuf:"varint,2,opt,name=Type,proto3,enum=api.pb.Pin_PinType" json:"Type,omitempty"`
Allocations [][]byte `protobuf:"bytes,3,rep,name=Allocations,proto3" json:"Allocations,omitempty"`
MaxDepth int32 `protobuf:"zigzag32,4,opt,name=MaxDepth,proto3" json:"MaxDepth,omitempty"`
Reference []byte `protobuf:"bytes,5,opt,name=Reference,proto3" json:"Reference,omitempty"`
ReplicationFactorMin int32 `protobuf:"zigzag32,8,opt,name=ReplicationFactorMin,proto3" json:"ReplicationFactorMin,omitempty"`
ReplicationFactorMax int32 `protobuf:"zigzag32,9,opt,name=ReplicationFactorMax,proto3" json:"ReplicationFactorMax,omitempty"`
Name string `protobuf:"bytes,10,opt,name=Name,proto3" json:"Name,omitempty"`
ShardSize uint64 `protobuf:"varint,11,opt,name=ShardSize,proto3" json:"ShardSize,omitempty"`
Metadata map[string]string `protobuf:"bytes,12,rep,name=Metadata,proto3" json:"Metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
Cid []byte `protobuf:"bytes,1,opt,name=Cid,proto3" json:"Cid,omitempty"`
Type Pin_PinType `protobuf:"varint,2,opt,name=Type,proto3,enum=api.pb.Pin_PinType" json:"Type,omitempty"`
Allocations [][]byte `protobuf:"bytes,3,rep,name=Allocations,proto3" json:"Allocations,omitempty"`
MaxDepth int32 `protobuf:"zigzag32,4,opt,name=MaxDepth,proto3" json:"MaxDepth,omitempty"`
Reference []byte `protobuf:"bytes,5,opt,name=Reference,proto3" json:"Reference,omitempty"`
Options *PinOptions `protobuf:"bytes,6,opt,name=Options,proto3" json:"Options,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Pin) Reset() { *m = Pin{} }
@ -130,35 +126,86 @@ func (m *Pin) GetReference() []byte {
return nil
}
func (m *Pin) GetReplicationFactorMin() int32 {
func (m *Pin) GetOptions() *PinOptions {
if m != nil {
return m.Options
}
return nil
}
type PinOptions struct {
ReplicationFactorMin int32 `protobuf:"zigzag32,1,opt,name=ReplicationFactorMin,proto3" json:"ReplicationFactorMin,omitempty"`
ReplicationFactorMax int32 `protobuf:"zigzag32,2,opt,name=ReplicationFactorMax,proto3" json:"ReplicationFactorMax,omitempty"`
Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"`
ShardSize uint64 `protobuf:"varint,4,opt,name=ShardSize,proto3" json:"ShardSize,omitempty"`
UserAllocations []string `protobuf:"bytes,5,rep,name=UserAllocations,proto3" json:"UserAllocations,omitempty"`
Metadata map[string]string `protobuf:"bytes,6,rep,name=Metadata,proto3" json:"Metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *PinOptions) Reset() { *m = PinOptions{} }
func (m *PinOptions) String() string { return proto.CompactTextString(m) }
func (*PinOptions) ProtoMessage() {}
func (*PinOptions) Descriptor() ([]byte, []int) {
return fileDescriptor_d938547f84707355, []int{1}
}
func (m *PinOptions) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PinOptions.Unmarshal(m, b)
}
func (m *PinOptions) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_PinOptions.Marshal(b, m, deterministic)
}
func (m *PinOptions) XXX_Merge(src proto.Message) {
xxx_messageInfo_PinOptions.Merge(m, src)
}
func (m *PinOptions) XXX_Size() int {
return xxx_messageInfo_PinOptions.Size(m)
}
func (m *PinOptions) XXX_DiscardUnknown() {
xxx_messageInfo_PinOptions.DiscardUnknown(m)
}
var xxx_messageInfo_PinOptions proto.InternalMessageInfo
func (m *PinOptions) GetReplicationFactorMin() int32 {
if m != nil {
return m.ReplicationFactorMin
}
return 0
}
func (m *Pin) GetReplicationFactorMax() int32 {
func (m *PinOptions) GetReplicationFactorMax() int32 {
if m != nil {
return m.ReplicationFactorMax
}
return 0
}
func (m *Pin) GetName() string {
func (m *PinOptions) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *Pin) GetShardSize() uint64 {
func (m *PinOptions) GetShardSize() uint64 {
if m != nil {
return m.ShardSize
}
return 0
}
func (m *Pin) GetMetadata() map[string]string {
func (m *PinOptions) GetUserAllocations() []string {
if m != nil {
return m.UserAllocations
}
return nil
}
func (m *PinOptions) GetMetadata() map[string]string {
if m != nil {
return m.Metadata
}
@ -168,33 +215,37 @@ func (m *Pin) GetMetadata() map[string]string {
func init() {
proto.RegisterEnum("api.pb.Pin_PinType", Pin_PinType_name, Pin_PinType_value)
proto.RegisterType((*Pin)(nil), "api.pb.Pin")
proto.RegisterMapType((map[string]string)(nil), "api.pb.Pin.MetadataEntry")
proto.RegisterType((*PinOptions)(nil), "api.pb.PinOptions")
proto.RegisterMapType((map[string]string)(nil), "api.pb.PinOptions.MetadataEntry")
}
func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) }
var fileDescriptor_d938547f84707355 = []byte{
// 352 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x4d, 0x0f, 0xd2, 0x30,
0x18, 0xb6, 0xac, 0xb0, 0xed, 0xdd, 0x20, 0xf3, 0x95, 0x43, 0x25, 0x1e, 0x1a, 0x2e, 0xee, 0xb4,
0x03, 0xc6, 0xc4, 0xe8, 0x09, 0x41, 0x4d, 0x48, 0x30, 0xa4, 0xe8, 0x0f, 0x28, 0xac, 0x86, 0xc6,
0xb9, 0x2d, 0xa3, 0x18, 0xe6, 0x7f, 0xf0, 0x3f, 0x9b, 0x76, 0x08, 0x98, 0xe0, 0xa1, 0xc9, 0xf3,
0xf1, 0x7e, 0xb4, 0x4f, 0x0a, 0x91, 0x69, 0x6b, 0x75, 0xcc, 0xea, 0xa6, 0x32, 0x15, 0x0e, 0x64,
0xad, 0xb3, 0x7a, 0x37, 0xfd, 0x4d, 0xc1, 0xdb, 0xe8, 0x12, 0x13, 0xf0, 0x16, 0x3a, 0x67, 0x84,
0x93, 0x34, 0x16, 0x16, 0xe2, 0x4b, 0xa0, 0x5f, 0xda, 0x5a, 0xb1, 0x1e, 0x27, 0xe9, 0x68, 0xf6,
0x2c, 0xeb, 0x1a, 0xb2, 0x8d, 0x2e, 0xed, 0xb1, 0x96, 0x70, 0x05, 0xc8, 0x21, 0x9a, 0x17, 0x45,
0xb5, 0x97, 0x46, 0x57, 0xe5, 0x91, 0x79, 0xdc, 0x4b, 0x63, 0x71, 0x2f, 0xe1, 0x04, 0x82, 0xb5,
0x3c, 0x2f, 0x55, 0x6d, 0x0e, 0x8c, 0x72, 0x92, 0x3e, 0x15, 0x57, 0x8e, 0x2f, 0x20, 0x14, 0xea,
0x9b, 0x6a, 0x54, 0xb9, 0x57, 0xac, 0xef, 0xd6, 0xdf, 0x04, 0x9c, 0xc1, 0x58, 0xa8, 0xba, 0xd0,
0xdd, 0xa4, 0x8f, 0x72, 0x6f, 0xaa, 0x66, 0xad, 0x4b, 0x16, 0xb8, 0x29, 0x0f, 0xbd, 0xc7, 0x3d,
0xf2, 0xcc, 0xc2, 0xff, 0xf5, 0xc8, 0x33, 0x22, 0xd0, 0xcf, 0xf2, 0x87, 0x62, 0xc0, 0x49, 0x1a,
0x0a, 0x87, 0xed, 0xcd, 0xb6, 0x07, 0xd9, 0xe4, 0x5b, 0xfd, 0x4b, 0xb1, 0x88, 0x93, 0x94, 0x8a,
0x9b, 0x80, 0xaf, 0x21, 0x58, 0x2b, 0x23, 0x73, 0x69, 0x24, 0x8b, 0xb9, 0x97, 0x46, 0xb3, 0xe7,
0xf7, 0x11, 0xfd, 0xf5, 0x3e, 0x94, 0xa6, 0x69, 0xc5, 0xb5, 0x74, 0xf2, 0x0e, 0x86, 0xff, 0x58,
0x36, 0xf8, 0xef, 0xaa, 0x75, 0xc1, 0x87, 0xc2, 0x42, 0x1c, 0x43, 0xff, 0xa7, 0x2c, 0x4e, 0x5d,
0xf2, 0xa1, 0xe8, 0xc8, 0xdb, 0xde, 0x1b, 0x32, 0xfd, 0x0a, 0xfe, 0x25, 0x7a, 0x8c, 0xc0, 0x7f,
0x2f, 0x73, 0x0b, 0x93, 0x27, 0x18, 0x43, 0xb0, 0x94, 0x46, 0x3a, 0x46, 0x2c, 0xb3, 0x2b, 0x1c,
0xeb, 0x21, 0xc2, 0x68, 0x51, 0x9c, 0x8e, 0x46, 0x35, 0xcb, 0xf9, 0x27, 0xa7, 0x79, 0x38, 0xbc,
0xbc, 0xcc, 0x51, 0xba, 0xa2, 0xc1, 0x20, 0xf1, 0x57, 0x34, 0xf0, 0x93, 0x60, 0x37, 0x70, 0xdf,
0xe3, 0xd5, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x63, 0x03, 0x06, 0x76, 0x2d, 0x02, 0x00, 0x00,
// 386 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x4d, 0x8f, 0xd3, 0x30,
0x10, 0xc5, 0x71, 0x9a, 0x6e, 0x26, 0xdd, 0x65, 0x77, 0xd8, 0x43, 0xb4, 0xe2, 0x60, 0xf5, 0x82,
0x0f, 0x28, 0x87, 0x70, 0x41, 0xc0, 0xa5, 0xb4, 0xc0, 0xa9, 0x50, 0xb9, 0xf4, 0x07, 0xb8, 0xad,
0x51, 0x2d, 0x42, 0x62, 0x25, 0x2e, 0x6a, 0xf8, 0x37, 0xfc, 0x1e, 0xfe, 0x14, 0xb2, 0xd3, 0x2f,
0xd8, 0x1e, 0x22, 0xcd, 0x7b, 0x6f, 0x5e, 0x66, 0xf2, 0x32, 0x90, 0xd8, 0xd6, 0xa8, 0x26, 0x33,
0x75, 0x65, 0x2b, 0x8c, 0xa4, 0xd1, 0x99, 0x59, 0x0e, 0x7f, 0x07, 0x40, 0x67, 0xba, 0xc4, 0x5b,
0xa0, 0x63, 0xbd, 0x4e, 0x09, 0x23, 0x7c, 0x20, 0x5c, 0x89, 0x2f, 0x20, 0xfc, 0xda, 0x1a, 0x95,
0x06, 0x8c, 0xf0, 0x9b, 0xfc, 0x59, 0xd6, 0x19, 0xb2, 0x99, 0x2e, 0xdd, 0xe3, 0x24, 0xe1, 0x1b,
0x90, 0x41, 0x32, 0x2a, 0x8a, 0x6a, 0x25, 0xad, 0xae, 0xca, 0x26, 0xa5, 0x8c, 0xf2, 0x81, 0x38,
0xa7, 0xf0, 0x01, 0xae, 0xa6, 0x72, 0x37, 0x51, 0xc6, 0x6e, 0xd2, 0x90, 0x11, 0x7e, 0x27, 0x8e,
0x18, 0x9f, 0x43, 0x2c, 0xd4, 0x37, 0x55, 0xab, 0x72, 0xa5, 0xd2, 0x9e, 0x1f, 0x7f, 0x22, 0xf0,
0x25, 0xf4, 0xbf, 0x98, 0xee, 0xbd, 0x11, 0x23, 0x3c, 0xc9, 0xf1, 0x6c, 0x8f, 0xbd, 0x22, 0x0e,
0x2d, 0xc3, 0x05, 0xf4, 0xf7, 0xab, 0x61, 0x02, 0xfd, 0xf7, 0x72, 0xed, 0xca, 0xdb, 0x27, 0x38,
0x80, 0xab, 0x89, 0xb4, 0xd2, 0x23, 0xe2, 0xd0, 0x54, 0xed, 0x51, 0x80, 0x08, 0x37, 0xe3, 0x62,
0xdb, 0x58, 0x55, 0x4f, 0x46, 0x9f, 0x3c, 0x47, 0xf1, 0x1a, 0xe2, 0xf9, 0x46, 0xd6, 0x9d, 0x3d,
0x1c, 0xfe, 0x09, 0x00, 0x4e, 0xe3, 0x30, 0x87, 0x7b, 0xa1, 0x4c, 0xa1, 0xbb, 0xaf, 0xfb, 0x28,
0x57, 0xb6, 0xaa, 0xa7, 0xba, 0xf4, 0xd9, 0xdd, 0x89, 0x8b, 0xda, 0x65, 0x8f, 0xdc, 0xf9, 0x70,
0x2f, 0x7a, 0xe4, 0x0e, 0x11, 0xc2, 0xcf, 0xf2, 0x87, 0x4a, 0x29, 0x23, 0x3c, 0x16, 0xbe, 0x76,
0x69, 0xf9, 0xcd, 0xe6, 0xfa, 0x97, 0xf2, 0x51, 0x86, 0xe2, 0x44, 0x20, 0x87, 0xa7, 0x8b, 0x46,
0xd5, 0xe7, 0x7f, 0xa3, 0xc7, 0x28, 0x8f, 0xc5, 0xff, 0x34, 0xbe, 0xeb, 0x32, 0x58, 0x4b, 0x2b,
0xd3, 0x88, 0x51, 0x9e, 0xe4, 0xec, 0x71, 0xb0, 0xd9, 0xa1, 0xe5, 0x43, 0x69, 0xeb, 0x56, 0x1c,
0x1d, 0x0f, 0x6f, 0xe1, 0xfa, 0x1f, 0xc9, 0x5d, 0xcf, 0x77, 0xd5, 0xfa, 0x04, 0x62, 0xe1, 0x4a,
0xbc, 0x87, 0xde, 0x4f, 0x59, 0x6c, 0xbb, 0xf3, 0x89, 0x45, 0x07, 0xde, 0x04, 0xaf, 0xc9, 0x32,
0xf2, 0x07, 0xf8, 0xea, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xda, 0x40, 0x89, 0x72, 0x8f, 0x02,
0x00, 0x00,
}

View File

@ -14,10 +14,14 @@ message Pin {
repeated bytes Allocations = 3;
sint32 MaxDepth = 4;
bytes Reference = 5;
reserved 6,7;
sint32 ReplicationFactorMin = 8;
sint32 ReplicationFactorMax = 9;
string Name = 10;
uint64 ShardSize = 11;
map<string, string> Metadata = 12;
PinOptions Options = 6;
}
message PinOptions {
sint32 ReplicationFactorMin = 1;
sint32 ReplicationFactorMax = 2;
string Name = 3;
uint64 ShardSize = 4;
repeated string UserAllocations = 5;
map<string, string> Metadata = 6;
}

View File

@ -707,21 +707,72 @@ func (pT PinType) String() string {
}
}
var pinOptionsMetaPrefix = "meta-"
// PinOptions wraps user-defined options for Pins
type PinOptions struct {
ReplicationFactorMin int `json:"replication_factor_min" codec:"rn,omitempty"`
ReplicationFactorMax int `json:"replication_factor_max" codec:"rx,omitempty"`
Name string `json:"name" codec:"n,omitempty"`
ShardSize uint64 `json:"shard_size" codec:"s,omitempty"`
UserAllocations []string `json:"user_allocations" codec:"ua,omitempty"`
Metadata map[string]string `json:"metadata" codec:"m,omitempty"`
}
// Equals returns true of two PinOption objects are equivalent.
func (po *PinOptions) Equals(po2 *PinOptions) bool {
if po.ReplicationFactorMax != po2.ReplicationFactorMax {
return false
}
if po.ReplicationFactorMin != po2.ReplicationFactorMin {
return false
}
if po.ShardSize != po2.ShardSize {
return false
}
lenAllocs1 := len(po.UserAllocations)
lenAllocs2 := len(po2.UserAllocations)
if lenAllocs1 != lenAllocs2 {
return false
}
// avoid side effecs in the original objects
allocs1 := make([]string, lenAllocs1, lenAllocs1)
allocs2 := make([]string, lenAllocs2, lenAllocs2)
copy(allocs1, po.UserAllocations)
copy(allocs2, po2.UserAllocations)
sort.Strings(allocs1)
sort.Strings(allocs2)
if strings.Join(allocs1, ",") != strings.Join(allocs2, ",") {
return false
}
for k, v := range po.Metadata {
v2 := po2.Metadata[k]
if k != "" && v != v2 {
return false
}
}
return true
}
// ToQuery returns the PinOption as query arguments.
func (po *PinOptions) ToQuery() string {
q := url.Values{}
q.Set("replication-min", fmt.Sprintf("%d", po.ReplicationFactorMin))
q.Set("replication-max", fmt.Sprintf("%d", po.ReplicationFactorMax))
q.Set("name", po.Name)
q.Set("shard-size", fmt.Sprintf("%d", po.ShardSize))
q.Set("user-allocations", strings.Join(po.UserAllocations, ","))
for k, v := range po.Metadata {
if k == "" {
continue
}
q.Set(fmt.Sprintf("%s%s", pinOptionsMetaPrefix, k), v)
}
return q.Encode()
}
@ -750,6 +801,26 @@ func (po *PinOptions) FromQuery(q url.Values) {
if rpl, err := strconv.Atoi(rplStrMax); err == nil {
po.ReplicationFactorMax = rpl
}
if shsize, err := strconv.ParseUint(q.Get("shard-size"), 10, 64); err == nil {
po.ShardSize = shsize
}
if allocs := q.Get("user-allocations"); allocs != "" {
po.UserAllocations = strings.Split(allocs, ",")
}
po.Metadata = make(map[string]string)
for k := range q {
if !strings.HasPrefix(k, pinOptionsMetaPrefix) {
continue
}
metaKey := strings.TrimPrefix(k, pinOptionsMetaPrefix)
if metaKey == "" {
continue
}
po.Metadata[metaKey] = q.Get(k)
}
}
// Pin carries all the information associated to a CID that is pinned
@ -858,18 +929,23 @@ func (pin *Pin) ProtoMarshal() ([]byte, error) {
allocs[i] = bs
}
pbPin := &pb.Pin{
Cid: pin.Cid.Bytes(),
Type: convertPinType(pin.Type),
Allocations: allocs,
MaxDepth: int32(pin.MaxDepth),
Reference: pin.Reference.Bytes(),
opts := &pb.PinOptions{
ReplicationFactorMin: int32(pin.ReplicationFactorMin),
ReplicationFactorMax: int32(pin.ReplicationFactorMax),
Name: pin.Name,
ShardSize: pin.ShardSize,
UserAllocations: pin.UserAllocations,
Metadata: pin.Metadata,
}
pbPin := &pb.Pin{
Cid: pin.Cid.Bytes(),
Type: convertPinType(pin.Type),
Allocations: allocs,
MaxDepth: int32(pin.MaxDepth),
Reference: pin.Reference.Bytes(),
Options: opts,
}
return proto.Marshal(pbPin)
}
@ -910,11 +986,14 @@ func (pin *Pin) ProtoUnmarshal(data []byte) error {
pin.Reference = ref
}
pin.Reference = ref
pin.ReplicationFactorMin = int(pbPin.GetReplicationFactorMin())
pin.ReplicationFactorMax = int(pbPin.GetReplicationFactorMax())
pin.Name = pbPin.GetName()
pin.ShardSize = pbPin.GetShardSize()
pin.Metadata = pbPin.GetMetadata()
opts := pbPin.GetOptions()
pin.ReplicationFactorMin = int(opts.GetReplicationFactorMin())
pin.ReplicationFactorMax = int(opts.GetReplicationFactorMax())
pin.Name = opts.GetName()
pin.ShardSize = opts.GetShardSize()
pin.UserAllocations = opts.GetUserAllocations()
pin.Metadata = opts.GetMetadata()
return nil
}
@ -941,7 +1020,7 @@ func (pin Pin) Equals(pin2 Pin) bool {
return false
}
if pin1s.ShardSize != pin2s.ShardSize {
if pin1s.Reference != pin2s.Reference {
return false
}
@ -952,19 +1031,7 @@ func (pin Pin) Equals(pin2 Pin) bool {
return false
}
if pin1s.ReplicationFactorMax != pin2s.ReplicationFactorMax {
return false
}
if pin1s.ReplicationFactorMin != pin2s.ReplicationFactorMin {
return false
}
if pin1s.Reference != pin2s.Reference {
return false
}
return true
return pin.PinOptions.Equals(&pin2.PinOptions)
}
// IsRemotePin determines whether a Pin's ReplicationFactor has

View File

@ -2,6 +2,7 @@ package api
import (
"fmt"
"net/url"
"reflect"
"strings"
"testing"
@ -349,3 +350,53 @@ func TestPinTags(t *testing.T) {
typ := reflect.TypeOf(PinSerial{})
checkDupTags(t, "codec", typ, nil)
}
func TestPinOptionsQuery(t *testing.T) {
testcases := []*PinOptions{
&PinOptions{
ReplicationFactorMax: 3,
ReplicationFactorMin: 2,
Name: "abc",
ShardSize: 33,
UserAllocations: []string{"host1", "host2"},
Metadata: map[string]string{
"hello": "bye",
"hello2": "bye2",
},
},
&PinOptions{
ReplicationFactorMax: -1,
ReplicationFactorMin: 0,
Name: "",
ShardSize: 0,
UserAllocations: []string{},
Metadata: nil,
},
&PinOptions{
ReplicationFactorMax: -1,
ReplicationFactorMin: 0,
Name: "",
ShardSize: 0,
UserAllocations: nil,
Metadata: map[string]string{
"": "bye",
},
},
}
for _, tc := range testcases {
queryStr := tc.ToQuery()
q, err := url.ParseQuery(queryStr)
if err != nil {
t.Error("error parsing query", err)
}
po2 := &PinOptions{}
po2.FromQuery(q)
if !tc.Equals(po2) {
t.Error("expected equal PinOptions")
t.Error(queryStr)
t.Errorf("%+v\n", tc)
t.Errorf("%+v\n", po2)
}
}
}