Skip to content

Commit

Permalink
chore: move validate to registry
Browse files Browse the repository at this point in the history
  • Loading branch information
sysulq committed Mar 28, 2024
1 parent 163d1e8 commit f3c698e
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 171 deletions.
4 changes: 1 addition & 3 deletions kod.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,6 @@ type options struct {

// newKod creates a new instance of Kod with the provided registrations and options.
func newKod(opts options) (*Kod, error) {

kod := &Kod{
mu: &sync.Mutex{},
config: kodConfig{
Expand Down Expand Up @@ -371,7 +370,6 @@ func (k *Kod) parseConfig(filename string) error {
}

func (k *Kod) initLog() {

k.logLevelVar = new(slog.LevelVar)
lo.Must0(k.logLevelVar.UnmarshalText([]byte(k.config.Log.Level)))

Expand All @@ -383,7 +381,7 @@ func (k *Kod) initLog() {
Filename: k.config.Log.File,
MaxSize: 500, // megabytes
MaxBackups: 7,
MaxAge: 28, //days
MaxAge: 28, // days
Compress: false,
}
k.hooker.Add(hooks.HookFunc{
Expand Down
67 changes: 66 additions & 1 deletion registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package kod

import (
"context"
"errors"
"fmt"
"log/slog"
"reflect"

"github.com/dominikbraun/graph"
"github.com/go-kod/kod/internal/callgraph"
"github.com/go-kod/kod/internal/hooks"
"github.com/go-kod/kod/internal/reflects"
"github.com/go-kod/kod/internal/registry"
)

Expand Down Expand Up @@ -34,7 +38,6 @@ func (k *Kod) getImpl(ctx context.Context, t reflect.Type) (any, error) {

// getIntf returns the component for the given interface type.
func (k *Kod) getIntf(ctx context.Context, t reflect.Type, caller string) (any, error) {

reg, ok := k.registryByInterface[t]
if !ok {
return nil, fmt.Errorf("kod: no component registered for interface %v", t)
Expand Down Expand Up @@ -158,3 +161,65 @@ func fillRefs(impl any, get func(reflect.Type) (any, error)) error {
}
return nil
}

// checkCircularDependency checks that there are no circular dependencies
// between registered components.
func checkCircularDependency(reg []*Registration) error {
g := graph.New(graph.StringHash, graph.Directed(), graph.PreventCycles())

for _, reg := range reg {
if err := g.AddVertex(reg.Name); err != nil {
return fmt.Errorf("components [%s], error %s", reg.Name, err)
}
}

var errs []error
for _, reg := range reg {
edges := callgraph.ParseEdges([]byte(reg.Refs))
for _, edge := range edges {
err := g.AddEdge(edge[0], edge[1])
if err != nil {
switch err {
case graph.ErrEdgeAlreadyExists, graph.ErrEdgeCreatesCycle:
err = fmt.Errorf("components [%s] and [%s] have cycle Ref", edge[0], edge[1])
}
errs = append(errs, err)
}
}
}

return errors.Join(errs...)
}

// validateRegistrations checks that all registered component interfaces are
// implemented by a registered component implementation struct.
func validateRegistrations(regs []*Registration) error {
// Gather the set of registered interfaces.
intfs := map[reflect.Type]struct{}{}
for _, reg := range regs {
intfs[reg.Interface] = struct{}{}
}

// Check that for every kod.Ref[T] field in a component implementation
// struct, T is a registered interface.
var errs []error
for _, reg := range regs {
for i := 0; i < reg.Impl.NumField(); i++ {
f := reg.Impl.Field(i)
switch {
case f.Type.Implements(reflects.TypeFor[interface{ isRef() }]()):
// f is a kod.Ref[T].
v := f.Type.Field(0) // a Ref[T]'s value field
if _, ok := intfs[v.Type]; !ok {
// T is not a registered component interface.
err := fmt.Errorf(
"component implementation struct %v has field %v, but component %v was not registered; maybe you forgot to run 'kod generate'",
reg.Impl, f.Type, v.Type,
)
errs = append(errs, err)
}
}
}
}
return errors.Join(errs...)
}
87 changes: 87 additions & 0 deletions registry_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package kod

import (
"io"
"reflect"
"strings"
"testing"

"github.com/go-kod/kod/internal/reflects"
"github.com/go-kod/kod/internal/registry"
"github.com/stretchr/testify/assert"
)

Expand All @@ -19,5 +24,87 @@ func TestFill(t *testing.T) {
i := 0
assert.NotNil(t, fillRefs(&i, nil))
})
}

func TestValidateUnregisteredRef(t *testing.T) {
type foo interface{}
type fooImpl struct{ Ref[io.Reader] }
regs := []*registry.Registration{
{
Name: "foo",
Interface: reflects.TypeFor[foo](),
Impl: reflects.TypeFor[fooImpl](),
},
}
err := validateRegistrations(regs)
if err == nil {
t.Fatal("unexpected validateRegistrations success")
}
const want = "component io.Reader was not registered"
if !strings.Contains(err.Error(), want) {
t.Fatalf("validateRegistrations: got %q, want %q", err, want)
}
}

// TestValidateNoRegistrations tests that validateRegistrations succeeds on an
// empty set of registrations.
func TestValidateNoRegistrations(t *testing.T) {
if err := validateRegistrations(nil); err != nil {
t.Fatal(err)
}
}

func TestMultipleRegistrations(t *testing.T) {
type foo interface{}
type fooImpl struct{ Ref[io.Reader] }
regs := []*Registration{
{
Name: "github.com/go-kod/kod/Main",
Interface: reflect.TypeOf((*Main)(nil)).Elem(),
Impl: reflect.TypeOf(fooImpl{}),
Refs: `⟦48699770:KoDeDgE:github.com/go-kod/kod/Main→github.com/go-kod/kod/tests/graphcase/test1Controller⟧`,
},
{
Name: "github.com/go-kod/kod/Main",
Interface: reflect.TypeOf((*foo)(nil)).Elem(),
Impl: reflect.TypeOf(fooImpl{}),
Refs: `⟦48699770:KoDeDgE:github.com/go-kod/kod/tests/graphcase/test1Controller→github.com/go-kod/kod/Main⟧`,
},
}
err := checkCircularDependency(regs)
if err == nil {
t.Fatal("unexpected checkCircularDependency success")
}
const want = "components [github.com/go-kod/kod/Main], error vertex already exists"
if !strings.Contains(err.Error(), want) {
t.Fatalf("checkCircularDependency: got %q, want %q", err, want)
}
}

func TestCycleRegistrations(t *testing.T) {
type test1Controller interface{}
type test1ControllerImpl struct{ Ref[io.Reader] }
type mainImpl struct{ Ref[test1Controller] }
regs := []*Registration{
{
Name: "github.com/go-kod/kod/Main",
Interface: reflect.TypeOf((*Main)(nil)).Elem(),
Impl: reflect.TypeOf(mainImpl{}),
Refs: `⟦48699770:KoDeDgE:github.com/go-kod/kod/Main→github.com/go-kod/kod/test1Controller⟧`,
},
{
Name: "github.com/go-kod/kod/test1Controller",
Interface: reflect.TypeOf((*test1Controller)(nil)).Elem(),
Impl: reflect.TypeOf(test1ControllerImpl{}),
Refs: `⟦b8422d0e:KoDeDgE:github.com/go-kod/kod/test1Controller→github.com/go-kod/kod/Main⟧`,
},
}
err := checkCircularDependency(regs)
if err == nil {
t.Fatal("unexpected checkCircularDependency success")
}
const want = "components [github.com/go-kod/kod/test1Controller] and [github.com/go-kod/kod/Main] have cycle Ref"
if !strings.Contains(err.Error(), want) {
t.Fatalf("checkCircularDependency: got %q, want %q", err, want)
}
}
73 changes: 0 additions & 73 deletions validate.go

This file was deleted.

94 changes: 0 additions & 94 deletions validate_test.go

This file was deleted.

0 comments on commit f3c698e

Please sign in to comment.