Skip to content

Commit

Permalink
Merge pull request #51 from danielgtaylor/raw-body
Browse files Browse the repository at this point in the history
feat: add RawBody support and UnsupportedMediaType response
  • Loading branch information
danielgtaylor authored May 26, 2022
2 parents 34009d6 + 0a73c64 commit 030f8b9
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 1 deletion.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ The following types are supported out of the box:

For example, if the parameter is a query param and the type is `[]string` it might look like `?tags=tag1,tag2` in the URI.

The special struct field `Body` will be treated as the input request body and can refer to another struct or you can embed a struct inline.
The special struct field `Body` will be treated as the input request body and can refer to another struct or you can embed a struct inline. `RawBody` can also be used to provide access to the `[]byte` used to validate & load `Body`.

Here is an example:

Expand Down Expand Up @@ -472,6 +472,18 @@ op.NoBodyReadTimeout()
op.Run(...)
```

If you just need access to the input body bytes and still want to use the built-in JSON Schema validation, then you can instead use the `RawBody` input struct field.

```go
type MyBody struct {
// This will generate JSON Schema, validate the input, and parse it.
Body MyStruct

// This will contain the raw bytes used to load the above.
RawBody []byte
}
```

### Resolvers

Sometimes the built-in validation isn't sufficient for your use-case, or you want to do something more complex with the incoming request object. This is where resolvers come in.
Expand Down
6 changes: 6 additions & 0 deletions resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,12 @@ func setFields(ctx *hcontext, req *http.Request, input reflect.Value, t reflect.
Value: string(data),
})
}

// If requested, also provide access to the raw body bytes.
if _, ok := t.FieldByName("RawBody"); ok {
input.FieldByName("RawBody").Set(reflect.ValueOf(data))
}

continue
}

Expand Down
33 changes: 33 additions & 0 deletions resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,36 @@ func TestStringQueryEmpty(t *testing.T) {
assert.Equal(t, o.BooleanParam, true)
assert.Equal(t, o.OtherParam, "")
}

func TestRawBody(t *testing.T) {
app := newTestRouter()

app.Resource("/").Get("test", "Test",
NewResponse(http.StatusOK, "desc"),
).Run(func(ctx Context, input struct {
Body struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}
RawBody []byte
}) {
ctx.Write(input.RawBody)
})

// Note the weird formatting
body := `{ "name" : "Huma","tags": [ "one" ,"two"]}`

w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodGet, "/", strings.NewReader(body))
app.ServeHTTP(w, r)

assert.Equal(t, http.StatusOK, w.Result().StatusCode)
assert.Equal(t, body, w.Body.String())

// Invalid input should still fail validation!
w = httptest.NewRecorder()
r, _ = http.NewRequest(http.MethodGet, "/", strings.NewReader("{}"))
app.ServeHTTP(w, r)

assert.Equal(t, http.StatusUnprocessableEntity, w.Result().StatusCode)
}
5 changes: 5 additions & 0 deletions responses/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ func RequestEntityTooLarge() huma.Response {
return errorResponse(http.StatusRequestEntityTooLarge)
}

// UnsupportedMediaType HTTP 415 response with a structured error body (e.g. JSON).
func UnsupportedMediaType() huma.Response {
return errorResponse(http.StatusUnsupportedMediaType)
}

// UnprocessableEntity HTTP 422 response with a structured error body (e.g. JSON).
func UnprocessableEntity() huma.Response {
return errorResponse(http.StatusUnprocessableEntity)
Expand Down
2 changes: 2 additions & 0 deletions responses/responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var funcs = struct {
RequestTimeout,
Conflict,
PreconditionFailed,
UnsupportedMediaType,
RequestEntityTooLarge,
UnprocessableEntity,
PreconditionRequired,
Expand Down Expand Up @@ -72,6 +73,7 @@ func TestResponses(t *testing.T) {
http.StatusConflict,
http.StatusPreconditionFailed,
http.StatusRequestEntityTooLarge,
http.StatusUnsupportedMediaType,
http.StatusUnprocessableEntity,
http.StatusPreconditionRequired,
http.StatusInternalServerError,
Expand Down

0 comments on commit 030f8b9

Please sign in to comment.