Skip to content

Commit

Permalink
Improve support for responses without body (304 et al.) (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored Mar 9, 2023
1 parent 3695394 commit 05afe69
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 24 deletions.
20 changes: 18 additions & 2 deletions _examples/advanced-generic/_testdata/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
"schema":{
"anyOf":[
{"$ref":"#/components/schemas/AdvancedCustomErr"},
{"$ref":"#/components/schemas/AdvancedAnotherErr"},
{"$ref":"#/components/schemas/AdvancedCustomErr"}
{"$ref":"#/components/schemas/AdvancedAnotherErr"}
]
}
}
Expand All @@ -60,6 +59,10 @@
"409":{
"description":"Conflict",
"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedCustomErr"}}}
},
"412":{
"description":"Precondition Failed",
"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedCustomErr"}}}
}
},
"x-forbid-unknown-query":true
Expand Down Expand Up @@ -379,17 +382,30 @@
"get":{
"tags":["Response"],"summary":"Output With Stream Writer","description":"Output with stream writer.",
"operationId":"_examples/advanced-generic.outputCSVWriter",
"parameters":[
{
"name":"If-None-Match","in":"header","description":"Content hash.",
"schema":{"type":"string","description":"Content hash."}
}
],
"responses":{
"200":{
"description":"OK",
"headers":{
"ETag":{
"style":"simple","description":"Content hash.","schema":{"type":"string","description":"Content hash."}
},
"X-Header":{
"style":"simple","description":"Sample response header.",
"schema":{"type":"string","description":"Sample response header."}
}
},
"content":{"text/csv":{"schema":{"type":"string"}}}
},
"304":{
"description":"Not Modified",
"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedCustomErr"}}}
},
"500":{
"description":"Internal Server Error",
"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedCustomErr"}}}
Expand Down
20 changes: 17 additions & 3 deletions _examples/advanced-generic/output_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,41 @@ package main
import (
"context"
"encoding/csv"
"net/http"

"github.com/swaggest/rest"
"github.com/swaggest/usecase"
"github.com/swaggest/usecase/status"
)

func outputCSVWriter() usecase.Interactor {
type writerOutput struct {
Header string `header:"X-Header" description:"Sample response header."`
Header string `header:"X-Header" description:"Sample response header."`
ContentHash string `header:"ETag" description:"Content hash."`
usecase.OutputWithEmbeddedWriter
}

u := usecase.NewInteractor(func(ctx context.Context, _ interface{}, out *writerOutput) (err error) {
type writerInput struct {
ContentHash string `header:"If-None-Match" description:"Content hash."`
}

u := usecase.NewInteractor(func(ctx context.Context, in writerInput, out *writerOutput) (err error) {
contentHash := "abc123" // Pretending this is an actual content hash.

if in.ContentHash == contentHash {
return rest.HTTPCodeAsError(http.StatusNotModified)
}

out.Header = "abc"
out.ContentHash = contentHash

c := csv.NewWriter(out)
return c.WriteAll([][]string{{"abc", "def", "hij"}, {"klm", "nop", "qrs"}})
})

u.SetTitle("Output With Stream Writer")
u.SetDescription("Output with stream writer.")
u.SetExpectedErrors(status.Internal)
u.SetExpectedErrors(status.Internal, rest.HTTPCodeAsError(http.StatusNotModified))
u.SetTags("Response")

return u
Expand Down
2 changes: 1 addition & 1 deletion _examples/advanced-generic/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func NewRouter() http.Handler {

return usecase.Interact(func(ctx context.Context, input, output interface{}) error {
err := next.Interact(ctx, input, output)
if err != nil {
if err != nil && err != rest.HTTPCodeAsError(http.StatusNotModified) {
log.Printf("usecase %s request (%v) failed: %v\n", name, input, err)
}

Expand Down
9 changes: 6 additions & 3 deletions _examples/advanced/_testdata/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@
"schema":{
"anyOf":[
{"$ref":"#/components/schemas/AdvancedCustomErr"},
{"$ref":"#/components/schemas/AdvancedAnotherErr"},
{"$ref":"#/components/schemas/AdvancedCustomErr"}
{"$ref":"#/components/schemas/AdvancedAnotherErr"}
]
}
}
Expand All @@ -61,6 +60,10 @@
"409":{
"description":"Conflict",
"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/AdvancedCustomErr"}}}
},
"412":{
"description":"Precondition Failed",
"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/AdvancedCustomErr"}}}
}
},
"x-forbid-unknown-query":true
Expand Down Expand Up @@ -611,4 +614,4 @@
},
"securitySchemes":{"User":{"type":"apiKey","name":"sessid","in":"cookie"}}
}
}
}
2 changes: 1 addition & 1 deletion _examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/santhosh-tekuri/jsonschema/v3 v3.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/swaggest/form/v5 v5.0.2 // indirect
github.com/swaggest/form/v5 v5.0.4 // indirect
github.com/swaggest/refl v1.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vearutop/dynhist-go v1.1.0 // indirect
Expand Down
5 changes: 2 additions & 3 deletions _examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ github.com/bool64/ctxd v1.2.1 h1:hARFteq0zdn4bwfmxLhak3fXFuvtJVKDH2X29VV/2ls=
github.com/bool64/ctxd v1.2.1/go.mod h1:ZG6QkeGVLTiUl2mxPpyHmFhDzFZCyocr9hluBV3LYuc=
github.com/bool64/dev v0.1.41/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
github.com/bool64/dev v0.2.22/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/dev v0.2.25 h1:p6euAfe1zLXb1qzLssm0lJnM5KhfUZp/Qjb2dsPkIKU=
github.com/bool64/dev v0.2.25/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/httpmock v0.1.8 h1:plNJylRdMGjNDV8kCuapKTJJS8p6zbvWbR8NG+Z6qAA=
Expand Down Expand Up @@ -60,8 +59,8 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw=
github.com/swaggest/assertjson v1.7.0/go.mod h1:vxMJMehbSVJd+dDWFCKv3QRZKNTpy/ktZKTz9LOEDng=
github.com/swaggest/form/v5 v5.0.2 h1:TiimP7UX3q1nSI5ZGg6tGMmxg8UJB0JNpaZjXWH6V2E=
github.com/swaggest/form/v5 v5.0.2/go.mod h1:Ayta1ggwSnDd4zwzv47jQL4RHk7WOTHp65nzwBc0IhU=
github.com/swaggest/form/v5 v5.0.4 h1:hTg+gjUEZG0vgsDdXOPi7I0Xu5OowXKoLZIGYZsoddg=
github.com/swaggest/form/v5 v5.0.4/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
github.com/swaggest/jsonschema-go v0.3.48 h1:zscQIIh2DlUaPTgCntPOq9s9a5QQTeWcs2QTE6P7nEY=
github.com/swaggest/jsonschema-go v0.3.48/go.mod h1:67EkOWKuBlpuvfZW1djhCD4ZSGNdXUjrTEWe16S5uEg=
github.com/swaggest/openapi-go v0.2.29 h1:irfkrid6DPGxncZUdGmDrXbSLqat1QOGwvd5M1OFFbY=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func Test_taskLifeSpan(t *testing.T) {
assert.NoError(t, rc.ExpectResponseStatus(http.StatusNoContent))
assert.NoError(t, rc.ExpectResponseBody(nil))

assert.NoError(t, rc.ExpectOtherResponsesStatus(http.StatusBadRequest))
assert.NoError(t, rc.ExpectOtherResponsesStatus(http.StatusPreconditionFailed))
assert.NoError(t, rc.ExpectOtherResponsesBody([]byte(`{"status":"FAILED_PRECONDITION",`+
`"error":"failed precondition: task is already closed"}`)))

Expand Down
15 changes: 14 additions & 1 deletion error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ import (
"github.com/swaggest/usecase/status"
)

// HTTPCodeAsError exposes HTTP status code as use case error that can be translated to response status.
type HTTPCodeAsError int

// Error return HTTP status text.
func (c HTTPCodeAsError) Error() string {
return http.StatusText(int(c))
}

// HTTPStatus returns HTTP status code.
func (c HTTPCodeAsError) HTTPStatus() int {
return int(c)
}

// ErrWithHTTPStatus exposes HTTP status code.
type ErrWithHTTPStatus interface {
error
Expand Down Expand Up @@ -127,7 +140,7 @@ func HTTPStatusFromCanonicalCode(c status.Code) int {
case status.ResourceExhausted:
return http.StatusTooManyRequests
case status.FailedPrecondition:
return http.StatusBadRequest
return http.StatusPreconditionFailed
case status.Aborted:
return http.StatusConflict
case status.OutOfRange:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/santhosh-tekuri/jsonschema/v3 v3.1.0
github.com/stretchr/testify v1.8.2
github.com/swaggest/assertjson v1.7.0
github.com/swaggest/form/v5 v5.0.2
github.com/swaggest/form/v5 v5.0.4
github.com/swaggest/jsonschema-go v0.3.48
github.com/swaggest/openapi-go v0.2.29
github.com/swaggest/refl v1.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw=
github.com/swaggest/assertjson v1.7.0/go.mod h1:vxMJMehbSVJd+dDWFCKv3QRZKNTpy/ktZKTz9LOEDng=
github.com/swaggest/form/v5 v5.0.2 h1:TiimP7UX3q1nSI5ZGg6tGMmxg8UJB0JNpaZjXWH6V2E=
github.com/swaggest/form/v5 v5.0.2/go.mod h1:Ayta1ggwSnDd4zwzv47jQL4RHk7WOTHp65nzwBc0IhU=
github.com/swaggest/form/v5 v5.0.4 h1:hTg+gjUEZG0vgsDdXOPi7I0Xu5OowXKoLZIGYZsoddg=
github.com/swaggest/form/v5 v5.0.4/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
github.com/swaggest/jsonschema-go v0.3.48 h1:zscQIIh2DlUaPTgCntPOq9s9a5QQTeWcs2QTE6P7nEY=
github.com/swaggest/jsonschema-go v0.3.48/go.mod h1:67EkOWKuBlpuvfZW1djhCD4ZSGNdXUjrTEWe16S5uEg=
github.com/swaggest/openapi-go v0.2.29 h1:irfkrid6DPGxncZUdGmDrXbSLqat1QOGwvd5M1OFFbY=
Expand Down
9 changes: 7 additions & 2 deletions openapi/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,7 @@ func TestCollector_Collect_CombineErrors(t *testing.T) {
"schema":{
"oneOf":[
{"$ref":"#/components/schemas/RestErrResponse"},
{"$ref":"#/components/schemas/OpenapiTestAnotherErr"},
{"$ref":"#/components/schemas/RestErrResponse"}
{"$ref":"#/components/schemas/OpenapiTestAnotherErr"}
]
}
}
Expand All @@ -294,6 +293,12 @@ func TestCollector_Collect_CombineErrors(t *testing.T) {
"content":{
"application/json":{"schema":{"$ref":"#/components/schemas/RestErrResponse"}}
}
},
"412":{
"description":"Precondition Failed",
"content":{
"application/json":{"schema":{"$ref":"#/components/schemas/RestErrResponse"}}
}
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions response/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,6 @@ func (h *Encoder) writeJSONResponse(

// WriteErrResponse encodes and writes error to response.
func (h *Encoder) WriteErrResponse(w http.ResponseWriter, r *http.Request, statusCode int, response interface{}) {
contentType := DefaultErrorResponseContentType

e := jsonEncoderPool.Get().(*jsonEncoder) //nolint:errcheck

e.buf.Reset()
Expand All @@ -292,8 +290,14 @@ func (h *Encoder) WriteErrResponse(w http.ResponseWriter, r *http.Request, statu
return
}

w.Header().Set("Content-Length", strconv.Itoa(e.buf.Len()))
w.Header().Set("Content-Type", contentType)
// Skip statuses that do not allow response body (1xx, 204, 304).
if !(statusCode < http.StatusOK || statusCode == http.StatusNoContent || statusCode == http.StatusNotModified) {
w.Header().Set("Content-Length", strconv.Itoa(e.buf.Len()))

contentType := DefaultErrorResponseContentType
w.Header().Set("Content-Type", contentType)
}

w.WriteHeader(statusCode)

if r.Method == http.MethodHead {
Expand Down

0 comments on commit 05afe69

Please sign in to comment.