Skip to content

Commit 98ac5ce

Browse files
authored
refactor(dix): update dep cycle check (#22)
* refactor(dix): replace panics with log errors for incorrect types * refactor(cycle-check): extract dependency graph building and cycle detection
1 parent fbfa60a commit 98ac5ce

6 files changed

Lines changed: 74 additions & 55 deletions

File tree

dix_internal/cycle-check.go

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,42 @@
11
package dix_internal
22

33
import (
4-
"container/list"
54
"reflect"
65
"strings"
76
)
87

9-
// isCycle Check whether type circular dependency
10-
func (x *Dix) isCycle() (string, bool) {
11-
types := make(map[reflect.Type]map[reflect.Type]bool)
12-
for _, nodes := range x.providers {
8+
func (x *Dix) buildDependencyGraph() map[reflect.Type]map[reflect.Type]bool {
9+
graph := make(map[reflect.Type]map[reflect.Type]bool)
10+
for typ, nodes := range x.providers {
1311
for _, n := range nodes {
14-
if types[n.output.typ] == nil {
15-
types[n.output.typ] = make(map[reflect.Type]bool)
12+
if graph[typ] == nil {
13+
graph[typ] = make(map[reflect.Type]bool)
1614
}
17-
18-
for i := range n.input {
19-
for _, v := range x.getAllProvideInput(n.input[i].typ) {
20-
types[n.output.typ][v.typ] = true
15+
for _, input := range n.input {
16+
for _, provider := range x.getAllProvideInput(input.typ) {
17+
graph[typ][provider.typ] = true
2118
}
2219
}
2320
}
2421
}
22+
return graph
23+
}
2524

26-
var check func(root reflect.Type, data map[reflect.Type]bool, nodes *list.List) bool
27-
check = func(root reflect.Type, nodeTypes map[reflect.Type]bool, nodes *list.List) bool {
28-
for typ := range nodeTypes {
29-
nodes.PushBack(typ)
30-
if root == typ {
31-
return true
32-
}
33-
34-
if check(root, types[typ], nodes) {
35-
return true
36-
}
37-
nodes.Remove(nodes.Back())
38-
}
39-
return false
40-
}
41-
42-
nodes := list.New()
43-
for root := range types {
44-
nodes.PushBack(root)
45-
if check(root, types[root], nodes) {
46-
break
47-
}
48-
nodes.Remove(nodes.Back())
49-
}
25+
// isCycle Check whether type circular dependency
26+
func (x *Dix) isCycle() (string, bool) {
27+
depGraph := x.buildDependencyGraph()
5028

51-
if nodes.Len() == 0 {
29+
cyclePath := detectCycle(depGraph)
30+
if len(cyclePath) == 0 {
5231
return "", false
5332
}
5433

55-
var dep []string
56-
for nodes.Len() != 0 {
57-
dep = append(dep, nodes.Front().Value.(reflect.Type).String())
58-
nodes.Remove(nodes.Front())
34+
var pathStr strings.Builder
35+
for i, t := range cyclePath {
36+
if i > 0 {
37+
pathStr.WriteString(" -> ")
38+
}
39+
pathStr.WriteString(t.String())
5940
}
60-
61-
return strings.Join(dep, " -> "), true
41+
return pathStr.String(), true
6242
}

dix_internal/dix.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package dix_internal
22

33
import (
44
"fmt"
5+
"os"
56
"reflect"
67
"strings"
78

89
"github.com/pubgo/funk/assert"
910
"github.com/pubgo/funk/errors"
11+
"github.com/pubgo/funk/log"
1012
"github.com/pubgo/funk/recovery"
1113
"github.com/pubgo/funk/stack"
1214
)
@@ -336,10 +338,7 @@ func (x *Dix) handleProvide(fnVal reflect.Value, out reflect.Type, in []*inType)
336338
x.handleProvide(fnVal, outTyp.Field(i).Type, in)
337339
}
338340
default:
339-
panic(&errors.Err{
340-
Msg: "incorrect output type",
341-
Detail: fmt.Sprintf("ouTyp=%s kind=%s", outTyp, outTyp.Kind()),
342-
})
341+
log.Error().Msgf("incorrect output type, ouTyp=%s kind=%s fnVal=%s", outTyp, outTyp.Kind(), fnVal.String())
343342
}
344343
}
345344

@@ -361,10 +360,7 @@ func (x *Dix) getAllProvideInput(typ reflect.Type) []*inType {
361360
case reflect.Slice:
362361
input = append(input, &inType{typ: inTye.Elem(), isList: true})
363362
default:
364-
panic(&errors.Err{
365-
Msg: "incorrect input type",
366-
Detail: fmt.Sprintf("inTyp=%s kind=%s", inTye, inTye.Kind()),
367-
})
363+
log.Error().Msgf("incorrect input type, inTyp=%s kind=%s", inTye, inTye.Kind())
368364
}
369365
return input
370366
}
@@ -383,10 +379,7 @@ func (x *Dix) getProvideInput(typ reflect.Type) []*inType {
383379
case reflect.Slice:
384380
input = append(input, &inType{typ: inTye.Elem(), isList: true})
385381
default:
386-
panic(&errors.Err{
387-
Msg: "incorrect input type",
388-
Detail: fmt.Sprintf("inTyp=%s kind=%s", inTye, inTye.Kind()),
389-
})
382+
log.Error().Msgf("incorrect input type, inTyp=%s kind=%s", inTye, inTye.Kind())
390383
}
391384
return input
392385
}
@@ -423,5 +416,6 @@ func (x *Dix) provide(param interface{}) {
423416
dep, ok := x.isCycle()
424417
if ok {
425418
logger.Fatal().Str("cycle", dep).Msg("provider circular dependency")
419+
os.Exit(1)
426420
}
427421
}

dix_internal/util.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,39 @@ func handleOutput(outType outputType, out reflect.Value) map[outputType]map[grou
125125
}
126126
return rr
127127
}
128+
129+
func detectCycle(graph map[reflect.Type]map[reflect.Type]bool) []reflect.Type {
130+
visited := make(map[reflect.Type]bool)
131+
recursionStack := make(map[reflect.Type]bool)
132+
133+
var cycle []reflect.Type
134+
135+
var dfs func(reflect.Type, []reflect.Type)
136+
dfs = func(t reflect.Type, path []reflect.Type) {
137+
if recursionStack[t] {
138+
cycle = append([]reflect.Type(nil), path...)
139+
return
140+
}
141+
if visited[t] {
142+
return
143+
}
144+
145+
visited[t] = true
146+
recursionStack[t] = true
147+
defer delete(recursionStack, t)
148+
149+
for dep := range graph[t] {
150+
dfs(dep, append(path, dep))
151+
if len(cycle) > 0 {
152+
return
153+
}
154+
}
155+
}
156+
157+
for t := range graph {
158+
if !visited[t] {
159+
dfs(t, []reflect.Type{t})
160+
}
161+
}
162+
return cycle
163+
}

example/struct-out/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type (
2424
)
2525

2626
type Conf struct {
27+
Data string
2728
Inline
2829
A *A
2930
B *B

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/pubgo/dix
22

3-
go 1.19
3+
go 1.22
44

55
require github.com/pubgo/funk v0.5.46
66

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk
22
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
33
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
44
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
56
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
67
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
78
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
89
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
910
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
1011
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
12+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1113
github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs=
1214
github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA=
1315
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -19,16 +21,21 @@ github.com/phuslu/goid v1.0.0 h1:Cgcvd/R54UO1fCtyt+iKXAi+yZQ/KWlAm6MmZNizCLM=
1921
github.com/phuslu/goid v1.0.0/go.mod h1:txc2fUIdrdnn+v9Vq+QpiPQ3dnrXEchjoVDgic+r+L0=
2022
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2123
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
24+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2225
github.com/pubgo/funk v0.5.46 h1:UoEW7vm0Iy2c73wMq/wYM6NVEg/ZPpxDaJhobCvbGeI=
2326
github.com/pubgo/funk v0.5.46/go.mod h1:Hm4oOYENrlr8A8nuH2YQWdx5jGg1fjAjaTvN2I28ts4=
2427
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
2528
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
2629
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
2730
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
2831
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
32+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
2933
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
34+
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
3035
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
36+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
3137
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
38+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
3239
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
3340
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
3441
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
@@ -47,3 +54,4 @@ google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsA
4754
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
4855
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
4956
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
57+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)