Merge pull request #79 from ucan-wg/meta-args

meta,args: add missing Include, add iterator to use normal or Readonly the same way
This commit is contained in:
Michael Muré
2024-11-21 11:27:37 +01:00
committed by GitHub
7 changed files with 206 additions and 18 deletions

View File

@@ -5,6 +5,7 @@ package args
import ( import (
"fmt" "fmt"
"iter"
"sort" "sort"
"strings" "strings"
@@ -54,21 +55,36 @@ func (a *Args) Add(key string, val any) error {
return nil return nil
} }
type Iterator interface {
Iter() iter.Seq2[string, ipld.Node]
}
// Include merges the provided arguments into the existing arguments. // Include merges the provided arguments into the existing arguments.
// //
// 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 (a *Args) Include(other *Args) { func (a *Args) Include(other Iterator) {
for _, key := range other.Keys { for key, value := range other.Iter() {
if _, ok := a.Values[key]; ok { if _, ok := a.Values[key]; ok {
// don't overwrite // don't overwrite
continue continue
} }
a.Values[key] = other.Values[key] a.Values[key] = value
a.Keys = append(a.Keys, key) a.Keys = append(a.Keys, key)
} }
} }
// Iter iterates over the args key/values
func (a *Args) Iter() iter.Seq2[string, ipld.Node] {
return func(yield func(string, ipld.Node) bool) {
for _, key := range a.Keys {
if !yield(key, a.Values[key]) {
return
}
}
}
}
// ToIPLD wraps an instance of an Args with an ipld.Node. // ToIPLD wraps an instance of an Args with an ipld.Node.
func (a *Args) ToIPLD() (ipld.Node, error) { func (a *Args) ToIPLD() (ipld.Node, error) {
sort.Strings(a.Keys) sort.Strings(a.Keys)

View File

@@ -1,6 +1,7 @@
package args_test package args_test
import ( import (
"maps"
"sync" "sync"
"testing" "testing"
@@ -8,6 +9,7 @@ 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/basicnode"
"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"
@@ -133,6 +135,56 @@ func TestArgs_Include(t *testing.T) {
assert.Equal(t, "val4", must(argsIn.Values["key4"].AsString())) assert.Equal(t, "val4", must(argsIn.Values["key4"].AsString()))
} }
func TestIterCloneEquals(t *testing.T) {
a := args.New()
require.NoError(t, a.Add("foo", "bar"))
require.NoError(t, a.Add("baz", 1234))
expected := map[string]ipld.Node{
"foo": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
}
// args -> iter
require.Equal(t, expected, maps.Collect(a.Iter()))
// readonly -> iter
ro := a.ReadOnly()
require.Equal(t, expected, maps.Collect(ro.Iter()))
// args -> clone -> iter
clone := a.Clone()
require.Equal(t, expected, maps.Collect(clone.Iter()))
// readonly -> WriteableClone -> iter
wclone := ro.WriteableClone()
require.Equal(t, expected, maps.Collect(wclone.Iter()))
require.True(t, a.Equals(wclone))
require.True(t, ro.Equals(wclone.ReadOnly()))
}
func TestInclude(t *testing.T) {
a1 := args.New()
require.NoError(t, a1.Add("samekey", "bar"))
require.NoError(t, a1.Add("baz", 1234))
a2 := args.New()
require.NoError(t, a2.Add("samekey", "othervalue")) // check no overwrite
require.NoError(t, a2.Add("otherkey", 1234))
a1.Include(a2)
require.Equal(t, map[string]ipld.Node{
"samekey": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
"otherkey": basicnode.NewInt(1234),
}, maps.Collect(a1.Iter()))
}
const ( const (
argsSchema = "type Args { String : Any }" argsSchema = "type Args { String : Any }"
argsName = "Args" argsName = "Args"

View File

@@ -1,17 +1,25 @@
package args package args
import "github.com/ipld/go-ipld-prime" import (
"iter"
"github.com/ipld/go-ipld-prime"
)
type ReadOnly struct { type ReadOnly struct {
args *Args args *Args
} }
func (r ReadOnly) Iter() iter.Seq2[string, ipld.Node] {
return r.args.Iter()
}
func (r ReadOnly) ToIPLD() (ipld.Node, error) { func (r ReadOnly) ToIPLD() (ipld.Node, error) {
return r.args.ToIPLD() return r.args.ToIPLD()
} }
func (r ReadOnly) Equals(other *Args) bool { func (r ReadOnly) Equals(other ReadOnly) bool {
return r.args.Equals(other) return r.args.Equals(other.args)
} }
func (r ReadOnly) String() string { func (r ReadOnly) String() string {

View File

@@ -3,6 +3,8 @@ package meta
import ( import (
"errors" "errors"
"fmt" "fmt"
"iter"
"sort"
"strings" "strings"
"github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime"
@@ -171,6 +173,36 @@ func (m *Meta) AddEncrypted(key string, val any, encryptionKey []byte) error {
return m.Add(key, encrypted) return m.Add(key, encrypted)
} }
type Iterator interface {
Iter() iter.Seq2[string, ipld.Node]
}
// Include merges the provided meta into the existing one.
//
// If duplicate keys are encountered, the new value is silently dropped
// without causing an error.
func (m *Meta) Include(other Iterator) {
for key, value := range other.Iter() {
if _, ok := m.Values[key]; ok {
// don't overwrite
continue
}
m.Values[key] = value
m.Keys = append(m.Keys, key)
}
}
// Iter iterates over the meta key/values
func (m *Meta) Iter() iter.Seq2[string, ipld.Node] {
return func(yield func(string, ipld.Node) bool) {
for _, key := range m.Keys {
if !yield(key, m.Values[key]) {
return
}
}
}
}
// Equals tells if two Meta hold the same key/values. // Equals tells if two Meta hold the same key/values.
func (m *Meta) Equals(other *Meta) bool { func (m *Meta) Equals(other *Meta) bool {
if len(m.Keys) != len(other.Keys) { if len(m.Keys) != len(other.Keys) {
@@ -188,6 +220,8 @@ func (m *Meta) Equals(other *Meta) bool {
} }
func (m *Meta) String() string { func (m *Meta) String() string {
sort.Strings(m.Keys)
buf := strings.Builder{} buf := strings.Builder{}
buf.WriteString("{") buf.WriteString("{")
@@ -209,5 +243,18 @@ func (m *Meta) String() string {
// ReadOnly returns a read-only version of Meta. // ReadOnly returns a read-only version of Meta.
func (m *Meta) ReadOnly() ReadOnly { func (m *Meta) ReadOnly() ReadOnly {
return ReadOnly{m: m} return ReadOnly{meta: m}
}
// Clone makes a deep copy.
func (m *Meta) Clone() *Meta {
res := &Meta{
Keys: make([]string, len(m.Keys)),
Values: make(map[string]ipld.Node, len(m.Values)),
}
copy(res.Keys, m.Keys)
for k, v := range m.Values {
res.Values[k] = v
}
return res
} }

View File

@@ -2,8 +2,11 @@ package meta_test
import ( import (
"crypto/rand" "crypto/rand"
"maps"
"testing" "testing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/meta" "github.com/ucan-wg/go-ucan/pkg/meta"
@@ -75,3 +78,53 @@ func TestMeta_Add(t *testing.T) {
}) })
}) })
} }
func TestIterCloneEquals(t *testing.T) {
m := meta.NewMeta()
require.NoError(t, m.Add("foo", "bar"))
require.NoError(t, m.Add("baz", 1234))
expected := map[string]ipld.Node{
"foo": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
}
// meta -> iter
require.Equal(t, expected, maps.Collect(m.Iter()))
// readonly -> iter
ro := m.ReadOnly()
require.Equal(t, expected, maps.Collect(ro.Iter()))
// meta -> clone -> iter
clone := m.Clone()
require.Equal(t, expected, maps.Collect(clone.Iter()))
// readonly -> WriteableClone -> iter
wclone := ro.WriteableClone()
require.Equal(t, expected, maps.Collect(wclone.Iter()))
require.True(t, m.Equals(wclone))
require.True(t, ro.Equals(wclone.ReadOnly()))
}
func TestInclude(t *testing.T) {
m1 := meta.NewMeta()
require.NoError(t, m1.Add("samekey", "bar"))
require.NoError(t, m1.Add("baz", 1234))
m2 := meta.NewMeta()
require.NoError(t, m2.Add("samekey", "othervalue")) // check no overwrite
require.NoError(t, m2.Add("otherkey", 1234))
m1.Include(m2)
require.Equal(t, map[string]ipld.Node{
"samekey": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
"otherkey": basicnode.NewInt(1234),
}, maps.Collect(m1.Iter()))
}

View File

@@ -1,50 +1,60 @@
package meta package meta
import ( import (
"iter"
"github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime"
) )
// ReadOnly wraps a Meta into a read-only facade. // ReadOnly wraps a Meta into a read-only facade.
type ReadOnly struct { type ReadOnly struct {
m *Meta meta *Meta
} }
func (r ReadOnly) GetBool(key string) (bool, error) { func (r ReadOnly) GetBool(key string) (bool, error) {
return r.m.GetBool(key) return r.meta.GetBool(key)
} }
func (r ReadOnly) GetString(key string) (string, error) { func (r ReadOnly) GetString(key string) (string, error) {
return r.m.GetString(key) return r.meta.GetString(key)
} }
func (r ReadOnly) GetEncryptedString(key string, encryptionKey []byte) (string, error) { func (r ReadOnly) GetEncryptedString(key string, encryptionKey []byte) (string, error) {
return r.m.GetEncryptedString(key, encryptionKey) return r.meta.GetEncryptedString(key, encryptionKey)
} }
func (r ReadOnly) GetInt64(key string) (int64, error) { func (r ReadOnly) GetInt64(key string) (int64, error) {
return r.m.GetInt64(key) return r.meta.GetInt64(key)
} }
func (r ReadOnly) GetFloat64(key string) (float64, error) { func (r ReadOnly) GetFloat64(key string) (float64, error) {
return r.m.GetFloat64(key) return r.meta.GetFloat64(key)
} }
func (r ReadOnly) GetBytes(key string) ([]byte, error) { func (r ReadOnly) GetBytes(key string) ([]byte, error) {
return r.m.GetBytes(key) return r.meta.GetBytes(key)
} }
func (r ReadOnly) GetEncryptedBytes(key string, encryptionKey []byte) ([]byte, error) { func (r ReadOnly) GetEncryptedBytes(key string, encryptionKey []byte) ([]byte, error) {
return r.m.GetEncryptedBytes(key, encryptionKey) return r.meta.GetEncryptedBytes(key, encryptionKey)
} }
func (r ReadOnly) GetNode(key string) (ipld.Node, error) { func (r ReadOnly) GetNode(key string) (ipld.Node, error) {
return r.m.GetNode(key) return r.meta.GetNode(key)
}
func (r ReadOnly) Iter() iter.Seq2[string, ipld.Node] {
return r.meta.Iter()
} }
func (r ReadOnly) Equals(other ReadOnly) bool { func (r ReadOnly) Equals(other ReadOnly) bool {
return r.m.Equals(other.m) return r.meta.Equals(other.meta)
} }
func (r ReadOnly) String() string { func (r ReadOnly) String() string {
return r.m.String() return r.meta.String()
}
func (r ReadOnly) WriteableClone() *Meta {
return r.meta.Clone()
} }

View File

@@ -164,6 +164,8 @@ func (t *Token) Arguments() args.ReadOnly {
// Proof() returns the ordered list of cid.Cid which reference the // Proof() returns the ordered list of cid.Cid which reference the
// delegation Tokens that authorize this invocation. // delegation Tokens that authorize this invocation.
// Ordering is from the leaf Delegation (with aud matching the invocation's iss)
// to the root delegation.
func (t *Token) Proof() []cid.Cid { func (t *Token) Proof() []cid.Cid {
return t.proof return t.proof
} }