Skip to content

Commit

Permalink
REST Encoder Implementation V2
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail committed Dec 4, 2019
1 parent dadd7ec commit ec21ba8
Show file tree
Hide file tree
Showing 10 changed files with 873 additions and 3 deletions.
6 changes: 3 additions & 3 deletions private/protocol/path_replace.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ func (r *PathReplace) Encode() (path string, rawPath string) {

// ReplaceElement replaces a single element in the path string.
func (r *PathReplace) ReplaceElement(key, val string) (err error) {
r.path, r.fieldBuf, err = replacePathElement(r.path, r.fieldBuf, key, val, false)
r.rawPath, r.fieldBuf, err = replacePathElement(r.rawPath, r.fieldBuf, key, val, true)
r.path, r.fieldBuf, err = ReplacePathElement(r.path, r.fieldBuf, key, val, false)
r.rawPath, r.fieldBuf, err = ReplacePathElement(r.rawPath, r.fieldBuf, key, val, true)
return err
}

func replacePathElement(path, fieldBuf []byte, key, val string, escape bool) ([]byte, []byte, error) {
func ReplacePathElement(path, fieldBuf []byte, key, val string, escape bool) ([]byte, []byte, error) {
fieldBuf = bufCap(fieldBuf, len(key)+3) // { <key> [+] }
fieldBuf = append(fieldBuf, uriTokenStart)
fieldBuf = append(fieldBuf, key...)
Expand Down
75 changes: 75 additions & 0 deletions private/protocol/rest/v2/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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 will return the request and body if one was set. If the body
// payload was not set the io.ReadSeeker will be nil.
//
// returns any error if one occured while encoding the API's parameters.
func (e *Encoder) Encode() *http.Request {
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 e.req
}

func (e *Encoder) AddHeader(key string) *HeaderValue {
return newHeaderValue(e.header, key, true)
}

func (e *Encoder) SetHeader(key string) *HeaderValue {
return newHeaderValue(e.header, key, false)
}

func (e *Encoder) Headers(prefix string) *Headers {
return &Headers{
header: e.header,
prefix: strings.TrimSpace(prefix),
}
}

func (e *Encoder) AddURI(key string) *URIValue {
return newURIValue(&e.path, &e.rawPath, &e.pathBuffer, key)
}

func (e *Encoder) SetQuery(key string) *QueryValue {
return newQueryValue(e.query, key, false)
}

func (e *Encoder) AddQuery(key string) *QueryValue {
return newQueryValue(e.query, key, true)
}
54 changes: 54 additions & 0 deletions private/protocol/rest/v2/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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.AddURI("pathKey").String("someValue"); err != nil {
t.Errorf("expected no err, but got %v", err)
}

actual = *encoder.Encode()

if !reflect.DeepEqual(expected, actual) {
t.Errorf("expected %v, but got %v", expected, actual)
}
}
86 changes: 86 additions & 0 deletions private/protocol/rest/v2/header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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"
)

type Headers struct {
header http.Header
prefix string
}

func (h *Headers) AddHeader(key string) *HeaderValue {
return h.newHeaderValue(key, true)
}

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

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) {
if h.append {
h.header.Add(h.key, value)
} else {
h.header.Set(h.key, value)
}
}

func (h *HeaderValue) String(v string) {
h.modifyHeader(v)
}

func (h *HeaderValue) Integer(v int64) {
h.modifyHeader(strconv.FormatInt(v, 10))
}

func (h *HeaderValue) Boolean(v bool) {
h.modifyHeader(strconv.FormatBool(v))
}

func (h *HeaderValue) Float(v float64) {
h.modifyHeader(strconv.FormatFloat(v, 'f', -1, 64))
}

func (h *HeaderValue) Time(t time.Time, format string) (err error) {
value, err := protocol.FormatTime(format, t)
if err != nil {
return err
}
h.modifyHeader(value)
return nil
}

func (h *HeaderValue) ByteSlice(v []byte) {
encodeToString := base64.StdEncoding.EncodeToString(v)
h.modifyHeader(encodeToString)
}

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 ec21ba8

Please sign in to comment.