From 1beaa1a17c2175024b268901ff2f912240c6cf0f Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Fri, 19 Feb 2021 00:31:15 +0100 Subject: [PATCH] Update examples with usecase.NewIOI (#25) --- .github/workflows/bench.yml | 2 +- .github/workflows/golangci-lint.yml | 4 +- .github/workflows/test-unit.yml | 6 +-- .golangci.yml | 2 + README.md | 28 +++++----- _examples/advanced/_testdata/openapi.json | 29 ++++++----- _examples/advanced/file_multi_upload.go | 11 ++-- _examples/advanced/file_upload.go | 11 ++-- _examples/advanced/gzip_pass_through.go | 51 +++++++++---------- _examples/advanced/json_body.go | 32 +++++------- _examples/advanced/json_body_test.go | 2 +- _examples/advanced/json_map_body.go | 11 ++-- _examples/advanced/json_param.go | 13 ++--- _examples/advanced/json_slice_body.go | 11 ++-- _examples/advanced/query_object.go | 24 ++++----- .../advanced/request_response_mapping.go | 15 ++---- _examples/advanced/router.go | 3 +- _examples/advanced/validation.go | 13 ++--- _examples/basic/main.go | 20 ++++---- _examples/go.mod | 12 ++--- _examples/go.sum | 31 ++++++----- .../infra/nethttp/integration_test.go | 2 +- .../task-api/internal/infra/nethttp/router.go | 6 ++- .../task-api/internal/usecase/create_task.go | 24 ++++----- .../task-api/internal/usecase/find_task.go | 31 +++++------ .../task-api/internal/usecase/find_tasks.go | 16 +++--- .../task-api/internal/usecase/finish_task.go | 23 ++++----- .../task-api/internal/usecase/update_task.go | 19 +++---- go.mod | 15 +++--- go.sum | 26 ++++++---- response/encoder.go | 2 + resttest/client.go | 6 +-- 32 files changed, 222 insertions(+), 279 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 417d94c..ca5888b 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -13,7 +13,7 @@ jobs: bench: strategy: matrix: - go-version: [ 1.15.x ] + go-version: [ 1.16.x ] runs-on: ubuntu-latest steps: - name: Install Go diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 746f57c..46e998e 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -14,10 +14,10 @@ jobs: steps: - uses: actions/checkout@v2 - name: golangci-lint - uses: golangci/golangci-lint-action@v2.3.0 + uses: golangci/golangci-lint-action@v2.4.0 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.34.1 + version: v1.37.0 # Optional: golangci-lint command line arguments. # args: ./the-only-dir-to-analyze/... diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index a8b0490..ba1b816 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -11,7 +11,7 @@ jobs: test: strategy: matrix: - go-version: [ 1.13.x, 1.14.x, 1.15.x ] + go-version: [ 1.13.x, 1.14.x, 1.15.x, 1.16.x ] runs-on: ubuntu-latest steps: - name: Install Go @@ -52,7 +52,7 @@ jobs: if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }} run: cp unit.txt unit-base.txt - name: Comment Test Coverage - if: matrix.go-version == '1.15.x' + if: matrix.go-version == '1.16.x' uses: marocchino/sticky-pull-request-comment@v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -68,7 +68,7 @@ jobs: - name: Upload code coverage - if: matrix.go-version == '1.15.x' + if: matrix.go-version == '1.16.x' uses: codecov/codecov-action@v1 with: file: ./unit.coverprofile diff --git a/.golangci.yml b/.golangci.yml index 4c8b79e..b47eb9a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,6 +28,8 @@ linters: - paralleltest - forbidigo - exhaustivestruct + - ifshort + - cyclop # TODO review issues. issues: exclude-use-default: false diff --git a/README.md b/README.md index e80e19d..ee4fae1 100644 --- a/README.md +++ b/README.md @@ -268,9 +268,6 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" - "github.com/swaggest/swgui/v3cdn" - "github.com/swaggest/usecase" - "github.com/swaggest/usecase/status" "github.com/swaggest/rest" "github.com/swaggest/rest/chirouter" "github.com/swaggest/rest/jsonschema" @@ -279,6 +276,9 @@ import ( "github.com/swaggest/rest/request" "github.com/swaggest/rest/response" "github.com/swaggest/rest/response/gzip" + "github.com/swaggest/swgui/v3cdn" + "github.com/swaggest/usecase" + "github.com/swaggest/usecase/status" ) func main() { @@ -307,33 +307,25 @@ func main() { gzip.Middleware, // Response compression with support for direct gzip pass through. ) - // Create use case interactor. - u := usecase.IOInteractor{} - - // Describe use case interactor. - u.SetTitle("Greeter") - u.SetDescription("Greeter greets you.") - // Declare input port type. type helloInput struct { - Locale string `query:"locale" default:"en-US" pattern:"^[a-z]{2}-[A-Z]{2}$"` + Locale string `query:"locale" default:"en-US" pattern:"^[a-z]{2}-[A-Z]{2}$" enum:"ru-RU,en-US"` Name string `path:"name" minLength:"3"` // Field tags define parameter location and JSON schema constraints. } - u.Input = new(helloInput) // Declare output port type. type helloOutput struct { Now time.Time `header:"X-Now" json:"-"` Message string `json:"message"` } - u.Output = new(helloOutput) - u.SetExpectedErrors(status.InvalidArgument) messages := map[string]string{ "en-US": "Hello, %s!", "ru-RU": "Привет, %s!", } - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) error { + + // Create use case interactor with references to input/output types and interaction function. + u := usecase.NewIOI(new(helloInput), new(helloOutput), func(ctx context.Context, input, output interface{}) error { var ( in = input.(*helloInput) out = output.(*helloOutput) @@ -350,6 +342,12 @@ func main() { return nil }) + // Describe use case interactor. + u.SetTitle("Greeter") + u.SetDescription("Greeter greets you.") + + u.SetExpectedErrors(status.InvalidArgument) + // Add use case handler to router. r.Method(http.MethodGet, "/hello/{name}", nethttp.NewHandler(u)) diff --git a/_examples/advanced/_testdata/openapi.json b/_examples/advanced/_testdata/openapi.json index 1be84c4..8c95b75 100644 --- a/_examples/advanced/_testdata/openapi.json +++ b/_examples/advanced/_testdata/openapi.json @@ -4,7 +4,8 @@ "paths":{ "/file-multi-upload":{ "post":{ - "summary":"Files Uploads With 'multipart/form-data'","description":"","operationId":"", + "summary":"Files Uploads With 'multipart/form-data'","description":"", + "operationId":"_examples/advanced.fileMultiUploader", "parameters":[ { "name":"in_query","in":"query","description":"Simple scalar value in query.", @@ -22,7 +23,8 @@ }, "/file-upload":{ "post":{ - "summary":"File Upload With 'multipart/form-data'","description":"","operationId":"", + "summary":"File Upload With 'multipart/form-data'","description":"", + "operationId":"_examples/advanced.fileUploader", "parameters":[ { "name":"in_query","in":"query","description":"Simple scalar value in query.", @@ -37,7 +39,7 @@ }, "/gzip-pass-through":{ "get":{ - "summary":"","description":"","operationId":"", + "summary":"directGzip","description":"","operationId":"_examples/advanced.directGzip", "parameters":[ { "name":"plainStruct","in":"query","description":"Output plain structure instead of gzip container.", @@ -56,7 +58,7 @@ } }, "head":{ - "summary":"","description":"","operationId":"", + "summary":"directGzip","description":"","operationId":"_examples/advanced.directGzip", "parameters":[ { "name":"plainStruct","in":"query","description":"Output plain structure instead of gzip container.", @@ -107,7 +109,7 @@ "post":{ "summary":"Request With JSON Body", "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.", - "operationId":"", + "operationId":"_examples/advanced.jsonBody", "parameters":[ { "name":"in_query","in":"query","description":"Simple scalar value in query.", @@ -124,8 +126,8 @@ ], "requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedInputWithJSONType2"}}}}, "responses":{ - "200":{ - "description":"OK", + "201":{ + "description":"Created", "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputWithJSONType2"}}} } } @@ -133,7 +135,8 @@ }, "/json-map-body":{ "post":{ - "summary":"Request With JSON Map In Body","description":"Request with JSON object (map) body.","operationId":"", + "summary":"Request With JSON Map In Body","description":"Request with JSON object (map) body.", + "operationId":"_examples/advanced.jsonMapBody", "parameters":[ { "name":"in_query","in":"query","description":"Simple scalar value in query.", @@ -157,7 +160,7 @@ "get":{ "summary":"Request With JSON Query Parameter", "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.", - "operationId":"", + "operationId":"_examples/advanced.jsonParam", "parameters":[ { "name":"in_query","in":"query","description":"Simple scalar value in query.", @@ -186,7 +189,7 @@ }, "/json-slice-body":{ "post":{ - "summary":"Request With JSON Array In Body","description":"","operationId":"", + "summary":"Request With JSON Array In Body","description":"","operationId":"_examples/advanced.jsonSliceBody", "parameters":[ { "name":"in_query","in":"query","description":"Simple scalar value in query.", @@ -261,7 +264,8 @@ }, "/query-object":{ "get":{ - "summary":"Request With Object As Query Parameter","description":"","operationId":"", + "summary":"Request With Object As Query Parameter","description":"", + "operationId":"_examples/advanced.queryObject", "parameters":[ { "name":"in_query","in":"query","description":"Object value in query.","style":"deepObject","explode":true, @@ -303,7 +307,8 @@ }, "/validation":{ "post":{ - "summary":"Validation","description":"Input/Output with validation. Custom annotation.","operationId":"", + "summary":"Validation","description":"Input/Output with validation. Custom annotation.", + "operationId":"_examples/advanced.validation", "parameters":[ { "name":"q","in":"query", diff --git a/_examples/advanced/file_multi_upload.go b/_examples/advanced/file_multi_upload.go index 4f41c08..ff572cf 100644 --- a/_examples/advanced/file_multi_upload.go +++ b/_examples/advanced/file_multi_upload.go @@ -9,10 +9,6 @@ import ( ) func fileMultiUploader() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("Files Uploads With 'multipart/form-data'") - type upload struct { Simple string `formData:"simple" description:"Simple scalar value in body."` Query int `query:"in_query" description:"Simple scalar value in query."` @@ -30,10 +26,7 @@ func fileMultiUploader() usecase.Interactor { Query int `json:"inQuery"` } - u.Input = new(upload) - u.Output = new(info) - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + u := usecase.NewIOI(new(upload), new(info), func(ctx context.Context, input, output interface{}) (err error) { var ( in = input.(*upload) out = output.(*info) @@ -81,5 +74,7 @@ func fileMultiUploader() usecase.Interactor { return nil }) + u.SetTitle("Files Uploads With 'multipart/form-data'") + return u } diff --git a/_examples/advanced/file_upload.go b/_examples/advanced/file_upload.go index 264f683..4ec2d2e 100644 --- a/_examples/advanced/file_upload.go +++ b/_examples/advanced/file_upload.go @@ -9,10 +9,6 @@ import ( ) func fileUploader() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("File Upload With 'multipart/form-data'") - type upload struct { Simple string `formData:"simple" description:"Simple scalar value in body."` Query int `query:"in_query" description:"Simple scalar value in query."` @@ -30,10 +26,7 @@ func fileUploader() usecase.Interactor { Query int `json:"inQuery"` } - u.Input = new(upload) - u.Output = new(info) - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + u := usecase.NewIOI(new(upload), new(info), func(ctx context.Context, input, output interface{}) (err error) { var ( in = input.(*upload) out = output.(*info) @@ -81,5 +74,7 @@ func fileUploader() usecase.Interactor { return nil }) + u.SetTitle("File Upload With 'multipart/form-data'") + return u } diff --git a/_examples/advanced/gzip_pass_through.go b/_examples/advanced/gzip_pass_through.go index e1262f7..4b597ef 100644 --- a/_examples/advanced/gzip_pass_through.go +++ b/_examples/advanced/gzip_pass_through.go @@ -49,8 +49,6 @@ func (dc gzipPassThroughContainer) gzipPassThroughStruct() gzipPassThroughStruct } func directGzip() usecase.Interactor { - u := usecase.IOInteractor{} - // Prepare moderately big JSON, resulting JSON payload is ~67KB. rawData := gzipPassThroughStruct{ ID: 123, @@ -67,31 +65,30 @@ func directGzip() usecase.Interactor { panic(err) } - u.Input = new(gzipPassThroughInput) - u.Output = new(gzipPassThroughOutput) - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) error { - var ( - in = input.(*gzipPassThroughInput) - out = output.(*gzipPassThroughOutput) - ) - - if in.PlainStruct { - o := rawData - o.Header = "cba" - *out = o - } else { - o := dataFromCache - o.Header = "abc" - *out = o - } - - // Imitating an internal read operation on data in container. - if in.CountItems { - _ = len((*out).gzipPassThroughStruct().Text) - } - - return nil - }) + u := usecase.NewIOI(new(gzipPassThroughInput), new(gzipPassThroughOutput), + func(ctx context.Context, input, output interface{}) error { + var ( + in = input.(*gzipPassThroughInput) + out = output.(*gzipPassThroughOutput) + ) + + if in.PlainStruct { + o := rawData + o.Header = "cba" + *out = o + } else { + o := dataFromCache + o.Header = "abc" + *out = o + } + + // Imitating an internal read operation on data in container. + if in.CountItems { + _ = len((*out).gzipPassThroughStruct().Text) + } + + return nil + }) return u } diff --git a/_examples/advanced/json_body.go b/_examples/advanced/json_body.go index 1628209..b51c69c 100644 --- a/_examples/advanced/json_body.go +++ b/_examples/advanced/json_body.go @@ -8,11 +8,6 @@ import ( ) func jsonBody() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("Request With JSON Body") - u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.") - type JSONPayload struct { ID int `json:"id"` Name string `json:"name"` @@ -32,22 +27,23 @@ func jsonBody() usecase.Interactor { JSONPayload } - u.Input = new(inputWithJSON) - u.Output = new(outputWithJSON) + u := usecase.NewIOI(new(inputWithJSON), new(outputWithJSON), + func(ctx context.Context, input, output interface{}) (err error) { + var ( + in = input.(*inputWithJSON) + out = output.(*outputWithJSON) + ) - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { - var ( - in = input.(*inputWithJSON) - out = output.(*outputWithJSON) - ) + out.Query = in.Query + out.Header = in.Header + out.Path = in.Path + out.JSONPayload = in.JSONPayload - out.Query = in.Query - out.Header = in.Header - out.Path = in.Path - out.JSONPayload = in.JSONPayload + return nil + }) - return nil - }) + u.SetTitle("Request With JSON Body") + u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.") return u } diff --git a/_examples/advanced/json_body_test.go b/_examples/advanced/json_body_test.go index b3972c2..5f0c1df 100644 --- a/_examples/advanced/json_body_test.go +++ b/_examples/advanced/json_body_test.go @@ -24,6 +24,6 @@ func Benchmark_jsonBody(b *testing.B) { req.Header.Set("X-Header", "def") req.SetBody([]byte(`{"id":321,"name":"Jane"}`)) }, func(i int, resp *fasthttp.Response) bool { - return resp.StatusCode() == http.StatusOK + return resp.StatusCode() == http.StatusCreated }) } diff --git a/_examples/advanced/json_map_body.go b/_examples/advanced/json_map_body.go index 98d928d..5e80d7e 100644 --- a/_examples/advanced/json_map_body.go +++ b/_examples/advanced/json_map_body.go @@ -20,20 +20,13 @@ func (j *jsonMapReq) UnmarshalJSON(data []byte) error { } func jsonMapBody() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("Request With JSON Map In Body") - type jsonOutput struct { Header string `json:"inHeader"` Query int `json:"inQuery"` Data JSONMapPayload `json:"data"` } - u.Input = new(jsonMapReq) - u.Output = new(jsonOutput) - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + u := usecase.NewIOI(new(jsonMapReq), new(jsonOutput), func(ctx context.Context, input, output interface{}) (err error) { var ( in = input.(*jsonMapReq) out = output.(*jsonOutput) @@ -46,5 +39,7 @@ func jsonMapBody() usecase.Interactor { return nil }) + u.SetTitle("Request With JSON Map In Body") + return u } diff --git a/_examples/advanced/json_param.go b/_examples/advanced/json_param.go index e72546d..95b504d 100644 --- a/_examples/advanced/json_param.go +++ b/_examples/advanced/json_param.go @@ -7,11 +7,6 @@ import ( ) func jsonParam() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("Request With JSON Query Parameter") - u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.") - type JSONPayload struct { ID int `json:"id"` Name string `json:"name"` @@ -31,10 +26,7 @@ func jsonParam() usecase.Interactor { JSONPayload } - u.Input = new(inputWithJSON) - u.Output = new(outputWithJSON) - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + u := usecase.NewIOI(new(inputWithJSON), new(outputWithJSON), func(ctx context.Context, input, output interface{}) (err error) { var ( in = input.(*inputWithJSON) out = output.(*outputWithJSON) @@ -48,5 +40,8 @@ func jsonParam() usecase.Interactor { return nil }) + u.SetTitle("Request With JSON Query Parameter") + u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.") + return u } diff --git a/_examples/advanced/json_slice_body.go b/_examples/advanced/json_slice_body.go index 948ee65..d85b0b7 100644 --- a/_examples/advanced/json_slice_body.go +++ b/_examples/advanced/json_slice_body.go @@ -20,20 +20,13 @@ func (j *jsonSliceReq) UnmarshalJSON(data []byte) error { } func jsonSliceBody() usecase.Interactor { - u := usecase.IOInteractor{} - type jsonOutput struct { Header string `json:"inHeader"` Query int `json:"inQuery"` Data JSONSlicePayload `json:"data"` } - u.SetTitle("Request With JSON Array In Body") - - u.Input = new(jsonSliceReq) - u.Output = new(jsonOutput) - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + u := usecase.NewIOI(new(jsonSliceReq), new(jsonOutput), func(ctx context.Context, input, output interface{}) (err error) { var ( in = input.(*jsonSliceReq) out = output.(*jsonOutput) @@ -46,5 +39,7 @@ func jsonSliceBody() usecase.Interactor { return nil }) + u.SetTitle("Request With JSON Array In Body") + return u } diff --git a/_examples/advanced/query_object.go b/_examples/advanced/query_object.go index c0d0f1c..3cc91d2 100644 --- a/_examples/advanced/query_object.go +++ b/_examples/advanced/query_object.go @@ -7,10 +7,6 @@ import ( ) func queryObject() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("Request With Object As Query Parameter") - type inputQueryObject struct { Query map[int]float64 `query:"in_query" description:"Object value in query."` } @@ -19,19 +15,19 @@ func queryObject() usecase.Interactor { Query map[int]float64 `json:"inQuery"` } - u.Input = new(inputQueryObject) - u.Output = new(outputQueryObject) + u := usecase.NewIOI(new(inputQueryObject), new(outputQueryObject), + func(ctx context.Context, input, output interface{}) (err error) { + var ( + in = input.(*inputQueryObject) + out = output.(*outputQueryObject) + ) - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { - var ( - in = input.(*inputQueryObject) - out = output.(*outputQueryObject) - ) + out.Query = in.Query - out.Query = in.Query + return nil + }) - return nil - }) + u.SetTitle("Request With Object As Query Parameter") return u } diff --git a/_examples/advanced/request_response_mapping.go b/_examples/advanced/request_response_mapping.go index 33504b4..2de9075 100644 --- a/_examples/advanced/request_response_mapping.go +++ b/_examples/advanced/request_response_mapping.go @@ -7,12 +7,6 @@ import ( ) func reqRespMapping() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("Request Response Mapping") - u.SetName("reqRespMapping") - u.SetDescription("This use case has transport concerns fully decoupled with external req/resp mapping.") - type inputPort struct { Val1 string `description:"Simple scalar value."` Val2 int `description:"Simple scalar value."` @@ -23,10 +17,7 @@ func reqRespMapping() usecase.Interactor { Val2 int `json:"-"` } - u.Input = new(inputPort) - u.Output = new(outputPort) - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + u := usecase.NewIOI(new(inputPort), new(outputPort), func(ctx context.Context, input, output interface{}) (err error) { var ( in = input.(*inputPort) out = output.(*outputPort) @@ -38,5 +29,9 @@ func reqRespMapping() usecase.Interactor { return nil }) + u.SetTitle("Request Response Mapping") + u.SetName("reqRespMapping") + u.SetDescription("This use case has transport concerns fully decoupled with external req/resp mapping.") + return u } diff --git a/_examples/advanced/router.go b/_examples/advanced/router.go index cea8f84..25b8092 100644 --- a/_examples/advanced/router.go +++ b/_examples/advanced/router.go @@ -58,7 +58,8 @@ func NewRouter() http.Handler { r.Method(http.MethodPost, "/file-upload", nethttp.NewHandler(fileUploader())) r.Method(http.MethodPost, "/file-multi-upload", nethttp.NewHandler(fileMultiUploader())) r.Method(http.MethodGet, "/json-param/{in-path}", nethttp.NewHandler(jsonParam())) - r.Method(http.MethodPost, "/json-body/{in-path}", nethttp.NewHandler(jsonBody())) + r.Method(http.MethodPost, "/json-body/{in-path}", nethttp.NewHandler(jsonBody(), + nethttp.SuccessStatus(http.StatusCreated))) r.Method(http.MethodPost, "/json-body-validation/{in-path}", nethttp.NewHandler(jsonBodyValidation())) r.Method(http.MethodPost, "/json-slice-body", nethttp.NewHandler(jsonSliceBody())) diff --git a/_examples/advanced/validation.go b/_examples/advanced/validation.go index 8be3737..be47e1b 100644 --- a/_examples/advanced/validation.go +++ b/_examples/advanced/validation.go @@ -7,11 +7,6 @@ import ( ) func validation() usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetTitle("Validation") - u.SetDescription("Input/Output with validation.") - type inputPort struct { Header int `header:"X-Input" minimum:"10" description:"Request minimum: 10, response maximum: 20."` Query bool `query:"q" description:"This parameter will bypass explicit validation as it does not have constraints."` @@ -28,10 +23,7 @@ func validation() usecase.Interactor { } `json:"data" required:"true"` } - u.Input = new(inputPort) - u.Output = new(outputPort) - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + u := usecase.NewIOI(new(inputPort), new(outputPort), func(ctx context.Context, input, output interface{}) (err error) { in := input.(*inputPort) out := output.(*outputPort) @@ -42,5 +34,8 @@ func validation() usecase.Interactor { return nil }) + u.SetTitle("Validation") + u.SetDescription("Input/Output with validation.") + return u } diff --git a/_examples/basic/main.go b/_examples/basic/main.go index 9b2b99a..3786c6e 100644 --- a/_examples/basic/main.go +++ b/_examples/basic/main.go @@ -49,33 +49,25 @@ func main() { gzip.Middleware, // Response compression with support for direct gzip pass through. ) - // Create use case interactor. - u := usecase.IOInteractor{} - - // Describe use case interactor. - u.SetTitle("Greeter") - u.SetDescription("Greeter greets you.") - // Declare input port type. type helloInput struct { Locale string `query:"locale" default:"en-US" pattern:"^[a-z]{2}-[A-Z]{2}$" enum:"ru-RU,en-US"` Name string `path:"name" minLength:"3"` // Field tags define parameter location and JSON schema constraints. } - u.Input = new(helloInput) // Declare output port type. type helloOutput struct { Now time.Time `header:"X-Now" json:"-"` Message string `json:"message"` } - u.Output = new(helloOutput) - u.SetExpectedErrors(status.InvalidArgument) messages := map[string]string{ "en-US": "Hello, %s!", "ru-RU": "Привет, %s!", } - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) error { + + // Create use case interactor with references to input/output types and interaction function. + u := usecase.NewIOI(new(helloInput), new(helloOutput), func(ctx context.Context, input, output interface{}) error { var ( in = input.(*helloInput) out = output.(*helloOutput) @@ -92,6 +84,12 @@ func main() { return nil }) + // Describe use case interactor. + u.SetTitle("Greeter") + u.SetDescription("Greeter greets you.") + + u.SetExpectedErrors(status.InvalidArgument) + // Add use case handler to router. r.Method(http.MethodGet, "/hello/{name}", nethttp.NewHandler(u)) diff --git a/_examples/go.mod b/_examples/go.mod index b96438b..a815057 100644 --- a/_examples/go.mod +++ b/_examples/go.mod @@ -5,17 +5,17 @@ go 1.13 replace github.com/swaggest/rest => ../ require ( - github.com/bool64/dev v0.1.15 + github.com/bool64/dev v0.1.18 github.com/bool64/httptestbench v0.1.1 - github.com/go-chi/chi v1.5.1 + github.com/go-chi/chi v1.5.2 github.com/kelseyhightower/envconfig v1.4.0 github.com/stretchr/testify v1.7.0 - github.com/swaggest/assertjson v1.6.2 - github.com/swaggest/jsonschema-go v0.3.14 + github.com/swaggest/assertjson v1.6.3 + github.com/swaggest/jsonschema-go v0.3.15 github.com/swaggest/openapi-go v0.2.9 github.com/swaggest/rest v0.1.17 - github.com/swaggest/swgui v1.1.2 - github.com/swaggest/usecase v0.1.1 + github.com/swaggest/swgui v1.2.0 + github.com/swaggest/usecase v0.1.2 github.com/valyala/fasthttp v1.17.0 golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect ) diff --git a/_examples/go.sum b/_examples/go.sum index 041ff5a..596e59f 100644 --- a/_examples/go.sum +++ b/_examples/go.sum @@ -6,13 +6,16 @@ github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/bool64/dev v0.1.7/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg= github.com/bool64/dev v0.1.10/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg= github.com/bool64/dev v0.1.12/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg= -github.com/bool64/dev v0.1.15 h1:EWvuiPnX4K99F7tt7drpNjhoGFO2dB3ehs4kZAbXvSU= github.com/bool64/dev v0.1.15/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU= +github.com/bool64/dev v0.1.17/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU= +github.com/bool64/dev v0.1.18 h1:uMN5MsHrVWmtRoauefI8wD86b8vbXYnrCZlIhFGyuXI= +github.com/bool64/dev v0.1.18/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU= github.com/bool64/httptestbench v0.1.1 h1:08112fXc5F1XuTWLdC9lPGB4FSWNACoeUWTeoR0a2qA= github.com/bool64/httptestbench v0.1.1/go.mod h1:gSmfFacu85Je3u6ZjmLZsFolcv7cwg1fLNUCzq2UUsM= github.com/bool64/shared v0.1.0/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU= -github.com/bool64/shared v0.1.1 h1:oeJC8LWpaCO9K3dvvVpyoxs9YXkOiGF8onBKUSNdJ2I= github.com/bool64/shared v0.1.1/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU= +github.com/bool64/shared v0.1.2 h1:3W41/TM41O4dMZRZbKErzMcSZQuDG4UnkoWZWaQnTTo= +github.com/bool64/shared v0.1.2/go.mod h1:Yt5dVKw6njpWoIN3rRV9mgkZO4CExiQUw9Q8n/Vovmo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -26,8 +29,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w= -github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= +github.com/go-chi/chi v1.5.2 h1:YcLIBANL4OTaAOcTdp//sskGa0yGACQMCtbnr7YEn0Q= +github.com/go-chi/chi v1.5.2/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -115,16 +118,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/swaggest/assertjson v1.0.0/go.mod h1:mE5ltBbrB+Ya8Xar5OMITxya76vwLZMoPG8aXF7sVQc= github.com/swaggest/assertjson v1.4.1/go.mod h1:v7atNMMlIa5ry2H1rgCHhvz7eho8yfP85UGio0+TFEI= github.com/swaggest/assertjson v1.6.0/go.mod h1:2dZU3TJoGcH+QPrekon0+zG5hJnZUpdEAxiLoxmJALI= -github.com/swaggest/assertjson v1.6.2 h1:4v4ueAy466hisGIeQ2WmFDyOJiE0YrCDDRaSXor642I= -github.com/swaggest/assertjson v1.6.2/go.mod h1:7/CWJAKS2t0KXIGsh5no8qyRXluwQ4fVduV3+Jue/MY= +github.com/swaggest/assertjson v1.6.3 h1:KvD1Y9nb7b6x6swrNdgXks0Z/h8fyWZetw0zhGFnD8g= +github.com/swaggest/assertjson v1.6.3/go.mod h1:fShyZuy4F57C1smqdYNVYDyehECSJLmvYiCGwWyQEKo= github.com/swaggest/form v1.11.1 h1:dNRMhld2VkVA6J9sjKaWSoabEP92G5NNlLs9wW+zN/Q= github.com/swaggest/form v1.11.1/go.mod h1:XI/xmw62iJuKxhbqvxOj1ri7VJFAaeRsX5qNdHVkbB4= github.com/swaggest/jsonschema-go v0.2.1/go.mod h1:QFauBdPTrU1UltwocM5FzOWnVjVVtcWkJWG3NlK9sV0= github.com/swaggest/jsonschema-go v0.2.4/go.mod h1:m4VV88Gbi7lCrt9ckJzigK1rMlEeFjdZUkJr1o5MnDE= github.com/swaggest/jsonschema-go v0.3.7/go.mod h1:TrWgbug4p2ZgcxnHDz+CvYvEtJ5KckL/XOV4mSR6FGw= github.com/swaggest/jsonschema-go v0.3.13/go.mod h1:INO3Dt0DeyNCbQbqMXNpMQ489BcyRtSb/k62OfmHbpY= -github.com/swaggest/jsonschema-go v0.3.14 h1:HmXY9SbMtwedXM6JOlRcAOxtJ0wH2WURWlnow40CAoo= -github.com/swaggest/jsonschema-go v0.3.14/go.mod h1:ohKskUhEMf/bhSi2zY0A5aLZoMz7ZkEafEvniNKBaQg= +github.com/swaggest/jsonschema-go v0.3.15 h1:6D23R4rhU2fVELiMyXtssVxlS/8eq6krRzemv07VvK8= +github.com/swaggest/jsonschema-go v0.3.15/go.mod h1:1JyQxHbtZcK3WqGqRybu022vrDdI02j9Xu9sK/Z58Tg= github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM= github.com/swaggest/openapi-go v0.1.13/go.mod h1:fju3Ka5zb8qBKf4789zLNVUqITydWLDjtPloZhhYHL8= github.com/swaggest/openapi-go v0.2.9 h1:JJQrDSRtKMycIkLju97yjhJbw72mqhsCDJjqFB8HvSs= @@ -132,16 +135,17 @@ github.com/swaggest/openapi-go v0.2.9/go.mod h1:tr1pt/xuYlmgk8HZDjfmx/L7zfKLCmxR github.com/swaggest/refl v0.1.0/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.2/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.3/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= -github.com/swaggest/refl v0.1.6 h1:z5drruoOSfWoB4aPuL22cMB5suLuxXS22oQkpTQ093o= github.com/swaggest/refl v0.1.6/go.mod h1:XqZZZ8i6hptbvd3A8pvXkF0ontg0Wziqe7OFAnvAZMk= +github.com/swaggest/refl v0.1.7 h1:pK2nWacMS6MIgeEdRVfmNUAxKih6vHIUF59osZrxpmY= +github.com/swaggest/refl v0.1.7/go.mod h1:acYd5x8NNxivp+ZHdRZKJYz66n/qjo3Q9Sa/jAivljQ= github.com/swaggest/swgen v0.6.20/go.mod h1:ipkZNfwztgRfbOWUllLZawfmxXprT8flqNJO9XhoMwM= github.com/swaggest/swgen v0.6.23/go.mod h1:gj2yCLONy3kosKjwRtQeT5O9qqlhUvXAiDnbVwBUNFM= github.com/swaggest/swgen v0.6.27 h1:bO3CtX4NvuetPuABMChEEp2N/bjsJraegaJpYZ72aoI= github.com/swaggest/swgen v0.6.27/go.mod h1:tQLsoQVLcvtAMzNbm26JbWXDHZM4BBRuI5nemk3gEDw= -github.com/swaggest/swgui v1.1.2 h1:0DNQ6gF2Rf/5NCQeStkJTbVA7k5+ulzlVKHFTyxqCt4= -github.com/swaggest/swgui v1.1.2/go.mod h1:b6hEcX7AbKHyhOgQ8DpbdPGKBQLD7lcL1RbxZLherIs= -github.com/swaggest/usecase v0.1.1 h1:z/jGjLBMoTMfkOvgLbqRAYBzoabNBitGhSzC7k2bZMk= -github.com/swaggest/usecase v0.1.1/go.mod h1:pgdcCw5G9H9S3J++CE2UZgMd0oxXvCuM1kCs1PP6Mgw= +github.com/swaggest/swgui v1.2.0 h1:zZWybwcfeWUNgOJOa+/FkLtYi7xI7pvGXnJrPcwh8vY= +github.com/swaggest/swgui v1.2.0/go.mod h1:gn/xO+5S+BDFhtZP05gPiB/P6cXurNPv6kAeDJYgT98= +github.com/swaggest/usecase v0.1.2 h1:Fi3z0nig4gBcX0n0nnEHD8QzCttdV3lAPmaoX3nQowA= +github.com/swaggest/usecase v0.1.2/go.mod h1:BKvpHx94bpg+4RCXbrCv0jx8CpJRr5P8dYK0b4UfUZc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.17.0 h1:P8/koH4aSnJ4xbd0cUUFEGQs3jQqIxoDDyRQrUiAkqg= @@ -149,6 +153,7 @@ github.com/valyala/fasthttp v1.17.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8erm github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vearutop/json5 v0.1.2 h1:AD+VTRiN8TZb4/ObzXXyYIY66AVNqdBn0O8ks/WqgH0= github.com/vearutop/json5 v0.1.2/go.mod h1:0EGfZGmwGu042tElaCn8+Song+vEMQOOgChj+mzD7OQ= +github.com/vearutop/statigz v1.1.1/go.mod h1:kyRG5JBG0J2bGxtNEvA2U8vXwpkhDDiPJvpOwAMTpbw= github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= diff --git a/_examples/task-api/internal/infra/nethttp/integration_test.go b/_examples/task-api/internal/infra/nethttp/integration_test.go index 939eb18..a236139 100644 --- a/_examples/task-api/internal/infra/nethttp/integration_test.go +++ b/_examples/task-api/internal/infra/nethttp/integration_test.go @@ -26,7 +26,7 @@ func Test_taskLifeSpan(t *testing.T) { WithBody([]byte(`{"deadline": "2020-05-17T11:12:42.085Z","goal": "string"}`)). Concurrently() - assert.NoError(t, rc.ExpectResponseStatus(http.StatusOK)) + assert.NoError(t, rc.ExpectResponseStatus(http.StatusCreated)) assert.NoError(t, rc.ExpectResponseBody([]byte(`{"createdAt": "",`+ `"deadline": "2020-05-17T11:12:42.085Z","goal": "string","id": 1}`))) diff --git a/_examples/task-api/internal/infra/nethttp/router.go b/_examples/task-api/internal/infra/nethttp/router.go index b1a3d0f..a6cc32d 100644 --- a/_examples/task-api/internal/infra/nethttp/router.go +++ b/_examples/task-api/internal/infra/nethttp/router.go @@ -58,7 +58,8 @@ func NewRouter(locator *service.Locator) http.Handler { return nil })) r.Group(func(r chi.Router) { - r.Method(http.MethodPost, "/tasks", nethttp.NewHandler(usecase.CreateTask(locator))) + r.Method(http.MethodPost, "/tasks", nethttp.NewHandler(usecase.CreateTask(locator), + nethttp.SuccessStatus(http.StatusCreated))) r.Method(http.MethodPut, "/tasks/{id}", nethttp.NewHandler(usecase.UpdateTask(locator), ff)) r.Method(http.MethodGet, "/tasks/{id}", nethttp.NewHandler(usecase.FindTask(locator), ff)) r.Method(http.MethodGet, "/tasks", nethttp.NewHandler(usecase.FindTasks(locator))) @@ -83,7 +84,8 @@ func NewRouter(locator *service.Locator) http.Handler { r.Route("/user", func(r chi.Router) { r.Group(func(r chi.Router) { r.Use(userAuth, nethttp.HTTPBasicSecurityMiddleware(apiSchema, "User", "User access")) - r.Method(http.MethodPost, "/tasks", nethttp.NewHandler(usecase.CreateTask(locator))) + r.Method(http.MethodPost, "/tasks", nethttp.NewHandler(usecase.CreateTask(locator), + nethttp.SuccessStatus(http.StatusCreated))) }) }) diff --git a/_examples/task-api/internal/usecase/create_task.go b/_examples/task-api/internal/usecase/create_task.go index 349cdaa..62ae7da 100644 --- a/_examples/task-api/internal/usecase/create_task.go +++ b/_examples/task-api/internal/usecase/create_task.go @@ -12,21 +12,8 @@ import ( // CreateTask creates usecase interactor. func CreateTask(deps interface { TaskCreator() task.Creator -}) usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetName("createTask") - u.SetTitle("Create Task") - u.SetDescription("Create task to be done.") - u.Input = new(task.Value) - u.Output = new(task.Entity) - u.SetExpectedErrors( - status.AlreadyExists, - status.InvalidArgument, - ) - u.SetTags("Tasks") - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) error { +}) usecase.IOInteractor { + u := usecase.NewIOI(new(task.Value), new(task.Entity), func(ctx context.Context, input, output interface{}) error { var ( in = input.(*task.Value) out = output.(*task.Entity) @@ -39,5 +26,12 @@ func CreateTask(deps interface { return err }) + u.SetDescription("Create task to be done.") + u.SetExpectedErrors( + status.AlreadyExists, + status.InvalidArgument, + ) + u.SetTags("Tasks") + return u } diff --git a/_examples/task-api/internal/usecase/find_task.go b/_examples/task-api/internal/usecase/find_task.go index d9dedfb..803a21a 100644 --- a/_examples/task-api/internal/usecase/find_task.go +++ b/_examples/task-api/internal/usecase/find_task.go @@ -11,31 +11,26 @@ import ( // FindTask creates usecase interactor. func FindTask(deps interface { TaskFinder() task.Finder -}) usecase.Interactor { - u := usecase.IOInteractor{} +}) usecase.IOInteractor { + u := usecase.NewIOI(new(task.Identity), new(task.Entity), + func(ctx context.Context, input, output interface{}) error { + var ( + in = input.(*task.Identity) + out = output.(*task.Entity) + err error + ) + + *out, err = deps.TaskFinder().FindByID(ctx, *in) + + return err + }) - u.SetName("findTask") - u.SetTitle("Find Task") u.SetDescription("Find task by ID.") - u.Input = new(task.Identity) - u.Output = new(task.Entity) u.SetExpectedErrors( status.NotFound, status.InvalidArgument, ) u.SetTags("Tasks") - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) error { - var ( - in = input.(*task.Identity) - out = output.(*task.Entity) - err error - ) - - *out, err = deps.TaskFinder().FindByID(ctx, *in) - - return err - }) - return u } diff --git a/_examples/task-api/internal/usecase/find_tasks.go b/_examples/task-api/internal/usecase/find_tasks.go index 7e1c96f..37bcae0 100644 --- a/_examples/task-api/internal/usecase/find_tasks.go +++ b/_examples/task-api/internal/usecase/find_tasks.go @@ -12,16 +12,8 @@ import ( // FindTasks creates usecase interactor. func FindTasks(deps interface { TaskFinder() task.Finder -}) usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetName("findTasks") - u.SetTitle("Find Tasks") - u.SetDescription("Find all tasks.") - u.Output = new([]task.Entity) - u.SetTags("Tasks") - - u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) error { +}) usecase.IOInteractor { + u := usecase.NewIOI(nil, new([]task.Entity), func(ctx context.Context, input, output interface{}) error { out, ok := output.(*[]task.Entity) if !ok { return fmt.Errorf("%w: unexpected output type %T", status.Unimplemented, output) @@ -32,5 +24,9 @@ func FindTasks(deps interface { return nil }) + u.SetDescription("Find all tasks.") + u.Output = new([]task.Entity) + u.SetTags("Tasks") + return u } diff --git a/_examples/task-api/internal/usecase/finish_task.go b/_examples/task-api/internal/usecase/finish_task.go index 47f6910..6ddb41a 100644 --- a/_examples/task-api/internal/usecase/finish_task.go +++ b/_examples/task-api/internal/usecase/finish_task.go @@ -13,20 +13,8 @@ type finishTaskDeps interface { } // FinishTask creates usecase interactor. -func FinishTask(deps finishTaskDeps) usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetName("closeTask") - u.SetTitle("Finish Task") - u.SetDescription("Finish task by ID.") - u.Input = new(task.Identity) - u.SetExpectedErrors( - status.NotFound, - status.InvalidArgument, - ) - u.SetTags("Tasks") - - u.Interactor = usecase.Interact(func(ctx context.Context, input, _ interface{}) error { +func FinishTask(deps finishTaskDeps) usecase.IOInteractor { + u := usecase.NewIOI(new(task.Identity), nil, func(ctx context.Context, input, _ interface{}) error { var ( in = input.(*task.Identity) err error @@ -37,5 +25,12 @@ func FinishTask(deps finishTaskDeps) usecase.Interactor { return err }) + u.SetDescription("Finish task by ID.") + u.SetExpectedErrors( + status.NotFound, + status.InvalidArgument, + ) + u.SetTags("Tasks") + return u } diff --git a/_examples/task-api/internal/usecase/update_task.go b/_examples/task-api/internal/usecase/update_task.go index 3f5acd6..cdcaf63 100644 --- a/_examples/task-api/internal/usecase/update_task.go +++ b/_examples/task-api/internal/usecase/update_task.go @@ -17,18 +17,7 @@ type updateTask struct { func UpdateTask(deps interface { TaskUpdater() task.Updater }) usecase.Interactor { - u := usecase.IOInteractor{} - - u.SetName("updateTask") - u.SetTitle("Update Task") - u.SetDescription("Update existing task.") - u.Input = new(updateTask) - u.SetExpectedErrors( - status.InvalidArgument, - ) - u.SetTags("Tasks") - - u.Interactor = usecase.Interact(func(ctx context.Context, input, _ interface{}) error { + u := usecase.NewIOI(new(updateTask), nil, func(ctx context.Context, input, _ interface{}) error { var ( in = input.(*updateTask) err error @@ -39,5 +28,11 @@ func UpdateTask(deps interface { return err }) + u.SetDescription("Update existing task.") + u.SetExpectedErrors( + status.InvalidArgument, + ) + u.SetTags("Tasks") + return u } diff --git a/go.mod b/go.mod index 5cec6db..aaba50e 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,17 @@ module github.com/swaggest/rest go 1.13 require ( - github.com/bool64/dev v0.1.15 - github.com/bool64/shared v0.1.1 + github.com/bool64/dev v0.1.18 + github.com/bool64/shared v0.1.2 github.com/cespare/xxhash/v2 v2.1.1 - github.com/go-chi/chi v1.5.1 + github.com/go-chi/chi v1.5.2 + github.com/iancoleman/orderedmap v0.2.0 // indirect github.com/santhosh-tekuri/jsonschema/v2 v2.2.0 github.com/stretchr/testify v1.7.0 - github.com/swaggest/assertjson v1.6.2 + github.com/swaggest/assertjson v1.6.3 github.com/swaggest/form v1.11.1 - github.com/swaggest/jsonschema-go v0.3.14 + github.com/swaggest/jsonschema-go v0.3.15 github.com/swaggest/openapi-go v0.2.9 - github.com/swaggest/refl v0.1.6 - github.com/swaggest/usecase v0.1.1 + github.com/swaggest/refl v0.1.7 + github.com/swaggest/usecase v0.1.2 ) diff --git a/go.sum b/go.sum index 9e9adbe..82440c1 100644 --- a/go.sum +++ b/go.sum @@ -3,11 +3,14 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/bool64/dev v0.1.7/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg= github.com/bool64/dev v0.1.10/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg= github.com/bool64/dev v0.1.12/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg= -github.com/bool64/dev v0.1.15 h1:EWvuiPnX4K99F7tt7drpNjhoGFO2dB3ehs4kZAbXvSU= github.com/bool64/dev v0.1.15/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU= +github.com/bool64/dev v0.1.17/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU= +github.com/bool64/dev v0.1.18 h1:uMN5MsHrVWmtRoauefI8wD86b8vbXYnrCZlIhFGyuXI= +github.com/bool64/dev v0.1.18/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU= github.com/bool64/shared v0.1.0/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU= -github.com/bool64/shared v0.1.1 h1:oeJC8LWpaCO9K3dvvVpyoxs9YXkOiGF8onBKUSNdJ2I= github.com/bool64/shared v0.1.1/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU= +github.com/bool64/shared v0.1.2 h1:3W41/TM41O4dMZRZbKErzMcSZQuDG4UnkoWZWaQnTTo= +github.com/bool64/shared v0.1.2/go.mod h1:Yt5dVKw6njpWoIN3rRV9mgkZO4CExiQUw9Q8n/Vovmo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -21,8 +24,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w= -github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= +github.com/go-chi/chi v1.5.2 h1:YcLIBANL4OTaAOcTdp//sskGa0yGACQMCtbnr7YEn0Q= +github.com/go-chi/chi v1.5.2/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -101,16 +104,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/swaggest/assertjson v1.0.0/go.mod h1:mE5ltBbrB+Ya8Xar5OMITxya76vwLZMoPG8aXF7sVQc= github.com/swaggest/assertjson v1.4.1/go.mod h1:v7atNMMlIa5ry2H1rgCHhvz7eho8yfP85UGio0+TFEI= github.com/swaggest/assertjson v1.6.0/go.mod h1:2dZU3TJoGcH+QPrekon0+zG5hJnZUpdEAxiLoxmJALI= -github.com/swaggest/assertjson v1.6.2 h1:4v4ueAy466hisGIeQ2WmFDyOJiE0YrCDDRaSXor642I= -github.com/swaggest/assertjson v1.6.2/go.mod h1:7/CWJAKS2t0KXIGsh5no8qyRXluwQ4fVduV3+Jue/MY= +github.com/swaggest/assertjson v1.6.3 h1:KvD1Y9nb7b6x6swrNdgXks0Z/h8fyWZetw0zhGFnD8g= +github.com/swaggest/assertjson v1.6.3/go.mod h1:fShyZuy4F57C1smqdYNVYDyehECSJLmvYiCGwWyQEKo= github.com/swaggest/form v1.11.1 h1:dNRMhld2VkVA6J9sjKaWSoabEP92G5NNlLs9wW+zN/Q= github.com/swaggest/form v1.11.1/go.mod h1:XI/xmw62iJuKxhbqvxOj1ri7VJFAaeRsX5qNdHVkbB4= github.com/swaggest/jsonschema-go v0.2.1/go.mod h1:QFauBdPTrU1UltwocM5FzOWnVjVVtcWkJWG3NlK9sV0= github.com/swaggest/jsonschema-go v0.2.4/go.mod h1:m4VV88Gbi7lCrt9ckJzigK1rMlEeFjdZUkJr1o5MnDE= github.com/swaggest/jsonschema-go v0.3.7/go.mod h1:TrWgbug4p2ZgcxnHDz+CvYvEtJ5KckL/XOV4mSR6FGw= github.com/swaggest/jsonschema-go v0.3.13/go.mod h1:INO3Dt0DeyNCbQbqMXNpMQ489BcyRtSb/k62OfmHbpY= -github.com/swaggest/jsonschema-go v0.3.14 h1:HmXY9SbMtwedXM6JOlRcAOxtJ0wH2WURWlnow40CAoo= -github.com/swaggest/jsonschema-go v0.3.14/go.mod h1:ohKskUhEMf/bhSi2zY0A5aLZoMz7ZkEafEvniNKBaQg= +github.com/swaggest/jsonschema-go v0.3.15 h1:6D23R4rhU2fVELiMyXtssVxlS/8eq6krRzemv07VvK8= +github.com/swaggest/jsonschema-go v0.3.15/go.mod h1:1JyQxHbtZcK3WqGqRybu022vrDdI02j9Xu9sK/Z58Tg= github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM= github.com/swaggest/openapi-go v0.1.13/go.mod h1:fju3Ka5zb8qBKf4789zLNVUqITydWLDjtPloZhhYHL8= github.com/swaggest/openapi-go v0.2.9 h1:JJQrDSRtKMycIkLju97yjhJbw72mqhsCDJjqFB8HvSs= @@ -118,14 +121,15 @@ github.com/swaggest/openapi-go v0.2.9/go.mod h1:tr1pt/xuYlmgk8HZDjfmx/L7zfKLCmxR github.com/swaggest/refl v0.1.0/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.2/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.3/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= -github.com/swaggest/refl v0.1.6 h1:z5drruoOSfWoB4aPuL22cMB5suLuxXS22oQkpTQ093o= github.com/swaggest/refl v0.1.6/go.mod h1:XqZZZ8i6hptbvd3A8pvXkF0ontg0Wziqe7OFAnvAZMk= +github.com/swaggest/refl v0.1.7 h1:pK2nWacMS6MIgeEdRVfmNUAxKih6vHIUF59osZrxpmY= +github.com/swaggest/refl v0.1.7/go.mod h1:acYd5x8NNxivp+ZHdRZKJYz66n/qjo3Q9Sa/jAivljQ= github.com/swaggest/swgen v0.6.20/go.mod h1:ipkZNfwztgRfbOWUllLZawfmxXprT8flqNJO9XhoMwM= github.com/swaggest/swgen v0.6.23/go.mod h1:gj2yCLONy3kosKjwRtQeT5O9qqlhUvXAiDnbVwBUNFM= github.com/swaggest/swgen v0.6.27 h1:bO3CtX4NvuetPuABMChEEp2N/bjsJraegaJpYZ72aoI= github.com/swaggest/swgen v0.6.27/go.mod h1:tQLsoQVLcvtAMzNbm26JbWXDHZM4BBRuI5nemk3gEDw= -github.com/swaggest/usecase v0.1.1 h1:z/jGjLBMoTMfkOvgLbqRAYBzoabNBitGhSzC7k2bZMk= -github.com/swaggest/usecase v0.1.1/go.mod h1:pgdcCw5G9H9S3J++CE2UZgMd0oxXvCuM1kCs1PP6Mgw= +github.com/swaggest/usecase v0.1.2 h1:Fi3z0nig4gBcX0n0nnEHD8QzCttdV3lAPmaoX3nQowA= +github.com/swaggest/usecase v0.1.2/go.mod h1:BKvpHx94bpg+4RCXbrCv0jx8CpJRr5P8dYK0b4UfUZc= github.com/vearutop/json5 v0.1.2 h1:AD+VTRiN8TZb4/ObzXXyYIY66AVNqdBn0O8ks/WqgH0= github.com/vearutop/json5 v0.1.2/go.mod h1:0EGfZGmwGu042tElaCn8+Song+vEMQOOgChj+mzD7OQ= github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= diff --git a/response/encoder.go b/response/encoder.go index 43cd73f..7d203dd 100644 --- a/response/encoder.go +++ b/response/encoder.go @@ -18,6 +18,8 @@ import ( // Encoder prepares and writes http response. type Encoder struct { + JSONWriter func(v interface{}) + outputBufferType reflect.Type outputHeadersEncoder *form.Encoder skipRendering bool diff --git a/resttest/client.go b/resttest/client.go index c8652da..b0c70b4 100644 --- a/resttest/client.go +++ b/resttest/client.go @@ -342,11 +342,7 @@ func (c *Client) assertResponseHeader(key, value string, resp *http.Response) er return err } - if err := c.JSONComparer.FailNotEqual(expected, received); err != nil { - return err - } - - return nil + return c.JSONComparer.FailNotEqual(expected, received) } // ExpectResponseBody sets expectation for response body to be received.