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

aws/protocol/rest: V2 REST Encoder Implementation #449

Merged
merged 1 commit into from
Dec 31, 2019
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
79 changes: 79 additions & 0 deletions aws/protocol/rest/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package rest

import (
"net/http"
"net/url"
"strings"
)

// An Encoder provides encoding of REST URI path, query, and header components
// of an HTTP request. Can also encode a stream as the payload.
//
// Does not support SetFields.
type Encoder struct {
req *http.Request

path, rawPath, pathBuffer []byte

query url.Values
header http.Header
}

// NewEncoder creates a new encoder from the passed in request. All query and
// header values will be added on top of the request's existing values. Overwriting
// duplicate values.
func NewEncoder(req *http.Request) *Encoder {
e := &Encoder{
req: req,

path: []byte(req.URL.Path),
rawPath: []byte(req.URL.Path),
query: req.URL.Query(),
header: req.Header,
}

return e
}

// Encode returns a REST protocol encoder for encoding HTTP bindings
// Returns any error if one occurred during encoding.
func (e *Encoder) Encode() error {
e.req.URL.Path, e.req.URL.RawPath = string(e.path), string(e.rawPath)
e.req.URL.RawQuery = e.query.Encode()
e.req.Header = e.header

return nil
}
Comment on lines +40 to +46
Copy link
Contributor

Choose a reason for hiding this comment

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

How will this return an error, if it occurred during encoding?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok to return error in this case since, we may want to add validation in the future


// AddHeader returns a HeaderValue for appending to the given header name
func (e *Encoder) AddHeader(key string) HeaderValue {
return newHeaderValue(e.header, key, true)
}

// SetHeader returns a HeaderValue for setting the given header name
func (e *Encoder) SetHeader(key string) HeaderValue {
return newHeaderValue(e.header, key, false)
}

// Headers returns a Header used encoding headers with the given prefix
func (e *Encoder) Headers(prefix string) Headers {
return Headers{
header: e.header,
prefix: strings.TrimSpace(prefix),
}
}

// SetURI returns a URIValue used for setting the given path key
func (e *Encoder) SetURI(key string) URIValue {
return newURIValue(&e.path, &e.rawPath, &e.pathBuffer, key)
}

// SetQuery returns a QueryValue used for setting the given query key
func (e *Encoder) SetQuery(key string) QueryValue {
return newQueryValue(e.query, key, false)
}

// AddQuery returns a QueryValue used for appending the given query key
func (e *Encoder) AddQuery(key string) QueryValue {
return newQueryValue(e.query, key, true)
}
56 changes: 56 additions & 0 deletions aws/protocol/rest/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package rest

import (
"net/http"
"net/url"
"reflect"
"testing"
)

func TestEncoder(t *testing.T) {
actual := http.Request{
Header: http.Header{
"custom-user-header": {"someValue"},
},
URL: &url.URL{
Path: "/some/{pathKey}/path",
RawQuery: "someExistingKeys=foobar",
},
}

expected := http.Request{
Header: map[string][]string{
"custom-user-header": {"someValue"},
"x-amzn-header-foo": {"someValue"},
"x-amzn-meta-foo": {"someValue"},
},
URL: &url.URL{
Path: "/some/someValue/path",
RawPath: "/some/someValue/path",
RawQuery: "someExistingKeys=foobar&someKey=someValue&someKey=otherValue",
},
}

encoder := NewEncoder(&actual)

// Headers
encoder.AddHeader("x-amzn-header-foo").String("someValue")
encoder.Headers("x-amzn-meta-").AddHeader("foo").String("someValue")

// Query
encoder.SetQuery("someKey").String("someValue")
encoder.AddQuery("someKey").String("otherValue")

// URI
if err := encoder.SetURI("pathKey").String("someValue"); err != nil {
t.Errorf("expected no err, but got %v", err)
}

if err := encoder.Encode(); err != nil {
t.Errorf("expected no err, but got %v", err)
}

if !reflect.DeepEqual(expected, actual) {
t.Errorf("expected %v, but got %v", expected, actual)
}
}
103 changes: 103 additions & 0 deletions aws/protocol/rest/header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package rest

import (
"encoding/base64"
"net/http"
"strconv"
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/private/protocol"
)

// Headers is used to encode header keys using a provided prefix
type Headers struct {
header http.Header
prefix string
}

// AddHeader returns a HeaderValue used to append values to prefix+key
func (h Headers) AddHeader(key string) HeaderValue {
return h.newHeaderValue(key, true)
}

// SetHeader returns a HeaderValue used to set the value of prefix+key
func (h Headers) SetHeader(key string) HeaderValue {
return h.newHeaderValue(key, false)
}

func (h Headers) newHeaderValue(key string, append bool) HeaderValue {
return newHeaderValue(h.header, h.prefix+strings.TrimSpace(key), append)
}

// HeaderValue is used to encode values to an HTTP header
type HeaderValue struct {
header http.Header
key string
append bool
}

func newHeaderValue(header http.Header, key string, append bool) HeaderValue {
return HeaderValue{header: header, key: strings.TrimSpace(key), append: append}
}

func (h HeaderValue) modifyHeader(value string) {
lk := strings.ToLower(h.key)

val := h.header[lk]

if h.append {
val = append(val, value)
} else {
val = append(val[:0], value)
}

h.header[lk] = val
}

// String encodes the value v as the header string value
func (h HeaderValue) String(v string) {
h.modifyHeader(v)
}

// Integer encodes the value v as the header string value
func (h HeaderValue) Integer(v int64) {
h.modifyHeader(strconv.FormatInt(v, 10))
}

// Boolean encodes the value v as a header string value
func (h HeaderValue) Boolean(v bool) {
h.modifyHeader(strconv.FormatBool(v))
}

// Float encodes the value v as a header string value
func (h HeaderValue) Float(v float64) {
h.modifyHeader(strconv.FormatFloat(v, 'f', -1, 64))
}

// Time encodes the value v using the format name as a header string value
func (h HeaderValue) Time(t time.Time, format string) error {
value, err := protocol.FormatTime(format, t)
if err != nil {
return err
}
h.modifyHeader(value)
return nil
}

// ByteSlice encodes the value v as a base64 header string value
func (h HeaderValue) ByteSlice(v []byte) {
encodeToString := base64.StdEncoding.EncodeToString(v)
h.modifyHeader(encodeToString)
}

// JSONValue encodes the value v as a base64 header string value
func (h HeaderValue) JSONValue(v aws.JSONValue) error {
encodedValue, err := protocol.EncodeJSONValue(v, protocol.Base64Escape)
if err != nil {
return err
}
h.modifyHeader(encodedValue)
return nil
}