(Translated by https://www.hiragana.jp/)
Initial version · tiendc/autowire@cd1e83e · GitHub
Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
tiendc committed Sep 4, 2024
1 parent 881ada0 commit cd1e83e
Show file tree
Hide file tree
Showing 28 changed files with 2,090 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
coverage:
range: 80..100
round: down
precision: 2

status:
project: # measuring the overall project coverage
default: # context, you can create multiple ones with custom titles
enabled: yes # must be yes|true to enable this status
target: 85% # specify the target coverage for each commit status
# option: "auto" (must increase from parent commit or pull request base)
# option: "X%" a static target percentage to hit
if_not_found: success # if parent is not found report status as success, error, or failure
if_ci_failed: error # if ci fails report status as success, error, or failure
48 changes: 48 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Go

on:
push:
branches: ['*']
tags: ['v*']
pull_request:
branches: ['*']

jobs:

build:
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.18.x", "1.22.x"]
include:
- go: 1.22.x
latest: true

steps:
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go }}

- name: Checkout code
uses: actions/checkout@v3

- name: Load cached dependencies
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download Dependencies
run: make prepare

- name: Lint
run: make lint

- name: Test
run: make cover

- name: Upload coverage to codecov.io
uses: codecov/codecov-action@v3
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test
*.test
*.out

# Dependency
vendor/

# Goland, vscode, OS
.idea
.vscode
.DS_Store
74 changes: 74 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
linters-settings:
funlen:
lines: 100
statements: 80
gci:
sections:
- standard
- default
- prefix(github.com/tiendc/autowire)
gocyclo:
min-complexity: 20
goimports:
local-prefixes: github.com/golangci/golangci-lint
lll:
line-length: 120
misspell:
locale: US

linters:
enable:
- bodyclose
- contextcheck
- dogsled
- errcheck
- errname
- errorlint
- exhaustive
- exportloopref
- forbidigo
- forcetypeassert
- funlen
- gci
- gocognit
- goconst
- gocritic
- gocyclo
- goerr113
- gofmt
- goimports
- gomnd
- gosec
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- nestif
- nilerr
- rowserrcheck
- staticcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- whitespace

issues:
exclude-rules:
- path: _test\.go
linters:
- funlen
- contextcheck
- staticcheck
- stylecheck
- gocyclo
- gocognit
- goerr113
- forcetypeassert
- wrapcheck
- gomnd
- errorlint
- unused
23 changes: 23 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
all: lint test

prepare:
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.54.2

build:
@go build -v ./...

test:
@go test -cover -v ./...

cover:
@go test -race -coverprofile=cover.out -coverpkg=./... ./...
@go tool cover -html=cover.out -o cover.html

lint:
golangci-lint --timeout=5m0s run -v ./...

bench:
go test -benchmem -count 100 -bench .

mod:
go mod tidy && go mod vendor
121 changes: 121 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
[![Go Version][gover-img]][gover] [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![GoReport][rpt-img]][rpt]

# Dependency injection for Go 1.18+ using Generics and reflection

## Functionalities

- Automatically wiring/injecting objects on creation.
- Easy to use (see Usages below).
- `Shared mode` by default that creates only one instance for a type. This option is configurable.
- Ability to overwrite objects of specific types on building which is convenient for unit testing.
- No code generation.

## Installation

```shell
go get github.com/tiendc/autowire
```

## Usage

### General usage

```go
// Suppose you have ServiceA and a creator function
type ServiceA interface {}
func NewServiceA(srvB ServiceB, repoX RepoX) ServiceA {
return <ServiceA-instance>
}

// and ServiceB
type ServiceB interface {}
func NewServiceB(ctx context.Context, repoX RepoX, repoY RepoY) (ServiceB, error) {
return <ServiceB-instance>, nil
}

// and RepoX
type RepoX interface {}
func NewRepoX(s3Client S3Client) (RepoX, error) {
return <RepoX-instance>, nil
}

// and RepoY
type RepoY interface {}
func NewRepoY(redisClient RedisClient) RepoY {
return <RepoY-instance>, nil
}

// and a struct provider
type ProviderStruct struct {
RedisClient RedisClient
S3Client S3Client
}

var (
// init the struct value
providerStruct = &ProviderStruct{...}

// create a container with passing providers
container = MustNewContainer([]any{
// Service providers
NewServiceA,
NewServiceB,
// Repo providers
NewRepoX,
NewRepoY,
// Struct provider (must be a pointer)
providerStruct,
})
)

func main() {
// Update some values of the struct provider
providerStruct.RedisClient = newRedisClient()
providerStruct.S3Client = newS3Client()

// Create ServiceA
serviceA, err := autowire.BuildWithCtx[ServiceA](ctx, container)
// Create RepoX
repoX, err := autowire.Build[RepoX](container)
}
```

### Non-shared mode

```go
// Set sharedMode when create a container
container = MustNewContainer([]any{
// your providers
}, SetSharedMode(false))

// Activate non-shared mode inline for a specific build only
serviceA, err := Build[ServiceA](container, NonSharedMode())
```

### Overwrite values of specific types

This is convenient in unit testing to overwrite specific types only.

```go
// In unit testing, you may want to overwrite `RepoX` and `RepoY` with fake instances
serviceA, err := Build[ServiceA](container, ProviderOverwrite[RepoX](fakeRepoX), ProviderOverwrite[RepoY](fakeRepoY))
```

## Contributing

- You are welcome to make pull requests for new functions and bug fixes.

## License

- [MIT License](LICENSE)

[doc-img]: https://pkg.go.dev/badge/github.com/tiendc/autowire
[doc]: https://pkg.go.dev/github.com/tiendc/autowire
[gover-img]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue
[gover]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue
[ci-img]: https://github.com/tiendc/autowire/actions/workflows/go.yml/badge.svg
[ci]: https://github.com/tiendc/autowire/actions/workflows/go.yml
[cov-img]: https://codecov.io/gh/tiendc/autowire/branch/main/graph/badge.svg
[cov]: https://codecov.io/gh/tiendc/autowire
[rpt-img]: https://goreportcard.com/badge/github.com/tiendc/autowire
[rpt]: https://goreportcard.com/report/github.com/tiendc/autowire
65 changes: 65 additions & 0 deletions autowire.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package autowire

import (
"context"
"fmt"
"reflect"
)

// Build builds object for the specified type within a container
func Build[T any](c Container, opts ...ContextOption) (value T, err error) {
targetType := reflect.TypeFor[T]()

Check failure on line 11 in autowire.go

View workflow job for this annotation

GitHub Actions / build (1.18.x)

undefined: reflect.TypeFor (typecheck)

val, err := c.Build(targetType, opts...)
if err != nil {
return value, err
}

value, ok := val.Interface().(T)
if !ok { // this should never happen
return value, fmt.Errorf("%w: unable to cast result as type '%v'", ErrTypeCast, targetType)
}

return value, nil
}

// BuildWithCtx builds object for the specified type within a container.
// This function will pass the specified context object to every provider that requires a context.
func BuildWithCtx[T any](ctx context.Context, c Container, opts ...ContextOption) (value T, err error) {
targetType := reflect.TypeFor[T]()

Check failure on line 29 in autowire.go

View workflow job for this annotation

GitHub Actions / build (1.18.x)

undefined: reflect.TypeFor (typecheck)

val, err := c.BuildWithCtx(ctx, targetType, opts...)
if err != nil {
return value, err
}

value, ok := val.Interface().(T)
if !ok { // this should never happen
return value, fmt.Errorf("%w: unable to cast result as type '%v'", ErrTypeCast, targetType)
}

return value, nil
}

// Get gets object of a type within a container.
// If no object is created for the type or `sharedMode` is `false`, ErrNotFound is returned.
func Get[T any](c Container) (value T, err error) {
targetType := reflect.TypeFor[T]()

val, err := c.Get(targetType)
if err != nil {
return value, err
}

value, ok := val.Interface().(T)
if !ok { // this should never happen
return value, fmt.Errorf("%w: unable to cast result as type '%v'", ErrTypeCast, targetType)
}

return value, nil
}

// Resolve builds dependency graph for the specified type within a container
func Resolve[T any](c Container) (DependencyGraph, error) {
return c.Resolve(reflect.TypeFor[T]())
}
Loading

0 comments on commit cd1e83e

Please sign in to comment.