Skip to content

Commit

Permalink
aws/protocol/rest: V2 REST Encoder Implementation (#449)
Browse files Browse the repository at this point in the history
Adds utility for encoding HTTP REST values. Will be used by SDK's generated marshalers.
  • Loading branch information
skmcgrail authored and jasdel committed Dec 31, 2019
1 parent 8feaa80 commit d2262ca
Show file tree
Hide file tree
Showing 10 changed files with 915 additions and 3 deletions.
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
}

// 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
}

0 comments on commit d2262ca

Please sign in to comment.