Skip to content

Commit d8dd7d3

Browse files
feat: add injector that support contextual injection
1 parent 1a5b8b7 commit d8dd7d3

File tree

15 files changed

+2221
-0
lines changed

15 files changed

+2221
-0
lines changed

.github/workflows/go.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# This workflow will build a golang project
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3+
4+
name: Go
5+
6+
on:
7+
push:
8+
branches: [ "main" ]
9+
pull_request:
10+
branches: [ "main" ]
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Set up Go
19+
uses: actions/setup-go@v5
20+
with:
21+
go-version: '1.24'
22+
23+
- name: Build
24+
run: cd inject && go build -v ./...
25+
26+
test:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- name: Set up Go
32+
uses: actions/setup-go@v5
33+
with:
34+
go-version: '1.24'
35+
36+
- name: Test
37+
run: cd inject && go test -coverprofile=coverage.out ./...
38+
39+
- name: Upload coverage report
40+
uses: codecov/codecov-action@v4
41+
with:
42+
files: inject/coverage.out
43+
fail_ci_if_error: true
44+
45+
lint:
46+
name: Lint
47+
runs-on: ubuntu-latest
48+
49+
steps:
50+
- name: Checkout repository
51+
uses: actions/checkout@v4
52+
53+
- name: Set up Go
54+
uses: actions/setup-go@v5
55+
with:
56+
go-version: '1.24'
57+
58+
- name: Install golangci-lint
59+
run: |
60+
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5
61+
62+
- name: Run golangci-lint
63+
run: cd inject && $(go env GOPATH)/bin/golangci-lint run

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

.golangci.yaml

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
linters-settings:
2+
gosec:
3+
excludes:
4+
- G306
5+
- G115
6+
depguard:
7+
# new configuration
8+
rules:
9+
logger:
10+
deny:
11+
# logging is allowed only by logutils.Log,
12+
# logrus is allowed to use only in logutils package.
13+
- pkg: "github.com/sirupsen/logrus"
14+
desc: logging is allowed only by logutils.Log
15+
dupl:
16+
threshold: 100
17+
funlen:
18+
lines: -1 # the number of lines (code + empty lines) is not a right metric and leads to code without empty line or one-liner.
19+
statements: 75
20+
goconst:
21+
min-len: 2
22+
min-occurrences: 3
23+
gocritic:
24+
enabled-tags:
25+
- diagnostic
26+
- experimental
27+
- opinionated
28+
- performance
29+
- style
30+
disabled-checks:
31+
- dupImport # https://github.com/go-critic/go-critic/issues/845
32+
- ifElseChain
33+
- octalLiteral
34+
- whyNoLint
35+
gocyclo:
36+
min-complexity: 15
37+
gofmt:
38+
rewrite-rules:
39+
- pattern: 'interface{}'
40+
replacement: 'any'
41+
goimports:
42+
local-prefixes: github.com/golangci/golangci-lint
43+
44+
govet:
45+
settings:
46+
printf:
47+
funcs:
48+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
49+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
50+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
51+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
52+
enable:
53+
- nilness
54+
- shadow
55+
errorlint:
56+
asserts: false
57+
lll:
58+
line-length: 140
59+
nolintlint:
60+
allow-unused: false # report any unused nolint directives
61+
require-explanation: false # don't require an explanation for nolint directives
62+
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
63+
revive:
64+
rules:
65+
- name: unexported-return
66+
disabled: true
67+
- name: unused-parameter
68+
69+
linters:
70+
disable-all: true
71+
enable:
72+
- bodyclose
73+
- depguard
74+
- dogsled
75+
- dupl
76+
- errcheck
77+
- errorlint
78+
- funlen
79+
- gocheckcompilerdirectives
80+
- gochecknoinits
81+
- goconst
82+
- gocritic
83+
- gocyclo
84+
- gofmt
85+
- goimports
86+
- goprintffuncname
87+
- gosec
88+
- gosimple
89+
- govet
90+
- ineffassign
91+
- lll
92+
- nakedret
93+
- noctx
94+
- nolintlint
95+
- revive
96+
- staticcheck
97+
- stylecheck
98+
- typecheck
99+
- unconvert
100+
- unparam
101+
- unused
102+
- whitespace
103+
104+
# don't enable:
105+
# - asciicheck
106+
# - scopelint
107+
# - gochecknoglobals
108+
# - gocognit
109+
# - godot
110+
# - godox
111+
# - goerr113
112+
# - interfacer
113+
# - maligned
114+
# - nestif
115+
# - prealloc
116+
# - testpackage
117+
# - wsl
118+
119+
issues:
120+
# Excluding configuration per-path, per-linter, per-text and per-source
121+
exclude-rules:
122+
- path: pkg/golinters/errcheck.go
123+
text: "SA1019: errCfg.Exclude is deprecated: use ExcludeFunctions instead"
124+
- path: pkg/commands/run.go
125+
text: "SA1019: lsc.Errcheck.Exclude is deprecated: use ExcludeFunctions instead"
126+
- path: pkg/commands/run.go
127+
text: "SA1019: e.cfg.Run.Deadline is deprecated: Deadline exists for historical compatibility and should not be used."
128+
129+
- path: pkg/golinters/gofumpt.go
130+
text: "SA1019: settings.LangVersion is deprecated: use the global `run.go` instead."
131+
- path: pkg/golinters/staticcheck_common.go
132+
text: "SA1019: settings.GoVersion is deprecated: use the global `run.go` instead."
133+
- path: pkg/lint/lintersdb/manager.go
134+
text: "SA1019: (.+).(GoVersion|LangVersion) is deprecated: use the global `run.go` instead."
135+
- path: pkg/golinters/unused.go
136+
text: "rangeValCopy: each iteration copies 160 bytes \\(consider pointers or indexing\\)"
137+
- path: test/(fix|linters)_test.go
138+
text: "string `gocritic.go` has 3 occurrences, make it a constant"
139+
140+
# Due to a change inside go-critic v0.10.0, some reports have been removed,
141+
# but as we run analysis with the previous version of golangci-lint this leads to a paradoxical situation.
142+
# This exclusion will be removed when the next version of golangci-lint (v1.56.0) will be released.
143+
- path: pkg/golinters/nolintlint/nolintlint.go
144+
text: "hugeParam: (i|b) is heavy \\(\\d+ bytes\\); consider passing it by pointer"
145+
exclude-dirs:
146+
- test/testdata_etc # test files
147+
- internal/cache # extracted from Go code
148+
- internal/renameio # extracted from Go code
149+
- internal/robustio # extracted from Go code
150+
151+
run:
152+
timeout: 5m

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# go-inject
2+
3+
## Description
4+
5+
`go-inject` is a dependency injection library that support contextual scope.
6+
7+
## Example
8+
9+
```go
10+
package myapp
11+
12+
import (
13+
"context"
14+
15+
"illuin-tech/go-inject/inject"
16+
)
17+
18+
type key int
19+
const myScopeKey key = 0
20+
21+
const MyScope = "MyScope"
22+
23+
// define function to declare your own scope in context
24+
func WithMyScopeEnabled(ctx context.Context) context.Context {
25+
return inject.WithContextualScopeEnabled(ctx, myScopeKey)
26+
}
27+
28+
func ShutdownMyContextScoped(ctx context.Context) {
29+
inject.ShutdownContextualScope(ctx, myScopeKey)
30+
}
31+
32+
// define injection modules
33+
var Module = inject.Module("myModule",
34+
inject.RegisterScope(MyScope, inject.NewContextualScope(myScopeKey)),
35+
inject.Provide(func() string {
36+
return "Hello world from scope"
37+
}, inject.In(MyScope)),
38+
)
39+
40+
func main() {
41+
ctx := context.Background()
42+
43+
// enable scope
44+
ctx = WithMyScopeEnabled(ctx)
45+
defer ShutdownMyContextScoped(ctx)
46+
47+
injector := inject.NewInjector(Module)
48+
_ = injector.Invoke(ctx, func(hello string) {
49+
println(hello)
50+
})
51+
}
52+
```

inject/binding.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package inject
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"reflect"
7+
)
8+
9+
// binding defines a type mapped to a more concrete type
10+
type binding struct {
11+
typeof reflect.Type
12+
provider reflect.Value
13+
providedType reflect.Type
14+
annotatedWith string
15+
scope string
16+
destroyMethod func(value reflect.Value)
17+
}
18+
19+
func (b *binding) create(ctx context.Context, injector *Injector) (reflect.Value, error) {
20+
res, err := injector.callFunctionWithArgumentInstance(ctx, b.provider)
21+
if err != nil {
22+
return reflect.Value{},
23+
fmt.Errorf("failed to call provider function for type %q: %w", b.providedType.String(), err)
24+
}
25+
if b.provider.Type().NumOut() == 2 {
26+
errValue := res[1].Interface()
27+
if errValue != nil {
28+
err, _ = errValue.(error)
29+
}
30+
}
31+
if err != nil {
32+
return res[0], fmt.Errorf("provider for type %q returned error: %w", b.providedType.String(), err)
33+
} else {
34+
return res[0], nil
35+
}
36+
}

inject/condition.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package inject
2+
3+
import "os"
4+
5+
type Conditional interface {
6+
evaluate() bool
7+
}
8+
9+
type environmentVariableConditional struct {
10+
name string
11+
havingValue string
12+
matchIfMissing bool
13+
}
14+
15+
func (c *environmentVariableConditional) evaluate() bool {
16+
val, ok := os.LookupEnv(c.name)
17+
if !ok {
18+
return c.matchIfMissing
19+
}
20+
return val == c.havingValue
21+
}
22+
23+
func OnEnvironmentVariable(name, havingValue string, matchIfMissing bool) Conditional {
24+
return &environmentVariableConditional{
25+
name: name,
26+
havingValue: havingValue,
27+
matchIfMissing: matchIfMissing,
28+
}
29+
}

0 commit comments

Comments
 (0)