Skip to content

Commit b0cd054

Browse files
committed
aws/protocol/rest: V2 REST Encoder Implementation (#449)
Adds utility for encoding HTTP REST values. Will be used by SDK's generated marshalers.
1 parent e3c58d5 commit b0cd054

File tree

10 files changed

+915
-3
lines changed

10 files changed

+915
-3
lines changed

aws/protocol/rest/encode.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package rest
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"strings"
7+
)
8+
9+
// An Encoder provides encoding of REST URI path, query, and header components
10+
// of an HTTP request. Can also encode a stream as the payload.
11+
//
12+
// Does not support SetFields.
13+
type Encoder struct {
14+
req *http.Request
15+
16+
path, rawPath, pathBuffer []byte
17+
18+
query url.Values
19+
header http.Header
20+
}
21+
22+
// NewEncoder creates a new encoder from the passed in request. All query and
23+
// header values will be added on top of the request's existing values. Overwriting
24+
// duplicate values.
25+
func NewEncoder(req *http.Request) *Encoder {
26+
e := &Encoder{
27+
req: req,
28+
29+
path: []byte(req.URL.Path),
30+
rawPath: []byte(req.URL.Path),
31+
query: req.URL.Query(),
32+
header: req.Header,
33+
}
34+
35+
return e
36+
}
37+
38+
// Encode returns a REST protocol encoder for encoding HTTP bindings
39+
// Returns any error if one occurred during encoding.
40+
func (e *Encoder) Encode() error {
41+
e.req.URL.Path, e.req.URL.RawPath = string(e.path), string(e.rawPath)
42+
e.req.URL.RawQuery = e.query.Encode()
43+
e.req.Header = e.header
44+
45+
return nil
46+
}
47+
48+
// AddHeader returns a HeaderValue for appending to the given header name
49+
func (e *Encoder) AddHeader(key string) HeaderValue {
50+
return newHeaderValue(e.header, key, true)
51+
}
52+
53+
// SetHeader returns a HeaderValue for setting the given header name
54+
func (e *Encoder) SetHeader(key string) HeaderValue {
55+
return newHeaderValue(e.header, key, false)
56+
}
57+
58+
// Headers returns a Header used encoding headers with the given prefix
59+
func (e *Encoder) Headers(prefix string) Headers {
60+
return Headers{
61+
header: e.header,
62+
prefix: strings.TrimSpace(prefix),
63+
}
64+
}
65+
66+
// SetURI returns a URIValue used for setting the given path key
67+
func (e *Encoder) SetURI(key string) URIValue {
68+
return newURIValue(&e.path, &e.rawPath, &e.pathBuffer, key)
69+
}
70+
71+
// SetQuery returns a QueryValue used for setting the given query key
72+
func (e *Encoder) SetQuery(key string) QueryValue {
73+
return newQueryValue(e.query, key, false)
74+
}
75+
76+
// AddQuery returns a QueryValue used for appending the given query key
77+
func (e *Encoder) AddQuery(key string) QueryValue {
78+
return newQueryValue(e.query, key, true)
79+
}

aws/protocol/rest/encode_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package rest
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"reflect"
7+
"testing"
8+
)
9+
10+
func TestEncoder(t *testing.T) {
11+
actual := http.Request{
12+
Header: http.Header{
13+
"custom-user-header": {"someValue"},
14+
},
15+
URL: &url.URL{
16+
Path: "/some/{pathKey}/path",
17+
RawQuery: "someExistingKeys=foobar",
18+
},
19+
}
20+
21+
expected := http.Request{
22+
Header: map[string][]string{
23+
"custom-user-header": {"someValue"},
24+
"x-amzn-header-foo": {"someValue"},
25+
"x-amzn-meta-foo": {"someValue"},
26+
},
27+
URL: &url.URL{
28+
Path: "/some/someValue/path",
29+
RawPath: "/some/someValue/path",
30+
RawQuery: "someExistingKeys=foobar&someKey=someValue&someKey=otherValue",
31+
},
32+
}
33+
34+
encoder := NewEncoder(&actual)
35+
36+
// Headers
37+
encoder.AddHeader("x-amzn-header-foo").String("someValue")
38+
encoder.Headers("x-amzn-meta-").AddHeader("foo").String("someValue")
39+
40+
// Query
41+
encoder.SetQuery("someKey").String("someValue")
42+
encoder.AddQuery("someKey").String("otherValue")
43+
44+
// URI
45+
if err := encoder.SetURI("pathKey").String("someValue"); err != nil {
46+
t.Errorf("expected no err, but got %v", err)
47+
}
48+
49+
if err := encoder.Encode(); err != nil {
50+
t.Errorf("expected no err, but got %v", err)
51+
}
52+
53+
if !reflect.DeepEqual(expected, actual) {
54+
t.Errorf("expected %v, but got %v", expected, actual)
55+
}
56+
}

aws/protocol/rest/header.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package rest
2+
3+
import (
4+
"encoding/base64"
5+
"net/http"
6+
"strconv"
7+
"strings"
8+
"time"
9+
10+
"github.com/aws/aws-sdk-go-v2/aws"
11+
"github.com/aws/aws-sdk-go-v2/private/protocol"
12+
)
13+
14+
// Headers is used to encode header keys using a provided prefix
15+
type Headers struct {
16+
header http.Header
17+
prefix string
18+
}
19+
20+
// AddHeader returns a HeaderValue used to append values to prefix+key
21+
func (h Headers) AddHeader(key string) HeaderValue {
22+
return h.newHeaderValue(key, true)
23+
}
24+
25+
// SetHeader returns a HeaderValue used to set the value of prefix+key
26+
func (h Headers) SetHeader(key string) HeaderValue {
27+
return h.newHeaderValue(key, false)
28+
}
29+
30+
func (h Headers) newHeaderValue(key string, append bool) HeaderValue {
31+
return newHeaderValue(h.header, h.prefix+strings.TrimSpace(key), append)
32+
}
33+
34+
// HeaderValue is used to encode values to an HTTP header
35+
type HeaderValue struct {
36+
header http.Header
37+
key string
38+
append bool
39+
}
40+
41+
func newHeaderValue(header http.Header, key string, append bool) HeaderValue {
42+
return HeaderValue{header: header, key: strings.TrimSpace(key), append: append}
43+
}
44+
45+
func (h HeaderValue) modifyHeader(value string) {
46+
lk := strings.ToLower(h.key)
47+
48+
val := h.header[lk]
49+
50+
if h.append {
51+
val = append(val, value)
52+
} else {
53+
val = append(val[:0], value)
54+
}
55+
56+
h.header[lk] = val
57+
}
58+
59+
// String encodes the value v as the header string value
60+
func (h HeaderValue) String(v string) {
61+
h.modifyHeader(v)
62+
}
63+
64+
// Integer encodes the value v as the header string value
65+
func (h HeaderValue) Integer(v int64) {
66+
h.modifyHeader(strconv.FormatInt(v, 10))
67+
}
68+
69+
// Boolean encodes the value v as a header string value
70+
func (h HeaderValue) Boolean(v bool) {
71+
h.modifyHeader(strconv.FormatBool(v))
72+
}
73+
74+
// Float encodes the value v as a header string value
75+
func (h HeaderValue) Float(v float64) {
76+
h.modifyHeader(strconv.FormatFloat(v, 'f', -1, 64))
77+
}
78+
79+
// Time encodes the value v using the format name as a header string value
80+
func (h HeaderValue) Time(t time.Time, format string) error {
81+
value, err := protocol.FormatTime(format, t)
82+
if err != nil {
83+
return err
84+
}
85+
h.modifyHeader(value)
86+
return nil
87+
}
88+
89+
// ByteSlice encodes the value v as a base64 header string value
90+
func (h HeaderValue) ByteSlice(v []byte) {
91+
encodeToString := base64.StdEncoding.EncodeToString(v)
92+
h.modifyHeader(encodeToString)
93+
}
94+
95+
// JSONValue encodes the value v as a base64 header string value
96+
func (h HeaderValue) JSONValue(v aws.JSONValue) error {
97+
encodedValue, err := protocol.EncodeJSONValue(v, protocol.Base64Escape)
98+
if err != nil {
99+
return err
100+
}
101+
h.modifyHeader(encodedValue)
102+
return nil
103+
}

0 commit comments

Comments
 (0)