Skip to content

Commit

Permalink
Implement support for multiple HTTP success statuses (#155)
Browse files Browse the repository at this point in the history
* Implement support for multiple HTTP success statuses:

When `OutputWithHTTPStatus` is implemented on the output port, `HTTPStatus()` defines the success HTTP status of a given operation, and `ExpectedHTTPStatuses()` defines all expected HTTP statuses for API documentation purposes.

* Make linter happy
  • Loading branch information
haimgel authored Apr 26, 2023
1 parent 0e69b49 commit 558c9ad
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 10 deletions.
22 changes: 15 additions & 7 deletions openapi/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,25 @@ func (c *Collector) setupOutput(oc *openapi3.OperationContext, u usecase.Interac
noContent = true
}

if oc.HTTPStatus == 0 {
oc.HTTPStatus = status
}

if !noContent && oc.RespContentType == "" {
oc.RespContentType = c.DefaultSuccessResponseContentType
}

err := c.Reflector().SetupResponse(*oc)
if err != nil {
return err
if outputWithStatus, ok := oc.Output.(rest.OutputWithHTTPStatus); ok {
for _, status := range outputWithStatus.ExpectedHTTPStatuses() {
oc.HTTPStatus = status
if err := c.Reflector().SetupResponse(*oc); err != nil {
return err
}
}
} else {
if oc.HTTPStatus == 0 {
oc.HTTPStatus = status
}
err := c.Reflector().SetupResponse(*oc)
if err != nil {
return err
}
}

if oc.HTTPMethod == http.MethodHead {
Expand Down
76 changes: 76 additions & 0 deletions openapi/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,79 @@ func TestCollector_Collect_CombineErrors(t *testing.T) {
}
}`), collector.Reflector().SpecEns())
}

// Output that implements OutputWithHTTPStatus interface.
type outputWithHTTPStatuses struct {
Number int `json:"number"`
}

func (outputWithHTTPStatuses) HTTPStatus() int {
return http.StatusCreated
}

func (outputWithHTTPStatuses) ExpectedHTTPStatuses() []int {
return []int{http.StatusCreated, http.StatusOK}
}

func TestCollector_Collect_multipleHttpStatuses(t *testing.T) {
c := openapi.Collector{}
u := usecase.IOInteractor{}
u.SetTitle("Title")
u.SetName("name")
u.Input = new(struct{})
u.Output = new(outputWithHTTPStatuses)

require.NoError(t, c.Collect(http.MethodPost, "/foo", u, rest.HandlerTrait{
ReqValidator: &jsonschema.Validator{},
}))

assertjson.EqualMarshal(t, []byte(`{
"openapi": "3.0.3",
"info": {
"title": "",
"version": ""
},
"paths": {
"/foo": {
"post": {
"summary": "Title",
"operationId": "name",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenapiTestOutputWithHTTPStatuses"
}
}
}
},
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenapiTestOutputWithHTTPStatuses"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"OpenapiTestOutputWithHTTPStatuses": {
"type": "object",
"properties": {
"number": {
"type": "integer"
}
}
}
}
}
}`), c.Reflector().SpecEns())
}
6 changes: 6 additions & 0 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ type ETagged interface {
type JSONWriterTo interface {
JSONWriteTo(w io.Writer) (int, error)
}

// OutputWithHTTPStatus exposes HTTP status code(s) for output.
type OutputWithHTTPStatus interface {
HTTPStatus() int
ExpectedHTTPStatuses() []int
}
10 changes: 7 additions & 3 deletions response/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,14 @@ func (h *Encoder) SetupOutput(output interface{}, ht *rest.HandlerTrait) {
return
}

if h.skipRendering && !h.outputWithWriter {
ht.SuccessStatus = http.StatusNoContent
if outputWithStatus, ok := output.(rest.OutputWithHTTPStatus); ok {
ht.SuccessStatus = outputWithStatus.HTTPStatus()
} else {
ht.SuccessStatus = http.StatusOK
if h.skipRendering && !h.outputWithWriter {
ht.SuccessStatus = http.StatusNoContent
} else {
ht.SuccessStatus = http.StatusOK
}
}
}

Expand Down
27 changes: 27 additions & 0 deletions response/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,30 @@ func TestEncoder_SetupOutput_nonPtr(t *testing.T) {
assert.Equal(t, "32", w.Header().Get("Content-Length"))
assert.Equal(t, `{"items":["one","two","three"]}`+"\n", w.Body.String())
}

// Output that implements OutputWithHTTPStatus interface.
type outputWithHTTPStatuses struct {
Number int `json:"number"`
}

func (outputWithHTTPStatuses) HTTPStatus() int {
return http.StatusCreated
}

func (outputWithHTTPStatuses) ExpectedHTTPStatuses() []int {
return []int{http.StatusCreated, http.StatusOK}
}

func TestEncoder_SetupOutput_httpStatus(t *testing.T) {
e := response.Encoder{}
ht := rest.HandlerTrait{}
e.SetupOutput(outputWithHTTPStatuses{}, &ht)

r, err := http.NewRequest(http.MethodPost, "/", nil)
require.NoError(t, err)

w := httptest.NewRecorder()
output := e.MakeOutput(w, ht)
e.WriteSuccessfulResponse(w, r, output, ht)
assert.Equal(t, http.StatusCreated, w.Code)
}

0 comments on commit 558c9ad

Please sign in to comment.