Merge branch 'v1' into v1-meta-encryption
# Conflicts: # pkg/meta/meta.go
This commit is contained in:
188
pkg/args/args.go
188
pkg/args/args.go
@@ -5,46 +5,23 @@ package args
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"sort"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// Args are the Command's arguments when an invocation Token is processed by the executor.
|
||||||
argsSchema = "type Args { String : Any }"
|
// This also serves as a way to construct the underlying IPLD data with minimum allocations
|
||||||
argsName = "Args"
|
// and transformations, while hiding the IPLD complexity from the caller.
|
||||||
)
|
|
||||||
|
|
||||||
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 {
|
type Args struct {
|
||||||
|
// This type must be compatible with the IPLD type represented by the IPLD
|
||||||
|
// schema { String : Any }.
|
||||||
|
|
||||||
Keys []string
|
Keys []string
|
||||||
Values map[string]ipld.Node
|
Values map[string]ipld.Node
|
||||||
}
|
}
|
||||||
@@ -56,41 +33,21 @@ 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.
|
// Add inserts a key/value pair in the Args set.
|
||||||
//
|
//
|
||||||
// Accepted types for val are: bool, string, int, int8, int16,
|
// Accepted types for val are any CBOR compatible type, or directly IPLD values.
|
||||||
// int32, int64, uint, uint8, uint16, uint32, float32, float64, []byte,
|
func (a *Args) Add(key string, val any) error {
|
||||||
// []any, map[string]any, ipld.Node and nil.
|
if _, ok := a.Values[key]; ok {
|
||||||
func (m *Args) Add(key string, val any) error {
|
|
||||||
if _, ok := m.Values[key]; ok {
|
|
||||||
return fmt.Errorf("duplicate key %q", key)
|
return fmt.Errorf("duplicate key %q", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
node, err := anyNode(val)
|
node, err := literal.Any(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Values[key] = node
|
a.Values[key] = node
|
||||||
m.Keys = append(m.Keys, key)
|
a.Keys = append(a.Keys, key)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -99,108 +56,39 @@ func (m *Args) Add(key string, val any) error {
|
|||||||
//
|
//
|
||||||
// If duplicate keys are encountered, the new value is silently dropped
|
// If duplicate keys are encountered, the new value is silently dropped
|
||||||
// without causing an error.
|
// without causing an error.
|
||||||
func (m *Args) Include(other *Args) {
|
func (a *Args) Include(other *Args) {
|
||||||
for _, key := range other.Keys {
|
for _, key := range other.Keys {
|
||||||
if _, ok := m.Values[key]; ok {
|
if _, ok := a.Values[key]; ok {
|
||||||
// don't overwrite
|
// don't overwrite
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.Values[key] = other.Values[key]
|
a.Values[key] = other.Values[key]
|
||||||
m.Keys = append(m.Keys, key)
|
a.Keys = append(a.Keys, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToIPLD wraps an instance of an Args with an ipld.Node.
|
// ToIPLD wraps an instance of an Args with an ipld.Node.
|
||||||
func (m *Args) ToIPLD() (ipld.Node, error) {
|
func (a *Args) ToIPLD() (ipld.Node, error) {
|
||||||
var err error
|
sort.Strings(a.Keys)
|
||||||
|
return qp.BuildMap(basicnode.Prototype.Any, int64(len(a.Keys)), func(ma datamodel.MapAssembler) {
|
||||||
defer func() {
|
for _, key := range a.Keys {
|
||||||
err = handlePanic(recover())
|
qp.MapEntry(ma, key, qp.Node(a.Values[key]))
|
||||||
}()
|
}
|
||||||
|
})
|
||||||
return bindnode.Wrap(m, argsType()), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func anyNode(val any) (ipld.Node, error) {
|
// Equals tells if two Args hold the same values.
|
||||||
var err error
|
func (a *Args) Equals(other *Args) bool {
|
||||||
|
if len(a.Keys) != len(other.Keys) {
|
||||||
defer func() {
|
return false
|
||||||
err = handlePanic(recover())
|
|
||||||
}()
|
|
||||||
|
|
||||||
if val == nil {
|
|
||||||
return literal.Null(), nil
|
|
||||||
}
|
}
|
||||||
|
if len(a.Values) != len(other.Values) {
|
||||||
if cast, ok := val.(ipld.Node); ok {
|
return false
|
||||||
return cast, nil
|
|
||||||
}
|
}
|
||||||
|
for _, key := range a.Keys {
|
||||||
if cast, ok := val.(cid.Cid); ok {
|
if !ipld.DeepEqual(a.Values[key], other.Values[key]) {
|
||||||
return literal.LinkCid(cast), err
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
|
||||||
func handlePanic(rec any) error {
|
|
||||||
if err, ok := rec.(error); ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%v", rec)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,40 +8,14 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
"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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func argsPrototype() schema.TypedPrototype {
|
|
||||||
return bindnode.Prototype((*args.Args)(nil), argsType())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArgs(t *testing.T) {
|
func TestArgs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -78,9 +52,6 @@ func TestArgs(t *testing.T) {
|
|||||||
|
|
||||||
argsIn := args.New()
|
argsIn := args.New()
|
||||||
|
|
||||||
// WARNING: Do not change the order of these statements as this is the
|
|
||||||
// order which will be present when decoded from DAG-CBOR (
|
|
||||||
// per RFC7049 default canonical ordering?).
|
|
||||||
for _, a := range []struct {
|
for _, a := range []struct {
|
||||||
key string
|
key string
|
||||||
val any
|
val any
|
||||||
@@ -91,8 +62,8 @@ func TestArgs(t *testing.T) {
|
|||||||
{key: boolKey, val: expBoolVal},
|
{key: boolKey, val: expBoolVal},
|
||||||
{key: linkKey, val: expLinkVal},
|
{key: linkKey, val: expLinkVal},
|
||||||
{key: listKey, val: expListVal},
|
{key: listKey, val: expListVal},
|
||||||
{key: nodeKey, val: expNodeVal},
|
|
||||||
{key: uintKey, val: expUintVal},
|
{key: uintKey, val: expUintVal},
|
||||||
|
{key: nodeKey, val: expNodeVal},
|
||||||
{key: bytesKey, val: expBytesVal},
|
{key: bytesKey, val: expBytesVal},
|
||||||
{key: floatKey, val: expFloatVal},
|
{key: floatKey, val: expFloatVal},
|
||||||
{key: stringKey, val: expStringVal},
|
{key: stringKey, val: expStringVal},
|
||||||
@@ -100,21 +71,19 @@ func TestArgs(t *testing.T) {
|
|||||||
require.NoError(t, argsIn.Add(a.key, a.val))
|
require.NoError(t, argsIn.Add(a.key, a.val))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round-trip to DAG-CBOR here as ToIPLD/FromIPLD is only a wrapper
|
// Round-trip to DAG-CBOR
|
||||||
argsOut := roundTripThroughDAGCBOR(t, argsIn)
|
argsOut := roundTripThroughDAGCBOR(t, argsIn)
|
||||||
assert.Equal(t, argsIn, argsOut)
|
assert.ElementsMatch(t, argsIn.Keys, argsOut.Keys)
|
||||||
|
assert.Equal(t, argsIn.Values, argsOut.Values)
|
||||||
|
|
||||||
actMapVal := map[string]string{}
|
actMapVal := map[string]string{}
|
||||||
mit := argsOut.Values[mapKey].MapIterator()
|
mit := argsOut.Values[mapKey].MapIterator()
|
||||||
|
|
||||||
es := errorSwallower(t)
|
|
||||||
|
|
||||||
for !mit.Done() {
|
for !mit.Done() {
|
||||||
k, v, err := mit.Next()
|
k, v, err := mit.Next()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ks := es(k.AsString()).(string)
|
ks := must(k.AsString())
|
||||||
vs := es(v.AsString()).(string)
|
vs := must(v.AsString())
|
||||||
|
|
||||||
actMapVal[ks] = vs
|
actMapVal[ks] = vs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,23 +93,23 @@ func TestArgs(t *testing.T) {
|
|||||||
for !lit.Done() {
|
for !lit.Done() {
|
||||||
_, v, err := lit.Next()
|
_, v, err := lit.Next()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
vs := es(v.AsString()).(string)
|
vs := must(v.AsString())
|
||||||
|
|
||||||
actListVal = append(actListVal, vs)
|
actListVal = append(actListVal, vs)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, expIntVal, es(argsOut.Values[intKey].AsInt()))
|
assert.Equal(t, expIntVal, must(argsOut.Values[intKey].AsInt()))
|
||||||
assert.Equal(t, expMapVal, actMapVal) // TODO: special accessor
|
assert.Equal(t, expMapVal, actMapVal) // TODO: special accessor
|
||||||
// TODO: the nil map comes back empty (but the right type)
|
// TODO: the nil map comes back empty (but the right type)
|
||||||
// assert.Equal(t, expNilVal, actNilVal)
|
// assert.Equal(t, expNilVal, actNilVal)
|
||||||
assert.Equal(t, expBoolVal, es(argsOut.Values[boolKey].AsBool()))
|
assert.Equal(t, expBoolVal, must(argsOut.Values[boolKey].AsBool()))
|
||||||
assert.Equal(t, expLinkVal.String(), es(argsOut.Values[linkKey].AsLink()).(datamodel.Link).String()) // TODO: special accessor
|
assert.Equal(t, expLinkVal.String(), must(argsOut.Values[linkKey].AsLink()).(datamodel.Link).String()) // TODO: special accessor
|
||||||
assert.Equal(t, expListVal, actListVal) // TODO: special accessor
|
assert.Equal(t, expListVal, actListVal) // TODO: special accessor
|
||||||
assert.Equal(t, expNodeVal, argsOut.Values[nodeKey])
|
assert.Equal(t, expNodeVal, argsOut.Values[nodeKey])
|
||||||
assert.Equal(t, expUintVal, uint(es(argsOut.Values[uintKey].AsInt()).(int64)))
|
assert.Equal(t, expUintVal, uint(must(argsOut.Values[uintKey].AsInt())))
|
||||||
assert.Equal(t, expBytesVal, es(argsOut.Values[bytesKey].AsBytes()))
|
assert.Equal(t, expBytesVal, must(argsOut.Values[bytesKey].AsBytes()))
|
||||||
assert.Equal(t, expFloatVal, es(argsOut.Values[floatKey].AsFloat()))
|
assert.Equal(t, expFloatVal, must(argsOut.Values[floatKey].AsFloat()))
|
||||||
assert.Equal(t, expStringVal, es(argsOut.Values[stringKey].AsString()))
|
assert.Equal(t, expStringVal, must(argsOut.Values[stringKey].AsString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgs_Include(t *testing.T) {
|
func TestArgs_Include(t *testing.T) {
|
||||||
@@ -157,21 +126,33 @@ func TestArgs_Include(t *testing.T) {
|
|||||||
|
|
||||||
argsIn.Include(argsOther)
|
argsIn.Include(argsOther)
|
||||||
|
|
||||||
es := errorSwallower(t)
|
|
||||||
|
|
||||||
assert.Len(t, argsIn.Values, 4)
|
assert.Len(t, argsIn.Values, 4)
|
||||||
assert.Equal(t, "val1", es(argsIn.Values["key1"].AsString()))
|
assert.Equal(t, "val1", must(argsIn.Values["key1"].AsString()))
|
||||||
assert.Equal(t, "val2", es(argsIn.Values["key2"].AsString()))
|
assert.Equal(t, "val2", must(argsIn.Values["key2"].AsString()))
|
||||||
assert.Equal(t, "val3", es(argsIn.Values["key3"].AsString()))
|
assert.Equal(t, "val3", must(argsIn.Values["key3"].AsString()))
|
||||||
assert.Equal(t, "val4", es(argsIn.Values["key4"].AsString()))
|
assert.Equal(t, "val4", must(argsIn.Values["key4"].AsString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorSwallower(t *testing.T) func(any, error) any {
|
const (
|
||||||
return func(val any, err error) any {
|
argsSchema = "type Args { String : Any }"
|
||||||
require.NoError(t, err)
|
argsName = "Args"
|
||||||
|
)
|
||||||
|
|
||||||
return val
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func roundTripThroughDAGCBOR(t *testing.T, argsIn *args.Args) *args.Args {
|
func roundTripThroughDAGCBOR(t *testing.T, argsIn *args.Args) *args.Args {
|
||||||
@@ -182,11 +163,17 @@ func roundTripThroughDAGCBOR(t *testing.T, argsIn *args.Args) *args.Args {
|
|||||||
|
|
||||||
data, err := ipld.Encode(node, dagcbor.Encode)
|
data, err := ipld.Encode(node, dagcbor.Encode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
node, err = ipld.DecodeUsingPrototype(data, dagcbor.Decode, argsPrototype())
|
|
||||||
|
var argsOut args.Args
|
||||||
|
_, err = ipld.Unmarshal(data, dagcbor.Decode, &argsOut, argsType())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
argsOut, err := args.FromIPLD(node)
|
return &argsOut
|
||||||
require.NoError(t, err)
|
}
|
||||||
|
|
||||||
return argsOut
|
func must[T any](t T, err error) T {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,16 @@ package meta
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
|
||||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
|
||||||
"github.com/ipld/go-ipld-prime/printer"
|
"github.com/ipld/go-ipld-prime/printer"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta/internal/crypto"
|
"github.com/ucan-wg/go-ucan/pkg/meta/internal/crypto"
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||||
|
)
|
||||||
var ErrUnsupported = errors.New("failure adding unsupported type to meta")
|
var ErrUnsupported = errors.New("failure adding unsupported type to meta")
|
||||||
|
|
||||||
var ErrNotFound = errors.New("key-value not found in meta")
|
var ErrNotFound = errors.New("key-value not found in meta")
|
||||||
@@ -21,9 +20,12 @@ var ErrNotFound = errors.New("key-value not found in meta")
|
|||||||
var ErrNotEncryptable = errors.New("value of this type cannot be encrypted")
|
var ErrNotEncryptable = errors.New("value of this type cannot be encrypted")
|
||||||
|
|
||||||
// Meta is a container for meta key-value pairs in a UCAN token.
|
// Meta is a container for meta key-value pairs in a UCAN token.
|
||||||
// This also serves as a way to construct the underlying IPLD data with minimum allocations and transformations,
|
// This also serves as a way to construct the underlying IPLD data with minimum allocations
|
||||||
// while hiding the IPLD complexity from the caller.
|
// and transformations, while hiding the IPLD complexity from the caller.
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
|
// This type must be compatible with the IPLD type represented by the IPLD
|
||||||
|
// schema { String : Any }.
|
||||||
|
|
||||||
Keys []string
|
Keys []string
|
||||||
Values map[string]ipld.Node
|
Values map[string]ipld.Node
|
||||||
}
|
}
|
||||||
@@ -130,35 +132,20 @@ func (m *Meta) GetNode(key string) (ipld.Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a key/value pair in the meta set.
|
// Add adds a key/value pair in the meta set.
|
||||||
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
|
// Accepted types for val are any CBOR compatible type, or directly IPLD values.
|
||||||
// and ipld.Node.
|
|
||||||
func (m *Meta) Add(key string, val any) error {
|
func (m *Meta) Add(key string, val any) error {
|
||||||
if _, ok := m.Values[key]; ok {
|
if _, ok := m.Values[key]; ok {
|
||||||
return fmt.Errorf("duplicate key %q", key)
|
return fmt.Errorf("duplicate key %q", key)
|
||||||
}
|
}
|
||||||
switch val := val.(type) {
|
|
||||||
case bool:
|
node, err := literal.Any(val)
|
||||||
m.Values[key] = basicnode.NewBool(val)
|
if err != nil {
|
||||||
case string:
|
return err
|
||||||
m.Values[key] = basicnode.NewString(val)
|
|
||||||
case int:
|
|
||||||
m.Values[key] = basicnode.NewInt(int64(val))
|
|
||||||
case int32:
|
|
||||||
m.Values[key] = basicnode.NewInt(int64(val))
|
|
||||||
case int64:
|
|
||||||
m.Values[key] = basicnode.NewInt(val)
|
|
||||||
case float32:
|
|
||||||
m.Values[key] = basicnode.NewFloat(float64(val))
|
|
||||||
case float64:
|
|
||||||
m.Values[key] = basicnode.NewFloat(val)
|
|
||||||
case []byte:
|
|
||||||
m.Values[key] = basicnode.NewBytes(val)
|
|
||||||
case datamodel.Node:
|
|
||||||
m.Values[key] = val
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: %s", ErrUnsupported, fqtn(val))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Keys = append(m.Keys, key)
|
m.Keys = append(m.Keys, key)
|
||||||
|
m.Values[key] = node
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,15 +213,3 @@ func (m *Meta) String() string {
|
|||||||
func (m *Meta) ReadOnly() ReadOnly {
|
func (m *Meta) ReadOnly() ReadOnly {
|
||||||
return ReadOnly{m: m}
|
return ReadOnly{m: m}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fqtn(val any) string {
|
|
||||||
var name string
|
|
||||||
|
|
||||||
t := reflect.TypeOf(val)
|
|
||||||
for t.Kind() == reflect.Pointer {
|
|
||||||
name += "*"
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
return name + t.PkgPath() + "." + t.Name()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gotest.tools/v3/assert"
|
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
)
|
)
|
||||||
@@ -19,8 +18,7 @@ func TestMeta_Add(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
|
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
|
||||||
require.ErrorIs(t, err, meta.ErrUnsupported)
|
require.Error(t, err)
|
||||||
assert.ErrorContains(t, err, "*github.com/ucan-wg/go-ucan/pkg/meta_test.Unsupported")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("encrypted meta", func(t *testing.T) {
|
t.Run("encrypted meta", func(t *testing.T) {
|
||||||
|
|||||||
@@ -55,6 +55,64 @@ func List[T any](l []T) (ipld.Node, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any creates an IPLD node from any value
|
||||||
|
// If possible, use another dedicated function for your type for performance.
|
||||||
|
func Any(v any) (res ipld.Node, err error) {
|
||||||
|
// TODO: handle uint overflow below
|
||||||
|
|
||||||
|
// some fast path
|
||||||
|
switch val := v.(type) {
|
||||||
|
case bool:
|
||||||
|
return basicnode.NewBool(val), nil
|
||||||
|
case string:
|
||||||
|
return basicnode.NewString(val), nil
|
||||||
|
case int:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case int8:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case int16:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case int32:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case int64:
|
||||||
|
return basicnode.NewInt(val), nil
|
||||||
|
case uint:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case uint8:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case uint16:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case uint32:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case uint64:
|
||||||
|
return basicnode.NewInt(int64(val)), nil
|
||||||
|
case float32:
|
||||||
|
return basicnode.NewFloat(float64(val)), nil
|
||||||
|
case float64:
|
||||||
|
return basicnode.NewFloat(val), nil
|
||||||
|
case []byte:
|
||||||
|
return basicnode.NewBytes(val), nil
|
||||||
|
case datamodel.Node:
|
||||||
|
return val, nil
|
||||||
|
case cid.Cid:
|
||||||
|
return LinkCid(val), nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := basicnode.Prototype__Any{}.NewBuilder()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
res = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
anyAssemble(v)(builder)
|
||||||
|
|
||||||
|
return builder.Build(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func anyAssemble(val any) qp.Assemble {
|
func anyAssemble(val any) qp.Assemble {
|
||||||
var rt reflect.Type
|
var rt reflect.Type
|
||||||
var rv reflect.Value
|
var rv reflect.Value
|
||||||
@@ -117,6 +175,11 @@ func anyAssemble(val any) qp.Assemble {
|
|||||||
return qp.Float(rv.Float())
|
return qp.Float(rv.Float())
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return qp.String(rv.String())
|
return qp.String(rv.String())
|
||||||
|
case reflect.Struct:
|
||||||
|
if rt == reflect.TypeOf(cid.Cid{}) {
|
||||||
|
c := rv.Interface().(cid.Cid)
|
||||||
|
return qp.Link(cidlink.Link{Cid: c})
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package literal
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||||
"github.com/ipld/go-ipld-prime/printer"
|
"github.com/ipld/go-ipld-prime/printer"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -53,6 +55,7 @@ func TestMap(t *testing.T) {
|
|||||||
"barbar": "foo",
|
"barbar": "foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"link": cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm"),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -115,6 +118,140 @@ func TestMap(t *testing.T) {
|
|||||||
string{"barbar"}: string{"foo"}
|
string{"barbar"}: string{"foo"}
|
||||||
}
|
}
|
||||||
}`, printer.Sprint(v))
|
}`, printer.Sprint(v))
|
||||||
|
|
||||||
|
v, err = n.LookupByString("link")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Link, v.Kind())
|
||||||
|
asLink, err := v.AsLink()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, asLink.(cidlink.Link).Equals(cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAny(t *testing.T) {
|
||||||
|
data := map[string]any{
|
||||||
|
"bool": true,
|
||||||
|
"string": "foobar",
|
||||||
|
"bytes": []byte{1, 2, 3, 4},
|
||||||
|
"int": 1234,
|
||||||
|
"uint": uint(12345),
|
||||||
|
"float": 1.45,
|
||||||
|
"slice": []int{1, 2, 3},
|
||||||
|
"array": [2]int{1, 2},
|
||||||
|
"map": map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
"foofoo": map[string]string{
|
||||||
|
"barbar": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"link": cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm"),
|
||||||
|
"func": func() {},
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := Any(data["bool"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Bool, v.Kind())
|
||||||
|
require.Equal(t, true, must(v.AsBool()))
|
||||||
|
|
||||||
|
v, err = Any(data["string"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_String, v.Kind())
|
||||||
|
require.Equal(t, "foobar", must(v.AsString()))
|
||||||
|
|
||||||
|
v, err = Any(data["bytes"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Bytes, v.Kind())
|
||||||
|
require.Equal(t, []byte{1, 2, 3, 4}, must(v.AsBytes()))
|
||||||
|
|
||||||
|
v, err = Any(data["int"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Int, v.Kind())
|
||||||
|
require.Equal(t, int64(1234), must(v.AsInt()))
|
||||||
|
|
||||||
|
v, err = Any(data["uint"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Int, v.Kind())
|
||||||
|
require.Equal(t, int64(12345), must(v.AsInt()))
|
||||||
|
|
||||||
|
v, err = Any(data["float"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Float, v.Kind())
|
||||||
|
require.Equal(t, 1.45, must(v.AsFloat()))
|
||||||
|
|
||||||
|
v, err = Any(data["slice"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_List, v.Kind())
|
||||||
|
require.Equal(t, int64(3), v.Length())
|
||||||
|
require.Equal(t, `list{
|
||||||
|
0: int{1}
|
||||||
|
1: int{2}
|
||||||
|
2: int{3}
|
||||||
|
}`, printer.Sprint(v))
|
||||||
|
|
||||||
|
v, err = Any(data["array"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_List, v.Kind())
|
||||||
|
require.Equal(t, int64(2), v.Length())
|
||||||
|
require.Equal(t, `list{
|
||||||
|
0: int{1}
|
||||||
|
1: int{2}
|
||||||
|
}`, printer.Sprint(v))
|
||||||
|
|
||||||
|
v, err = Any(data["map"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Map, v.Kind())
|
||||||
|
require.Equal(t, int64(2), v.Length())
|
||||||
|
require.Equal(t, `map{
|
||||||
|
string{"foo"}: string{"bar"}
|
||||||
|
string{"foofoo"}: map{
|
||||||
|
string{"barbar"}: string{"foo"}
|
||||||
|
}
|
||||||
|
}`, printer.Sprint(v))
|
||||||
|
|
||||||
|
v, err = Any(data["link"])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, datamodel.Kind_Link, v.Kind())
|
||||||
|
asLink, err := v.AsLink()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, asLink.(cidlink.Link).Equals(cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")))
|
||||||
|
|
||||||
|
v, err = Any(data["func"])
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAny(b *testing.B) {
|
||||||
|
b.Run("bool", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
_, _ = Any(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("string", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
_, _ = Any("foobar")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("bytes", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
_, _ = Any([]byte{1, 2, 3, 4})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("map", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
_, _ = Any(map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
"foofoo": map[string]string{
|
||||||
|
"barbar": "foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func must[T any](t T, err error) T {
|
func must[T any](t T, err error) T {
|
||||||
|
|||||||
@@ -187,6 +187,8 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
|||||||
return zero, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
return zero, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this re-encode the payload! Is there a less wasteful way?
|
||||||
|
|
||||||
data, err := ipld.Encode(info.sigPayloadNode, dagcbor.Encode)
|
data, err := ipld.Encode(info.sigPayloadNode, dagcbor.Encode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return zero, err
|
return zero, err
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
|
||||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
|
||||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
|
||||||
@@ -146,7 +144,7 @@ func prettyDAGJSON(data []byte) (string, error) {
|
|||||||
return out.String(), nil
|
return out.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupExampleNew() (privKey crypto.PrivKey, iss, sub did.DID, cmd command.Command, args map[string]datamodel.Node, prf []cid.Cid, meta map[string]datamodel.Node, errs error) {
|
func setupExampleNew() (privKey crypto.PrivKey, iss, sub did.DID, cmd command.Command, args map[string]any, prf []cid.Cid, meta map[string]any, errs error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
privKey, iss, err = did.GenerateEd25519()
|
privKey, iss, err = did.GenerateEd25519()
|
||||||
@@ -164,31 +162,19 @@ func setupExampleNew() (privKey crypto.PrivKey, iss, sub did.DID, cmd command.Co
|
|||||||
errs = errors.Join(errs, fmt.Errorf("failed to parse command: %w", err))
|
errs = errors.Join(errs, fmt.Errorf("failed to parse command: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
headers, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
headers := map[string]string{
|
||||||
qp.MapEntry(ma, "Content-Type", qp.String("application/json"))
|
"Content-Type": "application/json",
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
errs = errors.Join(errs, fmt.Errorf("failed to build headers: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***** WARNING - do not change the order of these elements. DAG-CBOR
|
payload := map[string]any{
|
||||||
// will order them alphabetically and the unsealed
|
"body": "UCAN is great",
|
||||||
// result won't match if the input isn't also created in
|
"draft": true,
|
||||||
// alphabetical order.
|
"title": "UCAN for Fun and Profit",
|
||||||
payload, err := qp.BuildMap(basicnode.Prototype.Any, 4, func(ma datamodel.MapAssembler) {
|
"topics": []string{"authz", "journal"},
|
||||||
qp.MapEntry(ma, "body", qp.String("UCAN is great"))
|
|
||||||
qp.MapEntry(ma, "draft", qp.Bool(true))
|
|
||||||
qp.MapEntry(ma, "title", qp.String("UCAN for Fun and Profit"))
|
|
||||||
qp.MapEntry(ma, "topics", qp.List(2, func(la datamodel.ListAssembler) {
|
|
||||||
qp.ListEntry(la, qp.String("authz"))
|
|
||||||
qp.ListEntry(la, qp.String("journal"))
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
errs = errors.Join(errs, fmt.Errorf("failed to build payload: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args = map[string]datamodel.Node{
|
args = map[string]any{
|
||||||
|
// you can also directly pass IPLD values
|
||||||
"uri": basicnode.NewString("https://example.com/blog/posts"),
|
"uri": basicnode.NewString("https://example.com/blog/posts"),
|
||||||
"headers": headers,
|
"headers": headers,
|
||||||
"payload": payload,
|
"payload": payload,
|
||||||
@@ -206,22 +192,9 @@ func setupExampleNew() (privKey crypto.PrivKey, iss, sub did.DID, cmd command.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***** WARNING - do not change the order of these elements. DAG-CBOR
|
meta = map[string]any{
|
||||||
// will order them alphabetically and the unsealed
|
|
||||||
// result won't match if the input isn't also created in
|
|
||||||
// alphabetical order.
|
|
||||||
tags, err := qp.BuildList(basicnode.Prototype.Any, 3, func(la datamodel.ListAssembler) {
|
|
||||||
qp.ListEntry(la, qp.String("blog"))
|
|
||||||
qp.ListEntry(la, qp.String("post"))
|
|
||||||
qp.ListEntry(la, qp.String("pr#123"))
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
errs = errors.Join(errs, fmt.Errorf("failed to build tags: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
meta = map[string]datamodel.Node{
|
|
||||||
"env": basicnode.NewString("development"),
|
"env": basicnode.NewString("development"),
|
||||||
"tags": tags,
|
"tags": []string{"blog", "post", "pr#123"},
|
||||||
}
|
}
|
||||||
|
|
||||||
return // WARNING: named return values
|
return // WARNING: named return values
|
||||||
|
|||||||
Reference in New Issue
Block a user