Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ContentTypeForm functionality to DecodeForm #4

Merged
merged 12 commits into from Apr 29, 2021
19 changes: 19 additions & 0 deletions .github/workflows/golangci-lint.yml
@@ -0,0 +1,19 @@
name: golangci-lint
on:
push:
tags:
- v*
branches:
- master
- main
pull_request:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.29
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
@@ -0,0 +1,24 @@
name: test
on:
push:
tags:
- v*
branches:
- master
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- run: go mod download
- name: Run tests
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
vendor
15 changes: 0 additions & 15 deletions .travis.yml

This file was deleted.

5 changes: 4 additions & 1 deletion README.md
@@ -1,5 +1,9 @@
# render

![tests](https://github.com/go-chi/render/actions/workflows/test.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-chi/render)](https://goreportcard.com/report/github.com/go-chi/render)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-chi/render.svg)](https://pkg.go.dev/github.com/go-chi/render)

The `render` package helps manage HTTP request / response payloads.

Every well-designed, robust and maintainable Web Service / REST API also needs
Expand All @@ -21,4 +25,3 @@ request bodies. Please have a look at the [rest](https://github.com/go-chi/chi/b
example which uses the latest chi/render sub-pkg.

All feedback is welcome, thank you!

19 changes: 16 additions & 3 deletions decoder.go
Expand Up @@ -7,6 +7,8 @@ import (
"io"
"io/ioutil"
"net/http"

"github.com/ajg/form"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we bring in github.com/ajg/form as golang/dep dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a more global question for you, for the project. Currently there's no dependency tooling, so I didn't add any.

)

// Decode is a package-level variable set to our default Decoder. We do this
Expand All @@ -17,6 +19,8 @@ import (
// bytes allowed to be read from the request body.
var Decode = DefaultDecoder

// DefaultDecoder detects the correct decoder for use on an HTTP request and
// marshals into a given interface.
func DefaultDecoder(r *http.Request, v interface{}) error {
var err error

Expand All @@ -25,20 +29,29 @@ func DefaultDecoder(r *http.Request, v interface{}) error {
err = DecodeJSON(r.Body, v)
case ContentTypeXML:
err = DecodeXML(r.Body, v)
// case ContentTypeForm: // TODO
case ContentTypeForm:
err = DecodeForm(r.Body, v)
default:
err = errors.New("render: unable to automatically decode the request content type")
}

return err
}

// DecodeJSON decodes a given reader into an interface using the json decoder.
func DecodeJSON(r io.Reader, v interface{}) error {
defer io.Copy(ioutil.Discard, r)
defer io.Copy(ioutil.Discard, r) //nolint:errcheck
return json.NewDecoder(r).Decode(v)
}

// DecodeXML decodes a given reader into an interface using the xml decoder.
func DecodeXML(r io.Reader, v interface{}) error {
defer io.Copy(ioutil.Discard, r)
defer io.Copy(ioutil.Discard, r) //nolint:errcheck
return xml.NewDecoder(r).Decode(v)
}

// DecodeForm decodes a given reader into an interface using the form decoder.
func DecodeForm(r io.Reader, v interface{}) error {
decoder := form.NewDecoder(r) //nolint:errcheck
return decoder.Decode(v)
}
5 changes: 5 additions & 0 deletions go.mod
@@ -0,0 +1,5 @@
module github.com/go-chi/render

go 1.16

require github.com/ajg/form v1.5.1
2 changes: 2 additions & 0 deletions go.sum
@@ -0,0 +1,2 @@
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
26 changes: 13 additions & 13 deletions responder.go
Expand Up @@ -66,7 +66,7 @@ func PlainText(w http.ResponseWriter, r *http.Request, v string) {
if status, ok := r.Context().Value(StatusCtxKey).(int); ok {
w.WriteHeader(status)
}
w.Write([]byte(v))
w.Write([]byte(v)) //nolint:errcheck
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: _ = w.Write() would be imho more idiomatic than using some special comments

}

// Data writes raw bytes to the response, setting the Content-Type as
Expand All @@ -76,7 +76,7 @@ func Data(w http.ResponseWriter, r *http.Request, v []byte) {
if status, ok := r.Context().Value(StatusCtxKey).(int); ok {
w.WriteHeader(status)
}
w.Write(v)
w.Write(v) //nolint:errcheck
}

// HTML writes a string to the response, setting the Content-Type as text/html.
Expand All @@ -85,7 +85,7 @@ func HTML(w http.ResponseWriter, r *http.Request, v string) {
if status, ok := r.Context().Value(StatusCtxKey).(int); ok {
w.WriteHeader(status)
}
w.Write([]byte(v))
w.Write([]byte(v)) //nolint:errcheck
}

// JSON marshals 'v' to JSON, automatically escaping HTML and setting the
Expand All @@ -103,7 +103,7 @@ func JSON(w http.ResponseWriter, r *http.Request, v interface{}) {
if status, ok := r.Context().Value(StatusCtxKey).(int); ok {
w.WriteHeader(status)
}
w.Write(buf.Bytes())
w.Write(buf.Bytes()) //nolint:errcheck
}

// XML marshals 'v' to JSON, setting the Content-Type as application/xml. It
Expand All @@ -128,15 +128,15 @@ func XML(w http.ResponseWriter, r *http.Request, v interface{}) {
}
if !bytes.Contains(b[:findHeaderUntil], []byte("<?xml")) {
// No header found. Print it out first.
w.Write([]byte(xml.Header))
w.Write([]byte(xml.Header)) //nolint:errcheck
}

w.Write(b)
w.Write(b) //nolint:errcheck
}

// NoContent returns a HTTP 204 "No Content" response.
func NoContent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
w.WriteHeader(http.StatusNoContent)
}

func channelEventStream(w http.ResponseWriter, r *http.Request, v interface{}) {
Expand All @@ -153,7 +153,7 @@ func channelEventStream(w http.ResponseWriter, r *http.Request, v interface{}) {
w.Header().Set("Connection", "keep-alive")
}

w.WriteHeader(200)
w.WriteHeader(http.StatusOK)

ctx := r.Context()
for {
Expand All @@ -162,12 +162,12 @@ func channelEventStream(w http.ResponseWriter, r *http.Request, v interface{}) {
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(v)},
}); chosen {
case 0: // equivalent to: case <-ctx.Done()
w.Write([]byte("event: error\ndata: {\"error\":\"Server Timeout\"}\n\n"))
w.Write([]byte("event: error\ndata: {\"error\":\"Server Timeout\"}\n\n")) //nolint:errcheck
return

default: // equivalent to: case v, ok := <-stream
if !ok {
w.Write([]byte("event: EOF\n\n"))
w.Write([]byte("event: EOF\n\n")) //nolint:errcheck
return
}
v := recv.Interface()
Expand All @@ -184,13 +184,13 @@ func channelEventStream(w http.ResponseWriter, r *http.Request, v interface{}) {

bytes, err := json.Marshal(v)
if err != nil {
w.Write([]byte(fmt.Sprintf("event: error\ndata: {\"error\":\"%v\"}\n\n", err)))
w.Write([]byte(fmt.Sprintf("event: error\ndata: {\"error\":\"%v\"}\n\n", err))) //nolint:errcheck
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
continue
}
w.Write([]byte(fmt.Sprintf("event: data\ndata: %s\n\n", bytes)))
w.Write([]byte(fmt.Sprintf("event: data\ndata: %s\n\n", bytes))) //nolint:errcheck
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
Expand All @@ -209,7 +209,7 @@ func channelIntoSlice(w http.ResponseWriter, r *http.Request, from interface{})
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(from)},
}); chosen {
case 0: // equivalent to: case <-ctx.Done()
http.Error(w, "Server Timeout", 504)
http.Error(w, "Server Timeout", http.StatusGatewayTimeout)
return nil

default: // equivalent to: case v, ok := <-stream
Expand Down