...

Source file src/github.com/go-openapi/runtime/middleware/request_test.go

Documentation: github.com/go-openapi/runtime/middleware

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package middleware
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"io"
    21  	"mime/multipart"
    22  	"net/http"
    23  	"net/url"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/go-openapi/runtime"
    29  	"github.com/go-openapi/spec"
    30  	"github.com/go-openapi/strfmt"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  const (
    36  	csvFormat = "csv"
    37  	testURL   = "http://localhost:8002/hello"
    38  )
    39  
    40  type stubConsumer struct {
    41  }
    42  
    43  func (s *stubConsumer) Consume(_ io.Reader, _ interface{}) error {
    44  	return nil
    45  }
    46  
    47  type friend struct {
    48  	Name string `json:"name"`
    49  	Age  int    `json:"age"`
    50  }
    51  
    52  type jsonRequestParams struct {
    53  	ID        int64    // path
    54  	Name      string   // query
    55  	Friend    friend   // body
    56  	RequestID int64    // header
    57  	Tags      []string // csv
    58  }
    59  
    60  type jsonRequestPtr struct {
    61  	ID        int64    // path
    62  	Name      string   // query
    63  	RequestID int64    // header
    64  	Tags      []string // csv
    65  	Friend    *friend
    66  }
    67  
    68  type jsonRequestSlice struct {
    69  	ID        int64    // path
    70  	Name      string   // query
    71  	RequestID int64    // header
    72  	Tags      []string // csv
    73  	Friend    []friend
    74  }
    75  
    76  func parametersForAllTypes(fmt string) map[string]spec.Parameter {
    77  	if fmt == "" {
    78  		fmt = csvFormat
    79  	}
    80  	nameParam := spec.QueryParam("name").Typed(typeString, "")
    81  	idParam := spec.PathParam("id").Typed("integer", "int64")
    82  	ageParam := spec.QueryParam("age").Typed("integer", "int32")
    83  	scoreParam := spec.QueryParam("score").Typed("number", "float")
    84  	factorParam := spec.QueryParam("factor").Typed("number", "double")
    85  
    86  	friendSchema := new(spec.Schema).Typed("object", "")
    87  	friendParam := spec.BodyParam("friend", friendSchema)
    88  
    89  	requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
    90  	requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
    91  	requestIDParam.Extensions.Add("go-name", "RequestID")
    92  
    93  	items := new(spec.Items)
    94  	items.Type = typeString
    95  	tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
    96  
    97  	confirmedParam := spec.QueryParam("confirmed").Typed("boolean", "")
    98  	plannedParam := spec.QueryParam("planned").Typed(typeString, "date")
    99  	deliveredParam := spec.QueryParam("delivered").Typed(typeString, "date-time")
   100  	pictureParam := spec.QueryParam("picture").Typed(typeString, "byte") // base64 encoded during transport
   101  
   102  	return map[string]spec.Parameter{
   103  		"ID":        *idParam,
   104  		"Name":      *nameParam,
   105  		"RequestID": *requestIDParam,
   106  		"Friend":    *friendParam,
   107  		"Tags":      *tagsParam,
   108  		"Age":       *ageParam,
   109  		"Score":     *scoreParam,
   110  		"Factor":    *factorParam,
   111  		"Confirmed": *confirmedParam,
   112  		"Planned":   *plannedParam,
   113  		"Delivered": *deliveredParam,
   114  		"Picture":   *pictureParam,
   115  	}
   116  }
   117  
   118  func parametersForJSONRequestParams(fmt string) map[string]spec.Parameter {
   119  	if fmt == "" {
   120  		fmt = csvFormat
   121  	}
   122  	nameParam := spec.QueryParam("name").Typed(typeString, "")
   123  	idParam := spec.PathParam("id").Typed("integer", "int64")
   124  
   125  	friendSchema := new(spec.Schema).Typed("object", "")
   126  	friendParam := spec.BodyParam("friend", friendSchema)
   127  
   128  	requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
   129  	requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
   130  	requestIDParam.Extensions.Add("go-name", "RequestID")
   131  
   132  	items := new(spec.Items)
   133  	items.Type = typeString
   134  	tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
   135  
   136  	return map[string]spec.Parameter{
   137  		"ID":        *idParam,
   138  		"Name":      *nameParam,
   139  		"RequestID": *requestIDParam,
   140  		"Friend":    *friendParam,
   141  		"Tags":      *tagsParam,
   142  	}
   143  }
   144  func parametersForJSONRequestSliceParams(fmt string) map[string]spec.Parameter {
   145  	if fmt == "" {
   146  		fmt = csvFormat
   147  	}
   148  	nameParam := spec.QueryParam("name").Typed(typeString, "")
   149  	idParam := spec.PathParam("id").Typed("integer", "int64")
   150  
   151  	friendSchema := new(spec.Schema).Typed("object", "")
   152  	friendParam := spec.BodyParam("friend", spec.ArrayProperty(friendSchema))
   153  
   154  	requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
   155  	requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
   156  	requestIDParam.Extensions.Add("go-name", "RequestID")
   157  
   158  	items := new(spec.Items)
   159  	items.Type = typeString
   160  	tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
   161  
   162  	return map[string]spec.Parameter{
   163  		"ID":        *idParam,
   164  		"Name":      *nameParam,
   165  		"RequestID": *requestIDParam,
   166  		"Friend":    *friendParam,
   167  		"Tags":      *tagsParam,
   168  	}
   169  }
   170  
   171  func TestRequestBindingDefaultValue(t *testing.T) {
   172  	confirmed := true
   173  	name := "thomas"
   174  	friend := map[string]interface{}{"name": "toby", "age": float64(32)}
   175  	id, age, score, factor := int64(7575), int32(348), float32(5.309), float64(37.403)
   176  	requestID := 19394858
   177  	tags := []string{"one", "two", "three"}
   178  	dt1 := time.Date(2014, 8, 9, 0, 0, 0, 0, time.UTC)
   179  	planned := strfmt.Date(dt1)
   180  	dt2 := time.Date(2014, 10, 12, 8, 5, 5, 0, time.UTC)
   181  	delivered := strfmt.DateTime(dt2)
   182  	uri, err := url.Parse(testURL)
   183  	require.NoError(t, err)
   184  	defaults := map[string]interface{}{
   185  		"id":           id,
   186  		"age":          age,
   187  		"score":        score,
   188  		"factor":       factor,
   189  		"name":         name,
   190  		"friend":       friend,
   191  		"X-Request-Id": requestID,
   192  		"tags":         tags,
   193  		"confirmed":    confirmed,
   194  		"planned":      planned,
   195  		"delivered":    delivered,
   196  		"picture":      []byte("hello"),
   197  	}
   198  	op2 := parametersForAllTypes("")
   199  	op3 := make(map[string]spec.Parameter)
   200  	for k, p := range op2 {
   201  		p.Default = defaults[p.Name]
   202  		op3[k] = p
   203  	}
   204  
   205  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, uri.String(), bytes.NewBuffer(nil))
   206  	require.NoError(t, err)
   207  	req.Header.Set("Content-Type", "application/json")
   208  	binder := NewUntypedRequestBinder(op3, new(spec.Swagger), strfmt.Default)
   209  
   210  	data := make(map[string]interface{})
   211  	err = binder.Bind(req, RouteParams(nil), runtime.JSONConsumer(), &data)
   212  	require.NoError(t, err)
   213  	assert.Equal(t, defaults["id"], data["id"])
   214  	assert.Equal(t, name, data["name"])
   215  	assert.Equal(t, friend, data["friend"])
   216  	assert.EqualValues(t, requestID, data["X-Request-Id"])
   217  	assert.Equal(t, tags, data["tags"])
   218  	assert.Equal(t, planned, data["planned"])
   219  	assert.Equal(t, delivered, data["delivered"])
   220  	assert.Equal(t, confirmed, data["confirmed"])
   221  	assert.Equal(t, age, data["age"])
   222  	assert.InDelta(t, factor, data["factor"], 1e-6)
   223  	assert.InDelta(t, score, data["score"], 1e-6)
   224  	assert.Equal(t, "hello", string(data["picture"].(strfmt.Base64)))
   225  }
   226  
   227  func TestRequestBindingForInvalid(t *testing.T) {
   228  	invalidParam := spec.QueryParam("some")
   229  
   230  	op1 := map[string]spec.Parameter{"Some": *invalidParam}
   231  
   232  	binder := NewUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
   233  	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://localhost:8002/hello?name=the-name", nil)
   234  	require.NoError(t, err)
   235  
   236  	err = binder.Bind(req, nil, new(stubConsumer), new(jsonRequestParams))
   237  	require.Error(t, err)
   238  
   239  	op2 := parametersForJSONRequestParams("")
   240  	binder = NewUntypedRequestBinder(op2, new(spec.Swagger), strfmt.Default)
   241  
   242  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, "http://localhost:8002/hello/1?name=the-name", bytes.NewBufferString(`{"name":"toby","age":32}`))
   243  	require.NoError(t, err)
   244  	req.Header.Set("Content-Type", "application(")
   245  	data := jsonRequestParams{}
   246  	err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
   247  	require.Error(t, err)
   248  
   249  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, "http://localhost:8002/hello/1?name=the-name", bytes.NewBufferString(`{]`))
   250  	require.NoError(t, err)
   251  	req.Header.Set("Content-Type", "application/json")
   252  	data = jsonRequestParams{}
   253  	err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
   254  	require.Error(t, err)
   255  
   256  	invalidMultiParam := spec.HeaderParam("tags").CollectionOf(new(spec.Items), "multi")
   257  	op3 := map[string]spec.Parameter{"Tags": *invalidMultiParam}
   258  	binder = NewUntypedRequestBinder(op3, new(spec.Swagger), strfmt.Default)
   259  
   260  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, "http://localhost:8002/hello/1?name=the-name", bytes.NewBufferString(`{}`))
   261  	require.NoError(t, err)
   262  	req.Header.Set("Content-Type", "application/json")
   263  	data = jsonRequestParams{}
   264  	err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
   265  	require.Error(t, err)
   266  
   267  	invalidMultiParam = spec.PathParam("").CollectionOf(new(spec.Items), "multi")
   268  
   269  	op4 := map[string]spec.Parameter{"Tags": *invalidMultiParam}
   270  	binder = NewUntypedRequestBinder(op4, new(spec.Swagger), strfmt.Default)
   271  
   272  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, "http://localhost:8002/hello/1?name=the-name", bytes.NewBufferString(`{}`))
   273  	require.NoError(t, err)
   274  	req.Header.Set("Content-Type", "application/json")
   275  	data = jsonRequestParams{}
   276  	err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
   277  	require.Error(t, err)
   278  
   279  	invalidInParam := spec.HeaderParam("tags").Typed(typeString, "")
   280  	invalidInParam.In = "invalid"
   281  	op5 := map[string]spec.Parameter{"Tags": *invalidInParam}
   282  	binder = NewUntypedRequestBinder(op5, new(spec.Swagger), strfmt.Default)
   283  
   284  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, "http://localhost:8002/hello/1?name=the-name", bytes.NewBufferString(`{}`))
   285  	require.NoError(t, err)
   286  	req.Header.Set("Content-Type", "application/json")
   287  	data = jsonRequestParams{}
   288  	err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
   289  	require.Error(t, err)
   290  }
   291  
   292  func TestRequestBindingForValid(t *testing.T) {
   293  	for _, fmt := range []string{csvFormat, "pipes", "tsv", "ssv", "multi"} {
   294  		op1 := parametersForJSONRequestParams(fmt)
   295  
   296  		binder := NewUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
   297  
   298  		lval := []string{"one", "two", "three"}
   299  		var queryString string
   300  		var skipEscape bool
   301  		switch fmt {
   302  		case "multi":
   303  			skipEscape = true
   304  			queryString = strings.Join(lval, "&tags=")
   305  		case "ssv":
   306  			queryString = strings.Join(lval, " ")
   307  		case "pipes":
   308  			queryString = strings.Join(lval, "|")
   309  		case "tsv":
   310  			queryString = strings.Join(lval, "\t")
   311  		default:
   312  			queryString = strings.Join(lval, ",")
   313  		}
   314  		if !skipEscape {
   315  			queryString = url.QueryEscape(queryString)
   316  		}
   317  
   318  		urlStr := "http://localhost:8002/hello/1?name=the-name&tags=" + queryString
   319  
   320  		req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, bytes.NewBufferString(`{"name":"toby","age":32}`))
   321  		require.NoError(t, err)
   322  		req.Header.Set("Content-Type", "application/json;charset=utf-8")
   323  		req.Header.Set("X-Request-Id", "1325959595")
   324  
   325  		data := jsonRequestParams{}
   326  		err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
   327  
   328  		expected := jsonRequestParams{
   329  			ID:        1,
   330  			Name:      "the-name",
   331  			Friend:    friend{"toby", 32},
   332  			RequestID: 1325959595,
   333  			Tags:      []string{"one", "two", "three"},
   334  		}
   335  		require.NoError(t, err)
   336  		assert.Equal(t, expected, data)
   337  	}
   338  
   339  	op1 := parametersForJSONRequestParams("")
   340  
   341  	binder := NewUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
   342  	urlStr := "http://localhost:8002/hello/1?name=the-name&tags=one,two,three"
   343  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, bytes.NewBufferString(`{"name":"toby","age":32}`))
   344  	require.NoError(t, err)
   345  	req.Header.Set("Content-Type", "application/json;charset=utf-8")
   346  	req.Header.Set("X-Request-Id", "1325959595")
   347  
   348  	data2 := jsonRequestPtr{}
   349  	err = binder.Bind(req, []RouteParam{{"id", "1"}}, runtime.JSONConsumer(), &data2)
   350  
   351  	expected2 := jsonRequestPtr{
   352  		Friend: &friend{"toby", 32},
   353  		Tags:   []string{"one", "two", "three"},
   354  	}
   355  	require.NoError(t, err)
   356  	if data2.Friend == nil {
   357  		t.Fatal("friend is nil")
   358  	}
   359  	assert.Equal(t, *expected2.Friend, *data2.Friend)
   360  	assert.Equal(t, expected2.Tags, data2.Tags)
   361  
   362  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, bytes.NewBufferString(`[{"name":"toby","age":32}]`))
   363  	require.NoError(t, err)
   364  	req.Header.Set("Content-Type", "application/json;charset=utf-8")
   365  	req.Header.Set("X-Request-Id", "1325959595")
   366  	op2 := parametersForJSONRequestSliceParams("")
   367  	binder = NewUntypedRequestBinder(op2, new(spec.Swagger), strfmt.Default)
   368  	data3 := jsonRequestSlice{}
   369  	err = binder.Bind(req, []RouteParam{{"id", "1"}}, runtime.JSONConsumer(), &data3)
   370  
   371  	expected3 := jsonRequestSlice{
   372  		Friend: []friend{{"toby", 32}},
   373  		Tags:   []string{"one", "two", "three"},
   374  	}
   375  	require.NoError(t, err)
   376  	assert.Equal(t, expected3.Friend, data3.Friend)
   377  	assert.Equal(t, expected3.Tags, data3.Tags)
   378  }
   379  
   380  type formRequest struct {
   381  	Name string
   382  	Age  int
   383  }
   384  
   385  func parametersForFormUpload() map[string]spec.Parameter {
   386  	nameParam := spec.FormDataParam("name").Typed(typeString, "")
   387  
   388  	ageParam := spec.FormDataParam("age").Typed("integer", "int32")
   389  
   390  	return map[string]spec.Parameter{"Name": *nameParam, "Age": *ageParam}
   391  }
   392  
   393  func TestFormUpload(t *testing.T) {
   394  	params := parametersForFormUpload()
   395  	binder := NewUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
   396  
   397  	urlStr := testURL
   398  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, bytes.NewBufferString(`name=the-name&age=32`))
   399  	require.NoError(t, err)
   400  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   401  
   402  	data := formRequest{}
   403  	res := binder.Bind(req, nil, runtime.JSONConsumer(), &data)
   404  	require.NoError(t, res)
   405  	assert.Equal(t, "the-name", data.Name)
   406  	assert.Equal(t, 32, data.Age)
   407  
   408  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, bytes.NewBufferString(`name=%3&age=32`))
   409  	require.NoError(t, err)
   410  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   411  
   412  	data = formRequest{}
   413  	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   414  }
   415  
   416  type fileRequest struct {
   417  	Name string       // body
   418  	File runtime.File // upload
   419  }
   420  
   421  func paramsForFileUpload() *UntypedRequestBinder {
   422  	nameParam := spec.FormDataParam("name").Typed(typeString, "")
   423  
   424  	fileParam := spec.FileParam("file").AsRequired()
   425  
   426  	params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
   427  	return NewUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
   428  }
   429  
   430  func TestBindingFileUpload(t *testing.T) {
   431  	binder := paramsForFileUpload()
   432  
   433  	body := bytes.NewBuffer(nil)
   434  	writer := multipart.NewWriter(body)
   435  	part, err := writer.CreateFormFile("file", "plain-jane.txt")
   436  	require.NoError(t, err)
   437  
   438  	_, err = part.Write([]byte("the file contents"))
   439  	require.NoError(t, err)
   440  	require.NoError(t, writer.WriteField("name", "the-name"))
   441  	require.NoError(t, writer.Close())
   442  
   443  	urlStr := testURL
   444  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   445  	require.NoError(t, err)
   446  	req.Header.Set("Content-Type", writer.FormDataContentType())
   447  
   448  	data := fileRequest{}
   449  	require.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   450  	assert.Equal(t, "the-name", data.Name)
   451  	assert.NotNil(t, data.File)
   452  	assert.NotNil(t, data.File.Header)
   453  	assert.Equal(t, "plain-jane.txt", data.File.Header.Filename)
   454  
   455  	bb, err := io.ReadAll(data.File.Data)
   456  	require.NoError(t, err)
   457  	assert.Equal(t, []byte("the file contents"), bb)
   458  
   459  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   460  	require.NoError(t, err)
   461  	req.Header.Set("Content-Type", "application/json")
   462  	data = fileRequest{}
   463  	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   464  
   465  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   466  	require.NoError(t, err)
   467  	req.Header.Set("Content-Type", "application(")
   468  	data = fileRequest{}
   469  	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   470  
   471  	body = bytes.NewBuffer(nil)
   472  	writer = multipart.NewWriter(body)
   473  	part, err = writer.CreateFormFile("bad-name", "plain-jane.txt")
   474  	require.NoError(t, err)
   475  
   476  	_, err = part.Write([]byte("the file contents"))
   477  	require.NoError(t, err)
   478  	require.NoError(t, writer.WriteField("name", "the-name"))
   479  	require.NoError(t, writer.Close())
   480  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   481  	require.NoError(t, err)
   482  	req.Header.Set("Content-Type", writer.FormDataContentType())
   483  
   484  	data = fileRequest{}
   485  	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   486  
   487  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   488  	require.NoError(t, err)
   489  	req.Header.Set("Content-Type", writer.FormDataContentType())
   490  	_, err = req.MultipartReader()
   491  	require.NoError(t, err)
   492  
   493  	data = fileRequest{}
   494  	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   495  
   496  	writer = multipart.NewWriter(body)
   497  	require.NoError(t, writer.WriteField("name", "the-name"))
   498  	require.NoError(t, writer.Close())
   499  
   500  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   501  	require.NoError(t, err)
   502  	req.Header.Set("Content-Type", writer.FormDataContentType())
   503  
   504  	data = fileRequest{}
   505  	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   506  }
   507  
   508  func paramsForOptionalFileUpload() *UntypedRequestBinder {
   509  	nameParam := spec.FormDataParam("name").Typed(typeString, "")
   510  	fileParam := spec.FileParam("file").AsOptional()
   511  
   512  	params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
   513  	return NewUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
   514  }
   515  
   516  func TestBindingOptionalFileUpload(t *testing.T) {
   517  	binder := paramsForOptionalFileUpload()
   518  
   519  	body := bytes.NewBuffer(nil)
   520  	writer := multipart.NewWriter(body)
   521  	require.NoError(t, writer.WriteField("name", "the-name"))
   522  	require.NoError(t, writer.Close())
   523  
   524  	urlStr := testURL
   525  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   526  	require.NoError(t, err)
   527  	req.Header.Set("Content-Type", writer.FormDataContentType())
   528  
   529  	data := fileRequest{}
   530  	require.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   531  	assert.Equal(t, "the-name", data.Name)
   532  	assert.Nil(t, data.File.Data)
   533  	assert.Nil(t, data.File.Header)
   534  
   535  	writer = multipart.NewWriter(body)
   536  	part, err := writer.CreateFormFile("file", "plain-jane.txt")
   537  	require.NoError(t, err)
   538  	_, err = part.Write([]byte("the file contents"))
   539  	require.NoError(t, err)
   540  	require.NoError(t, writer.WriteField("name", "the-name"))
   541  	require.NoError(t, writer.Close())
   542  
   543  	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, urlStr, body)
   544  	require.NoError(t, err)
   545  	req.Header.Set("Content-Type", writer.FormDataContentType())
   546  	require.NoError(t, writer.Close())
   547  
   548  	data = fileRequest{}
   549  	require.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
   550  	assert.Equal(t, "the-name", data.Name)
   551  	assert.NotNil(t, data.File)
   552  	assert.NotNil(t, data.File.Header)
   553  	assert.Equal(t, "plain-jane.txt", data.File.Header.Filename)
   554  
   555  	bb, err := io.ReadAll(data.File.Data)
   556  	require.NoError(t, err)
   557  	assert.Equal(t, []byte("the file contents"), bb)
   558  }
   559  

View as plain text