feat(args): export fluent builder
This commit is contained in:
71
pkg/args/builder.go
Normal file
71
pkg/args/builder.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package args
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder allows the fluid construction of an Args.
|
||||||
|
type Builder struct {
|
||||||
|
args *Args
|
||||||
|
errs error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder returns a Builder which will assemble the Args.
|
||||||
|
func NewBuilder() *Builder {
|
||||||
|
return &Builder{
|
||||||
|
args: New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add inserts a new key/val into the Args being assembled while collecting
|
||||||
|
// any errors caused by duplicate keys.
|
||||||
|
func (b *Builder) Add(key string, val any) *Builder {
|
||||||
|
b.errs = errors.Join(b.errs, b.args.Add(key, val))
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build returns the assembled Args or an error containing a list of
|
||||||
|
// errors encountered while trying to build the Args.
|
||||||
|
func (b *Builder) Build() (*Args, error) {
|
||||||
|
if b.errs != nil {
|
||||||
|
return nil, b.errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildIPLD is the same as Build except it takes the additional step of
|
||||||
|
// converting the Args to an ipld.Node.
|
||||||
|
func (b *Builder) BuildIPLD() (ipld.Node, error) {
|
||||||
|
args, err := b.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.ToIPLD()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBuild is the same as Build except it panics if an error occurs.
|
||||||
|
func (b *Builder) MustBuild() *Args {
|
||||||
|
args, err := b.Build()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(b.errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBuildIPLD is the same as BuildIPLD except it panics if an error
|
||||||
|
// occurs.
|
||||||
|
func (b *Builder) MustBuildIPLD() ipld.Node {
|
||||||
|
node, err := b.BuildIPLD()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
81
pkg/args/builder_test.go
Normal file
81
pkg/args/builder_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package args_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuilder_XXX(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyOne = "key1"
|
||||||
|
valOne = "string"
|
||||||
|
keyTwo = "key2"
|
||||||
|
valTwo = 42
|
||||||
|
)
|
||||||
|
|
||||||
|
exp := args.New()
|
||||||
|
exp.Add(keyOne, valOne)
|
||||||
|
exp.Add(keyTwo, valTwo)
|
||||||
|
|
||||||
|
expNode, err := exp.ToIPLD()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
disjointKeys := args.NewBuilder().
|
||||||
|
Add(keyOne, valOne).
|
||||||
|
Add(keyTwo, valTwo)
|
||||||
|
|
||||||
|
duplicateKeys := args.NewBuilder().
|
||||||
|
Add(keyOne, valOne).
|
||||||
|
Add(keyTwo, valTwo).
|
||||||
|
Add(keyOne, "oh no!")
|
||||||
|
|
||||||
|
t.Run("MustBuild succeeds with disjoint keys", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var act *args.Args
|
||||||
|
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
act = disjointKeys.MustBuild()
|
||||||
|
})
|
||||||
|
assert.Equal(t, exp, act)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("MustBuild fails with duplicate keys", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var act *args.Args
|
||||||
|
|
||||||
|
require.Panics(t, func() {
|
||||||
|
act = duplicateKeys.MustBuild()
|
||||||
|
})
|
||||||
|
assert.Nil(t, act)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("MustBuildIPLD succeeds with disjoint keys", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var act ipld.Node
|
||||||
|
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
act = disjointKeys.MustBuildIPLD()
|
||||||
|
})
|
||||||
|
assert.Equal(t, expNode, act)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("MustBuildIPLD fails with duplicate keys", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var act ipld.Node
|
||||||
|
|
||||||
|
require.Panics(t, func() {
|
||||||
|
act = duplicateKeys.MustBuildIPLD()
|
||||||
|
})
|
||||||
|
assert.Nil(t, act)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package policytest
|
package policytest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||||
@@ -12,12 +10,12 @@ import (
|
|||||||
// EmptyPolicy provides a Policy with no statements.
|
// EmptyPolicy provides a Policy with no statements.
|
||||||
var EmptyPolicy = policy.Policy{}
|
var EmptyPolicy = policy.Policy{}
|
||||||
|
|
||||||
// ExampleValidationPolicy provides a instantiated Policy containing the
|
// ExampleValidationPolicy provides a instantiated SpecPolicy containing the
|
||||||
// statements that are included in the second code block of the [Validation]
|
// statements that are included in the second code block of the [Validation]
|
||||||
// section of the delegation specification.
|
// section of the delegation specification.
|
||||||
//
|
//
|
||||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||||
var ExamplePolicy = policy.MustConstruct(
|
var SpecPolicy = policy.MustConstruct(
|
||||||
policy.Equal(".from", literal.String("alice@example.com")),
|
policy.Equal(".from", literal.String("alice@example.com")),
|
||||||
policy.Any(".to", policy.Like(".", "*@example.com")),
|
policy.Any(".to", policy.Like(".", "*@example.com")),
|
||||||
)
|
)
|
||||||
@@ -25,68 +23,39 @@ var ExamplePolicy = policy.MustConstruct(
|
|||||||
// TODO: Replace the URL for [Validation] above when the delegation
|
// TODO: Replace the URL for [Validation] above when the delegation
|
||||||
// specification has been finished/merged.
|
// specification has been finished/merged.
|
||||||
|
|
||||||
// ExampleValidArguments provides valid, instantiated Arguments containing
|
// SpecValidArguments provides valid, instantiated Arguments containing
|
||||||
// the key/value pairs that are included in portion of the the second code
|
// the key/value pairs that are included in portion of the the second code
|
||||||
// block of the [Validation] section of the delegation specification.
|
// block of the [Validation] section of the delegation specification.
|
||||||
//
|
//
|
||||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||||
var ExampleValidArguments = newBuilder(nil).
|
var SpecValidArguments = args.NewBuilder().
|
||||||
add("from", "alice@example.com").
|
Add("from", "alice@example.com").
|
||||||
add("to", []string{
|
Add("to", []string{
|
||||||
"bob@example.com",
|
"bob@example.com",
|
||||||
"carol@not.example.com",
|
"carol@not.example.com",
|
||||||
}).
|
}).
|
||||||
add("title", "Coffee").
|
Add("title", "Coffee").
|
||||||
add("body", "Still on for coffee").
|
Add("body", "Still on for coffee").
|
||||||
mustBuild()
|
MustBuild()
|
||||||
|
|
||||||
var exampleValidArgumentsIPLD = mustIPLD(ExampleValidArguments)
|
var specValidArgumentsIPLD = mustIPLD(SpecValidArguments)
|
||||||
|
|
||||||
// ExampleInvalidArguments provides invalid, instantiated Arguments containing
|
// SpecInvalidArguments provides invalid, instantiated Arguments containing
|
||||||
// the key/value pairs that are included in portion of the the second code
|
// the key/value pairs that are included in portion of the the second code
|
||||||
// block of the [Validation] section of the delegation specification.
|
// block of the [Validation] section of the delegation specification.
|
||||||
//
|
//
|
||||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||||
var ExampleInvalidArguments = newBuilder(nil).
|
var SpecInvalidArguments = args.NewBuilder().
|
||||||
add("from", "alice@example.com").
|
Add("from", "alice@example.com").
|
||||||
add("to", []string{
|
Add("to", []string{
|
||||||
"bob@null.com",
|
"bob@null.com",
|
||||||
"carol@elsewhere.example.com",
|
"carol@elsewhere.example.com",
|
||||||
}).
|
}).
|
||||||
add("title", "Coffee").
|
Add("title", "Coffee").
|
||||||
add("body", "Still on for coffee").
|
Add("body", "Still on for coffee").
|
||||||
mustBuild()
|
MustBuild()
|
||||||
|
|
||||||
var exampleInvalidArgumentsIPLD = mustIPLD(ExampleInvalidArguments)
|
var specInvalidArgumentsIPLD = mustIPLD(SpecInvalidArguments)
|
||||||
|
|
||||||
type builder struct {
|
|
||||||
args *args.Args
|
|
||||||
errs error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBuilder(a *args.Args) *builder {
|
|
||||||
if a == nil {
|
|
||||||
a = args.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &builder{
|
|
||||||
args: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) add(key string, val any) *builder {
|
|
||||||
b.errs = errors.Join(b.errs, b.args.Add(key, val))
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) mustBuild() *args.Args {
|
|
||||||
if b.errs != nil {
|
|
||||||
panic(b.errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.args
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustIPLD(args *args.Args) ipld.Node {
|
func mustIPLD(args *args.Args) ipld.Node {
|
||||||
node, err := args.ToIPLD()
|
node, err := args.ToIPLD()
|
||||||
@@ -17,7 +17,7 @@ func TestInvocationValidationSpecExamples(t *testing.T) {
|
|||||||
t.Run("with passing args", func(t *testing.T) {
|
t.Run("with passing args", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
exec, stmt := ExamplePolicy.Match(exampleValidArgumentsIPLD)
|
exec, stmt := SpecPolicy.Match(specValidArgumentsIPLD)
|
||||||
assert.True(t, exec)
|
assert.True(t, exec)
|
||||||
assert.Nil(t, stmt)
|
assert.Nil(t, stmt)
|
||||||
})
|
})
|
||||||
@@ -25,7 +25,7 @@ func TestInvocationValidationSpecExamples(t *testing.T) {
|
|||||||
t.Run("fails on recipients (second statement)", func(t *testing.T) {
|
t.Run("fails on recipients (second statement)", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
exec, stmt := ExamplePolicy.Match(exampleInvalidArgumentsIPLD)
|
exec, stmt := SpecPolicy.Match(specInvalidArgumentsIPLD)
|
||||||
assert.False(t, exec)
|
assert.False(t, exec)
|
||||||
assert.NotNil(t, stmt)
|
assert.NotNil(t, stmt)
|
||||||
})
|
})
|
||||||
@@ -130,7 +130,7 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari
|
|||||||
p.opts = append(p.opts, delegation.WithNotBefore(nbf))
|
p.opts = append(p.opts, delegation.WithNotBefore(nbf))
|
||||||
}},
|
}},
|
||||||
{name: "ValidExamplePolicy", variant: func(p *newDelegationParams) {
|
{name: "ValidExamplePolicy", variant: func(p *newDelegationParams) {
|
||||||
p.pol = policytest.ExamplePolicy
|
p.pol = policytest.SpecPolicy
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ func TestToken_ExecutionAllowed(t *testing.T) {
|
|||||||
t.Run("passes - arguments satisfy empty policy", func(t *testing.T) {
|
t.Run("passes - arguments satisfy empty policy", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.ExampleValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("passes - arguments satify example policy", func(t *testing.T) {
|
t.Run("passes - arguments satify example policy", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.ExampleValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails - no proof", func(t *testing.T) {
|
t.Run("fails - no proof", func(t *testing.T) {
|
||||||
@@ -132,7 +132,7 @@ func TestToken_ExecutionAllowed(t *testing.T) {
|
|||||||
t.Run("passes - arguments satify example policy", func(t *testing.T) {
|
t.Run("passes - arguments satify example policy", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.ExampleInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user