Skip to content

Commit

Permalink
Merge pull request #5 from ggicci/feat/override-namespace
Browse files Browse the repository at this point in the history
feat: allow overriding namespace when calling Scan and Resolve
  • Loading branch information
ggicci authored Oct 2, 2023
2 parents ff1212b + f6b3757 commit 1635467
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 45 deletions.
15 changes: 6 additions & 9 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ import (

var (
ErrUnsupportedType = errors.New("unsupported type")
ErrNilNamespace = errors.New("nil namespace")
ErrInvalidDirectiveName = errors.New("invalid directive/executor name")
ErrDuplicateExecutor = errors.New("duplicate executor")
ErrInvalidDirectiveName = errors.New("invalid directive name")
ErrDuplicateDirective = errors.New("duplicate directive")
ErrNilExecutor = errors.New("nil executor")
ErrMissingExecutor = errors.New("missing executor")
ErrTypeMismatch = errors.New("type mismatch")
ErrScanNilField = errors.New("scan nil field")
Expand All @@ -22,16 +19,16 @@ func invalidDirectiveName(name string) error {
return fmt.Errorf("%w: %q (should comply with %s)", ErrInvalidDirectiveName, name, reDirectiveName.String())
}

func duplicateExecutor(name string) error {
return fmt.Errorf("%w: %q (registered to the same namespace)", ErrDuplicateExecutor, name)
}

func duplicateDirective(name string) error {
return fmt.Errorf("%w: %q (defined in the same struct tag)", ErrDuplicateDirective, name)
}

func duplicateExecutor(name string) error {
return fmt.Errorf("duplicate executor: %q (registered to the same namespace)", name)
}

func nilExecutor(name string) error {
return fmt.Errorf("%w: %q", ErrNilExecutor, name)
return fmt.Errorf("nil executor: %q", name)
}

type ResolveError struct {
Expand Down
2 changes: 1 addition & 1 deletion helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,6 @@ func createNsForTrackingCtor(throwError error, verifier *ContextVerifier) (*owl.
ns := owl.NewNamespace()
ns.RegisterDirectiveExecutor("form", NewEchoExecutor(tracker, "form", throwError, verifier))
ns.RegisterDirectiveExecutor("default", NewEchoExecutor(tracker, "default", throwError, verifier))
ns.ReplaceDirectiveExecutor("env", NewEchoExecutor(tracker, "env", throwError, verifier))
ns.RegisterDirectiveExecutor("env", NewEchoExecutor(tracker, "env", throwError, verifier))
return ns, tracker
}
40 changes: 15 additions & 25 deletions namespace.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
package owl

import "fmt"

var (
defaultNS = NewNamespace()
)

// RegisterDirectiveExecutor registers a named executor globally (in the default
// namespace).
func RegisterDirectiveExecutor(name string, exe DirectiveExecutor) {
defaultNS.RegisterDirectiveExecutor(name, exe)
}

// ReplaceDirectiveExecutor replaces a named executor globally (in the default
// namespace). It works like RegisterDirectiveExecutor without panic on
// duplicate names.
func ReplaceDirectiveExecutor(name string, exe DirectiveExecutor) {
defaultNS.ReplaceDirectiveExecutor(name, exe)
// RegisterDirectiveExecutor registers a named executor globally, i.e. to the default
// namespace.
func RegisterDirectiveExecutor(name string, exe DirectiveExecutor, replace ...bool) {
defaultNS.RegisterDirectiveExecutor(name, exe, replace...)
}

// LookupExecutor returns the executor by name globally (from the default
Expand All @@ -35,24 +30,19 @@ func NewNamespace() *Namespace {
}
}

// RegisterDirectiveExecutor registers a named executor in the namespace.
// The executor should implement the DirectiveExecutor interface.
// Will panic if the name were taken or the executor is nil.
func (ns *Namespace) RegisterDirectiveExecutor(name string, exe DirectiveExecutor) {
if _, ok := ns.executors[name]; ok {
panic(duplicateExecutor(name))
// RegisterDirectiveExecutor registers a named executor to the namespace. The executor
// should implement the DirectiveExecutor interface. Will panic if the name were taken
// or the executor is nil. Pass replace (true) to ignore the name conflict.
func (ns *Namespace) RegisterDirectiveExecutor(name string, exe DirectiveExecutor, replace ...bool) {
force := len(replace) > 0 && replace[0]
if _, ok := ns.executors[name]; ok && !force {
panic(fmt.Errorf("owl: %s", duplicateExecutor(name)))
}
ns.ReplaceDirectiveExecutor(name, exe)
}

// ReplaceDirectiveExecutor works like RegisterDirectiveExecutor without panicing
// on duplicate names. But it will panic if the executor is nil or the name is invalid.
func (ns *Namespace) ReplaceDirectiveExecutor(name string, exe DirectiveExecutor) {
if exe == nil {
panic(nilExecutor(name))
panic(fmt.Errorf("owl: %s", nilExecutor(name)))
}
if !isValidDirectiveName(name) {
panic(invalidDirectiveName(name))
panic(fmt.Errorf("owl: %s", invalidDirectiveName(name)))
}
ns.executors[name] = exe
}
Expand Down
16 changes: 8 additions & 8 deletions namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,41 @@ func TestNamespace(t *testing.T) {

ns.RegisterDirectiveExecutor("foo", DirectiveExecutorFunc(exeFoo))
assert.Equal(ns.LookupExecutor("foo").Execute(nil), errFoo)
assert.PanicsWithError(duplicateExecutor("foo").Error(), func() {
assert.PanicsWithError("owl: "+duplicateExecutor("foo").Error(), func() {
ns.RegisterDirectiveExecutor("foo", DirectiveExecutorFunc(exeFoo))
})

ns.RegisterDirectiveExecutor("bar", DirectiveExecutorFunc(exeBar))
assert.Equal(ns.LookupExecutor("bar").Execute(nil), errBar)
assert.PanicsWithError(duplicateExecutor("bar").Error(), func() {
assert.PanicsWithError("owl: "+duplicateExecutor("bar").Error(), func() {
ns.RegisterDirectiveExecutor("bar", DirectiveExecutorFunc(exeBar))
})

ns.ReplaceDirectiveExecutor("foo", DirectiveExecutorFunc(exeFoo))
ns.RegisterDirectiveExecutor("foo", DirectiveExecutorFunc(exeFoo), true)
assert.Equal(ns.LookupExecutor("foo").Execute(nil), errFoo)
ns.ReplaceDirectiveExecutor("foo", DirectiveExecutorFunc(exeBar))
ns.RegisterDirectiveExecutor("foo", DirectiveExecutorFunc(exeBar), true)
assert.Equal(ns.LookupExecutor("foo").Execute(nil), errBar)
}

func TestNamespace_RegisterNilExecutor(t *testing.T) {
assert.PanicsWithError(t, nilExecutor("foo").Error(), func() {
assert.PanicsWithError(t, "owl: "+nilExecutor("foo").Error(), func() {
NewNamespace().RegisterDirectiveExecutor("foo", nil)
})
}

func TestNamespace_RegisterInvalidName(t *testing.T) {
assert.PanicsWithError(t, invalidDirectiveName(".foo").Error(), func() {
assert.PanicsWithError(t, "owl: "+invalidDirectiveName(".foo").Error(), func() {
NewNamespace().RegisterDirectiveExecutor(".foo", DirectiveExecutorFunc(exeFoo))
})
}

func TestDefaultNamespace(t *testing.T) {
RegisterDirectiveExecutor("foo", DirectiveExecutorFunc(exeFoo))
assert.Equal(t, LookupExecutor("foo").Execute(nil), errFoo)
assert.PanicsWithError(t, duplicateExecutor("foo").Error(), func() {
assert.PanicsWithError(t, "owl: "+duplicateExecutor("foo").Error(), func() {
RegisterDirectiveExecutor("foo", DirectiveExecutorFunc(exeFoo))
})

ReplaceDirectiveExecutor("foo", DirectiveExecutorFunc(exeBar))
RegisterDirectiveExecutor("foo", DirectiveExecutorFunc(exeBar), true)
assert.Equal(t, LookupExecutor("foo").Execute(nil), errBar)
}
8 changes: 7 additions & 1 deletion resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package owl

import (
"context"
"errors"
"fmt"
"reflect"
"strconv"
Expand Down Expand Up @@ -81,7 +82,7 @@ func (r *Resolver) copy() *Resolver {

func (r *Resolver) validate() error {
if r.Namespace() == nil {
return fmt.Errorf("%w: the namespace you passed through WithNamespace to owl.New is nil", ErrNilNamespace)
return errors.New("nil namespace")
}

return nil
Expand Down Expand Up @@ -289,6 +290,11 @@ func (root *Resolver) resolve(ctx context.Context, rootValue reflect.Value) erro
func (r *Resolver) runDirectives(ctx context.Context, rv reflect.Value) error {
ns := r.Namespace()

// The namespace can be overriden by calling Scan/Resolve with WithNamespace.
if nsOverriden := ctx.Value(ckNamespace); nsOverriden != nil {
ns = nsOverriden.(*Namespace)
}

for _, directive := range r.Directives {
dirRuntime := &DirectiveRuntime{
Directive: directive,
Expand Down
13 changes: 12 additions & 1 deletion resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func TestNew_ParsingDirectives_DuplicateDirectives(t *testing.T) {
func TestNew_OptionNilNamespace(t *testing.T) {
resolver, err := owl.New(struct{}{}, owl.WithNamespace(nil))
assert.Nil(t, resolver)
assert.ErrorIs(t, err, owl.ErrNilNamespace)
assert.ErrorContains(t, err, "nil namespace")
}

func TestNew_OptionCustomValue(t *testing.T) {
Expand Down Expand Up @@ -597,6 +597,17 @@ func TestScan_withOpts(t *testing.T) {
assert.ErrorContains(t, err, "unexpected context value")
}

func TestScan_overrideNamespace(t *testing.T) {
resolver, err := owl.New(User{}) // using default namespace
assert.NoError(t, err)
err = resolver.Scan(User{})
assert.Error(t, err) // should fail because default namespace has no directive executors

ns, _ := createNsForTracking()
err = resolver.Scan(User{}, owl.WithNamespace(ns))
assert.NoError(t, err) // should success because namespace is overrided
}

func TestScan_onNilValue(t *testing.T) {
resolver, err := owl.New(User{})
assert.NoError(t, err)
Expand Down

0 comments on commit 1635467

Please sign in to comment.