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

openapi3filter: RegisterBodyDecoder for text/csv #734

Merged
merged 6 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/docs/openapi3filter.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err er
func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, ...) error
func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
func ValidateSecurityRequirements(ctx context.Context, input *RequestValidationInput, ...) error
func ZipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, ...) (interface{}, error)
type AuthenticationFunc func(context.Context, *AuthenticationInput) error
type AuthenticationInput struct{ ... }
type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) (interface{}, error)
Expand Down
127 changes: 127 additions & 0 deletions openapi3filter/csv_file_upload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package openapi3filter_test

import (
"bytes"
"context"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)

func TestValidateCsvFileUpload(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/test:
post:
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: string
responses:
'200':
description: Created
`

loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)

tests := []struct {
csvData string
wantErr bool
}{
{
`foo,bar`,
false,
},
{
`"foo","bar"`,
false,
},
{
`foo,bar
baz,qux`,
false,
},
{
`foo,bar
baz,qux,quux`,
true,
},
{
`"""`,
true,
},
}
for _, tt := range tests {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)

{ // Add file data
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="file"; filename="hello.csv"`)
h.Set("Content-Type", "text/csv")

fw, err := writer.CreatePart(h)
require.NoError(t, err)
_, err = io.Copy(fw, strings.NewReader(tt.csvData))

require.NoError(t, err)
}

writer.Close()

req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader(body.Bytes()))
require.NoError(t, err)

req.Header.Set("Content-Type", writer.FormDataContentType())

route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)

if err = openapi3filter.ValidateRequestBody(
context.Background(),
&openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
},
route.Operation.RequestBody.Value,
); err != nil {
if !tt.wantErr {
t.Errorf("got %v", err)
}
continue
}
if tt.wantErr {
t.Errorf("want err")
}
}
}
28 changes: 25 additions & 3 deletions openapi3filter/req_resp_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package openapi3filter
import (
"archive/zip"
"bytes"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -1013,8 +1014,9 @@ func init() {
RegisterBodyDecoder("application/x-www-form-urlencoded", urlencodedBodyDecoder)
RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder)
RegisterBodyDecoder("application/yaml", yamlBodyDecoder)
RegisterBodyDecoder("application/zip", ZipFileBodyDecoder)
RegisterBodyDecoder("application/zip", zipFileBodyDecoder)
RegisterBodyDecoder("multipart/form-data", multipartBodyDecoder)
RegisterBodyDecoder("text/csv", csvBodyDecoder)
RegisterBodyDecoder("text/plain", plainBodyDecoder)
}

Expand Down Expand Up @@ -1221,8 +1223,8 @@ func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema
return string(data), nil
}

// ZipFileBodyDecoder is a body decoder that decodes a zip file body to a string.
func ZipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
// zipFileBodyDecoder is a body decoder that decodes a zip file body to a string.
func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
buff := bytes.NewBuffer([]byte{})
size, err := io.Copy(buff, body)
if err != nil {
Expand Down Expand Up @@ -1271,3 +1273,23 @@ func ZipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Sch

return string(content), nil
}

// csvBodyDecoder is a body decoder that decodes a csv body to a string.
func csvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
r := csv.NewReader(body)

var content string
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

content += strings.Join(record, ",") + "\n"
}

return content, nil
}
4 changes: 2 additions & 2 deletions openapi3filter/req_resp_decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,7 @@ func TestRegisterAndUnregisterBodyDecoder(t *testing.T) {
}
return strings.Split(string(data), ","), nil
}
contentType := "text/csv"
contentType := "application/csv"
h := make(http.Header)
h.Set(headerCT, contentType)

Expand All @@ -1371,7 +1371,7 @@ func TestRegisterAndUnregisterBodyDecoder(t *testing.T) {
_, _, err = decodeBody(body, h, schema, encFn)
require.Equal(t, &ParseError{
Kind: KindUnsupportedFormat,
Reason: prefixUnsupportedCT + ` "text/csv"`,
Reason: prefixUnsupportedCT + ` "application/csv"`,
}, err)
}

Expand Down