Skip to content

Commit 54fed8b

Browse files
authored
Add generic interactor constructor (#8)
1 parent fe22fff commit 54fed8b

File tree

8 files changed

+181
-9
lines changed

8 files changed

+181
-9
lines changed

.github/workflows/tip.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: tip
2+
on:
3+
push:
4+
branches:
5+
- master
6+
- main
7+
pull_request:
8+
env:
9+
GO111MODULE: "on"
10+
RUN_BASE_COVERAGE: "on" # Runs test for PR base in case base test coverage is missing.
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v2
17+
- name: Go cache
18+
uses: actions/cache@v2
19+
with:
20+
# In order:
21+
# * Module download cache
22+
# * Build cache (Linux)
23+
path: |
24+
~/go/pkg/mod
25+
~/.cache/go-build
26+
key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.sum') }}
27+
restore-keys: |
28+
${{ runner.os }}-go-cache
29+
- name: Test
30+
uses: docker://aleksi/golang-tip:master
31+
with:
32+
entrypoint: /bin/sh
33+
args: -c "go version;make test-unit"

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ ifeq ($(DEVGO_PATH),)
2323
DEVGO_PATH := $(shell GO111MODULE=on $(GO) list ${modVendor} -f '{{.Dir}}' -m github.com/bool64/dev)
2424
ifeq ($(DEVGO_PATH),)
2525
$(info Module github.com/bool64/dev not found, downloading.)
26-
DEVGO_PATH := $(shell export GO111MODULE=on && $(GO) mod tidy && $(GO) list -f '{{.Dir}}' -m github.com/bool64/dev)
26+
DEVGO_PATH := $(shell export GO111MODULE=on && $(GO) get github.com/bool64/dev && $(GO) list -f '{{.Dir}}' -m github.com/bool64/dev)
2727
endif
2828
endif
2929

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This abstraction is intended for use with automated transport layer, for example
2424

2525
## Usage
2626

27+
### Input/Output Definitions
28+
2729
```go
2830
// Configure use case interactor in application layer.
2931
type myInput struct {
@@ -36,6 +38,10 @@ type myOutput struct {
3638
Value2 string `json:"value2"`
3739
}
3840

41+
```
42+
### Classic API
43+
44+
```go
3945
u := usecase.NewIOI(new(myInput), new(myOutput), func(ctx context.Context, input, output interface{}) error {
4046
var (
4147
in = input.(*myInput)
@@ -53,6 +59,30 @@ u := usecase.NewIOI(new(myInput), new(myOutput), func(ctx context.Context, input
5359
return nil
5460
})
5561

62+
```
63+
64+
### Generic API with type parameters
65+
66+
With `go1.18` and later (or [`gotip`](https://pkg.go.dev/golang.org/dl/gotip)) you can use simplified generic API instead
67+
of classic API based on `interface{}`.
68+
69+
```go
70+
u := usecase.NewInteractor(func(ctx context.Context, input myInput, output *myOutput) error {
71+
if in.Param1%2 != 0 {
72+
return status.InvalidArgument
73+
}
74+
75+
// Do something to set output based on input.
76+
out.Value1 = in.Param1 + in.Param1
77+
out.Value2 = in.Param2 + in.Param2
78+
79+
return nil
80+
})
81+
```
82+
83+
### Further Configuration And Usage
84+
85+
```go
5686
// Additional properties can be configured for purposes of automated documentation.
5787
u.SetTitle("Doubler")
5888
u.SetDescription("Doubler doubles parameter values.")

generic_go1.18.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package usecase
5+
6+
import (
7+
"context"
8+
"fmt"
9+
)
10+
11+
type IOInteractorOf[i, o interface{}] struct {
12+
IOInteractor
13+
14+
InteractFunc func(ctx context.Context, input i, output *o) error
15+
}
16+
17+
func (ioi IOInteractorOf[i, o]) Invoke(ctx context.Context, input i, output *o) error {
18+
return ioi.InteractFunc(ctx, input, output)
19+
}
20+
21+
// NewInteractor creates generic use case interactor with input and output ports.
22+
//
23+
// It pre-fills name and title with caller function.
24+
// Input is passed by value, while output is passed by pointer to be mutable.
25+
func NewInteractor[i, o any](interact func(ctx context.Context, input i, output *o) error) IOInteractorOf[i, o] {
26+
u := IOInteractorOf[i, o]{}
27+
u.Input = *new(i)
28+
u.Output = new(o)
29+
u.InteractFunc = interact
30+
u.Interactor = Interact(func(ctx context.Context, input, output interface{}) error {
31+
inp, ok := input.(i)
32+
if !ok {
33+
return fmt.Errorf("invalid input type received: %T, expected: %T", input, u.Input)
34+
}
35+
36+
out, ok := output.(*o)
37+
if !ok {
38+
return fmt.Errorf("invalid output type received: %T, expected: %T", output, u.Output)
39+
}
40+
41+
return interact(ctx, inp, out)
42+
})
43+
44+
u.name, u.title = callerFunc()
45+
u.name = filterName(u.name)
46+
47+
return u
48+
}

generic_go1.18_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package usecase_test
5+
6+
import (
7+
"context"
8+
"strconv"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/swaggest/usecase"
13+
)
14+
15+
func TestNewIOI_classic(t *testing.T) {
16+
u := usecase.NewIOI(*new(int), new(string), func(ctx context.Context, input, output interface{}) error {
17+
in := input.(int)
18+
out := output.(*string)
19+
20+
*out = strconv.Itoa(in)
21+
22+
return nil
23+
})
24+
25+
ctx := context.Background()
26+
27+
var out string
28+
assert.NoError(t, u.Interact(ctx, 123, &out))
29+
assert.Equal(t, "123", out)
30+
}
31+
32+
func TestNewInteractor(t *testing.T) {
33+
u := usecase.NewInteractor(func(ctx context.Context, input int, output *string) error {
34+
*output = strconv.Itoa(input)
35+
36+
return nil
37+
})
38+
39+
u.SetDescription("Foo.")
40+
41+
ctx := context.Background()
42+
43+
var out string
44+
assert.NoError(t, u.Interact(ctx, 123, &out))
45+
assert.Equal(t, "123", out)
46+
47+
out = ""
48+
assert.NoError(t, u.Invoke(ctx, 123, &out))
49+
assert.Equal(t, "123", out)
50+
}

go.mod

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
module github.com/swaggest/usecase
22

3-
go 1.12
3+
go 1.18
44

55
require (
6-
github.com/bool64/dev v0.1.41
6+
github.com/bool64/dev v0.1.42
77
github.com/stretchr/testify v1.4.0
88
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.0 // indirect
12+
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
gopkg.in/yaml.v2 v2.2.2 // indirect
14+
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
github.com/bool64/dev v0.1.41 h1:L554LCQZc3d7mtcdPUgDbSrCVbr48/30zgu0VuC/FTA=
2-
github.com/bool64/dev v0.1.41/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
1+
github.com/bool64/dev v0.1.42 h1:Ps0IvNNf/v1MlIXt8Q5YKcKjYsIVLY/fb/5BmA7gepg=
2+
github.com/bool64/dev v0.1.42/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
33
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
44
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

interactor.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,7 @@ func NewIOI(input, output interface{}, interact Interact) IOInteractor {
184184
u.Interactor = interact
185185

186186
u.name, u.title = callerFunc()
187-
188-
u.name = strings.TrimPrefix(u.name, "internal/")
189-
u.name = strings.TrimPrefix(u.name, "usecase.")
190-
u.name = strings.TrimPrefix(u.name, "./main.")
187+
u.name = filterName(u.name)
191188

192189
return u
193190
}
@@ -199,6 +196,14 @@ var titleReplacer = strings.NewReplacer(
199196
")", "",
200197
)
201198

199+
func filterName(name string) string {
200+
name = strings.TrimPrefix(name, "internal/")
201+
name = strings.TrimPrefix(name, "usecase.")
202+
name = strings.TrimPrefix(name, "./main.")
203+
204+
return name
205+
}
206+
202207
// callerFunc returns trimmed path and name of parent function.
203208
func callerFunc() (string, string) {
204209
skipFrames := 2

0 commit comments

Comments
 (0)