Skip to content

Commit

Permalink
args: simplify API + code
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMure committed Nov 12, 2024
1 parent 633b3d2 commit 522181b
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 204 deletions.
175 changes: 32 additions & 143 deletions pkg/args/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,17 @@ package args

import (
"fmt"
"reflect"
"sync"
"sort"

"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/schema"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)

const (
argsSchema = "type Args { String : Any }"
argsName = "Args"
)
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/fluent/qp"
"github.com/ipld/go-ipld-prime/node/basicnode"

var (
once sync.Once
ts *schema.TypeSystem
err error
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)

func argsType() schema.Type {
once.Do(func() {
ts, err = ipld.LoadSchemaBytes([]byte(argsSchema))
})
if err != nil {
panic(err)
}

return ts.TypeByName(argsName)
}

var ErrUnsupported = fmt.Errorf("failure adding unsupported type to meta")

// Args are the Command's argumennts when an invocation Token is processed
// Args are the Command's arguments when an invocation Token is processed
// by the executor.
//
// This type must be compatible with the IPLD type represented by the IPLD
Expand All @@ -56,41 +32,23 @@ func New() *Args {
}
}

// FromIPLD unwraps an Args instance from an ipld.Node.
func FromIPLD(node ipld.Node) (*Args, error) {
var err error

defer func() {
err = handlePanic(recover())
}()

obj := bindnode.Unwrap(node)

args, ok := obj.(*Args)
if !ok {
err = fmt.Errorf("failed to convert to Args")
}

return args, err
}

// Add inserts a key/value pair in the Args set.
//
// Accepted types for val are: bool, string, int, int8, int16,
// int32, int64, uint, uint8, uint16, uint32, float32, float64, []byte,
// []any, map[string]any, ipld.Node and nil.
func (m *Args) Add(key string, val any) error {
if _, ok := m.Values[key]; ok {
func (a *Args) Add(key string, val any) error {
if _, ok := a.Values[key]; ok {
return fmt.Errorf("duplicate key %q", key)
}

node, err := anyNode(val)
node, err := literal.Any(val)
if err != nil {
return err
}

m.Values[key] = node
m.Keys = append(m.Keys, key)
a.Values[key] = node
a.Keys = append(a.Keys, key)

return nil
}
Expand All @@ -99,108 +57,39 @@ func (m *Args) Add(key string, val any) error {
//
// If duplicate keys are encountered, the new value is silently dropped
// without causing an error.
func (m *Args) Include(other *Args) {
func (a *Args) Include(other *Args) {
for _, key := range other.Keys {
if _, ok := m.Values[key]; ok {
if _, ok := a.Values[key]; ok {
// don't overwrite
continue
}
m.Values[key] = other.Values[key]
m.Keys = append(m.Keys, key)
a.Values[key] = other.Values[key]
a.Keys = append(a.Keys, key)
}
}

// ToIPLD wraps an instance of an Args with an ipld.Node.
func (m *Args) ToIPLD() (ipld.Node, error) {
var err error

defer func() {
err = handlePanic(recover())
}()

return bindnode.Wrap(m, argsType()), err
func (a *Args) ToIPLD() (ipld.Node, error) {
sort.Strings(a.Keys)
return qp.BuildMap(basicnode.Prototype.Any, int64(len(a.Keys)), func(ma datamodel.MapAssembler) {
for _, key := range a.Keys {
qp.MapEntry(ma, key, qp.Node(a.Values[key]))
}
})
}

func anyNode(val any) (ipld.Node, error) {
var err error

defer func() {
err = handlePanic(recover())
}()

if val == nil {
return literal.Null(), nil
// Equals tells if two Args hold the same values.
func (a *Args) Equals(other *Args) bool {
if len(a.Keys) != len(other.Keys) {
return false
}

if cast, ok := val.(ipld.Node); ok {
return cast, nil
}

if cast, ok := val.(cid.Cid); ok {
return literal.LinkCid(cast), err
}

var rv reflect.Value

rv.Kind()

if cast, ok := val.(reflect.Value); ok {
rv = cast
} else {
rv = reflect.ValueOf(val)
}

for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
rv = rv.Elem()
if len(a.Values) != len(other.Values) {
return false
}

switch rv.Kind() {
case reflect.Slice:
if rv.Type().Elem().Kind() == reflect.Uint8 {
return literal.Bytes(val.([]byte)), nil
}

l := make([]reflect.Value, rv.Len())

for i := 0; i < rv.Len(); i++ {
l[i] = rv.Index(i)
}

return literal.List(l)
case reflect.Map:
if rv.Type().Key().Kind() != reflect.String {
return nil, fmt.Errorf("unsupported map key type: %s", rv.Type().Key().Name())
}

m := make(map[string]reflect.Value, rv.Len())
it := rv.MapRange()

for it.Next() {
m[it.Key().String()] = it.Value()
for _, key := range a.Keys {
if !ipld.DeepEqual(a.Values[key], other.Values[key]) {
return false
}

return literal.Map(m)
case reflect.String:
return literal.String(rv.String()), nil
case reflect.Bool:
return literal.Bool(rv.Bool()), nil
// reflect.Int64 may exceed the safe 53-bit limit of JavaScript
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return literal.Int(rv.Int()), nil
// reflect.Uint64 can't be safely converted to int64
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
return literal.Int(int64(rv.Uint())), nil
case reflect.Float32, reflect.Float64:
return literal.Float(rv.Float()), nil
default:
return nil, fmt.Errorf("unsupported Args type: %s", rv.Type().Name())
}
}

func handlePanic(rec any) error {
if err, ok := rec.(error); ok {
return err
}

return fmt.Errorf("%v", rec)
return true
}
Loading

0 comments on commit 522181b

Please sign in to comment.