feat(args): create a specialized type to manage invocation Arguments
This commit is contained in:
206
pkg/args/args.go
Normal file
206
pkg/args/args.go
Normal file
@@ -0,0 +1,206 @@
|
||||
// Package args provides the type that represents the Arguments passed to
|
||||
// a command within an invocation.Token as well as a convenient Add method
|
||||
// to incrementally build the underlying map.
|
||||
package args
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
ts *schema.TypeSystem
|
||||
err error
|
||||
)
|
||||
|
||||
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
|
||||
// by the executor.
|
||||
//
|
||||
// This type must be compatible with the IPLD type represented by the IPLD
|
||||
// schema { String : Any }.
|
||||
type Args struct {
|
||||
Keys []string
|
||||
Values map[string]ipld.Node
|
||||
}
|
||||
|
||||
// New returns a pointer to an initialized Args value.
|
||||
func New() *Args {
|
||||
return &Args{
|
||||
Values: map[string]ipld.Node{},
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return fmt.Errorf("duplicate key %q", key)
|
||||
}
|
||||
|
||||
node, err := anyNode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Values[key] = node
|
||||
m.Keys = append(m.Keys, key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Include merges the provided arguments into the existing arguments.
|
||||
//
|
||||
// If duplicate keys are encountered, the new value is silently dropped
|
||||
// without causing an error.
|
||||
func (m *Args) Include(other *Args) {
|
||||
for _, key := range other.Keys {
|
||||
if _, ok := m.Values[key]; ok {
|
||||
// don't overwrite
|
||||
continue
|
||||
}
|
||||
m.Values[key] = other.Values[key]
|
||||
m.Keys = append(m.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 anyNode(val any) (ipld.Node, error) {
|
||||
var err error
|
||||
|
||||
defer func() {
|
||||
err = handlePanic(recover())
|
||||
}()
|
||||
|
||||
if val == nil {
|
||||
return literal.Null(), nil
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user