diff options
author | vitalyisaev <vitalyisaev@ydb.tech> | 2023-12-12 21:55:07 +0300 |
---|---|---|
committer | vitalyisaev <vitalyisaev@ydb.tech> | 2023-12-12 22:25:10 +0300 |
commit | 4967f99474a4040ba150eb04995de06342252718 (patch) | |
tree | c9c118836513a8fab6e9fcfb25be5d404338bca7 /vendor/github.com/aws/smithy-go/encoding/httpbinding | |
parent | 2ce9cccb9b0bdd4cd7a3491dc5cbf8687cda51de (diff) | |
download | ydb-4967f99474a4040ba150eb04995de06342252718.tar.gz |
YQ Connector: prepare code base for S3 integration
1. Кодовая база Коннектора переписана с помощью Go дженериков так, чтобы добавление нового источника данных (в частности S3 + csv) максимально переиспользовало имеющийся код (чтобы сохранялась логика нарезания на блоки данных, учёт трафика и пр.)
2. API Connector расширено для работы с S3, но ещё пока не протестировано.
Diffstat (limited to 'vendor/github.com/aws/smithy-go/encoding/httpbinding')
13 files changed, 1578 insertions, 0 deletions
diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/encode.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/encode.go new file mode 100644 index 0000000000..543e7cf038 --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/encode.go @@ -0,0 +1,123 @@ +package httpbinding + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +const ( + contentLengthHeader = "Content-Length" + floatNaN = "NaN" + floatInfinity = "Infinity" + floatNegInfinity = "-Infinity" +) + +// 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 { + path, rawPath, pathBuffer []byte + + query url.Values + header http.Header +} + +// NewEncoder creates a new encoder from the passed in request. It assumes that +// raw path contains no valuable information at this point, so it passes in path +// as path and raw path for subsequent trans +func NewEncoder(path, query string, headers http.Header) (*Encoder, error) { + return NewEncoderWithRawPath(path, path, query, headers) +} + +// NewHTTPBindingEncoder 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 NewEncoderWithRawPath(path, rawPath, query string, headers http.Header) (*Encoder, error) { + parseQuery, err := url.ParseQuery(query) + if err != nil { + return nil, fmt.Errorf("failed to parse query string: %w", err) + } + + e := &Encoder{ + path: []byte(path), + rawPath: []byte(rawPath), + query: parseQuery, + header: headers.Clone(), + } + + return e, nil +} + +// Encode returns a REST protocol encoder for encoding HTTP bindings. +// +// Due net/http requiring `Content-Length` to be specified on the http.Request#ContentLength directly. Encode +// will look for whether the header is present, and if so will remove it and set the respective value on http.Request. +// +// Returns any error occurring during encoding. +func (e *Encoder) Encode(req *http.Request) (*http.Request, error) { + req.URL.Path, req.URL.RawPath = string(e.path), string(e.rawPath) + req.URL.RawQuery = e.query.Encode() + + // net/http ignores Content-Length header and requires it to be set on http.Request + if v := e.header.Get(contentLengthHeader); len(v) > 0 { + iv, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return nil, err + } + req.ContentLength = iv + e.header.Del(contentLengthHeader) + } + + req.Header = e.header + + return req, 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 for encoding headers with the given prefix +func (e *Encoder) Headers(prefix string) Headers { + return Headers{ + header: e.header, + prefix: strings.TrimSpace(prefix), + } +} + +// HasHeader returns if a header with the key specified exists with one or +// more value. +func (e Encoder) HasHeader(key string) bool { + return len(e.header[key]) != 0 +} + +// 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) +} + +// HasQuery returns if a query with the key specified exists with one or +// more values. +func (e *Encoder) HasQuery(key string) bool { + return len(e.query.Get(key)) != 0 +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/encode_test.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/encode_test.go new file mode 100644 index 0000000000..6d972ae508 --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/encode_test.go @@ -0,0 +1,146 @@ +package httpbinding + +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/{pathKeyOne}/{pathKeyTwo}", + 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, err := NewEncoder(actual.URL.Path, actual.URL.RawQuery, actual.Header) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // 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("pathKeyOne").String("someValue"); err != nil { + t.Errorf("expected no err, but got %v", err) + } + + // URI + if err := encoder.SetURI("pathKeyTwo").String("path"); err != nil { + t.Errorf("expected no err, but got %v", err) + } + + if actual, err = encoder.Encode(actual); 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) + } +} + +func TestEncoderHasHeader(t *testing.T) { + encoder, err := NewEncoder("/", "", http.Header{}) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if h := "i-dont-exist"; encoder.HasHeader(h) { + t.Errorf("expect %v not to be set", h) + } + + encoder.AddHeader("I-do-exist").String("some value") + + if h := "I-do-exist"; !encoder.HasHeader(h) { + t.Errorf("expect %v to be set", h) + } + +} + +func TestEncoderHasQuery(t *testing.T) { + encoder, err := NewEncoder("/", "", http.Header{}) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if q := "i-dont-exist"; encoder.HasQuery(q) { + t.Errorf("expect %v not to be set", q) + } + + encoder.AddQuery("I-do-exist").String("some value") + + if q := "I-do-exist"; !encoder.HasQuery(q) { + t.Errorf("expect %v to be set", q) + } + +} + +func TestEncodeContentLength(t *testing.T) { + cases := map[string]struct { + headerValue string + expected int64 + wantErr bool + }{ + "valid number": { + headerValue: "1024", + expected: 1024, + }, + "invalid number": { + headerValue: "1024.5", + wantErr: true, + }, + "not a number": { + headerValue: "NaN", + wantErr: true, + }, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + encoder, err := NewEncoder("/", "", http.Header{}) + if err != nil { + t.Fatalf("expect no error, got %v", err) + } + + encoder.SetHeader("Content-Length").String(tt.headerValue) + + req := &http.Request{URL: &url.URL{}} + req, err = encoder.Encode(req) + if (err != nil) != tt.wantErr { + t.Fatalf("unexpected error value wantErr=%v", tt.wantErr) + } else if tt.wantErr { + return + } + if e, a := tt.expected, req.ContentLength; e != a { + t.Errorf("expect %v, got %v", e, a) + } + if v := req.Header.Get("Content-Length"); len(v) > 0 { + t.Errorf("expect header not to be set") + } + }) + } +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/gotest/ya.make b/vendor/github.com/aws/smithy-go/encoding/httpbinding/gotest/ya.make new file mode 100644 index 0000000000..c8a86501ea --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/github.com/aws/smithy-go/encoding/httpbinding) + +LICENSE(Apache-2.0) + +END() diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/header.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/header.go new file mode 100644 index 0000000000..f9256e175f --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/header.go @@ -0,0 +1,122 @@ +package httpbinding + +import ( + "encoding/base64" + "math" + "math/big" + "net/http" + "strconv" + "strings" +) + +// 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) { + if h.append { + h.header[h.key] = append(h.header[h.key], value) + } else { + h.header[h.key] = append(h.header[h.key][:0], value) + } +} + +// String encodes the value v as the header string value +func (h HeaderValue) String(v string) { + h.modifyHeader(v) +} + +// Byte encodes the value v as a query string value +func (h HeaderValue) Byte(v int8) { + h.Long(int64(v)) +} + +// Short encodes the value v as a query string value +func (h HeaderValue) Short(v int16) { + h.Long(int64(v)) +} + +// Integer encodes the value v as the header string value +func (h HeaderValue) Integer(v int32) { + h.Long(int64(v)) +} + +// Long encodes the value v as the header string value +func (h HeaderValue) Long(v int64) { + h.modifyHeader(strconv.FormatInt(v, 10)) +} + +// Boolean encodes the value v as a query string value +func (h HeaderValue) Boolean(v bool) { + h.modifyHeader(strconv.FormatBool(v)) +} + +// Float encodes the value v as a query string value +func (h HeaderValue) Float(v float32) { + h.float(float64(v), 32) +} + +// Double encodes the value v as a query string value +func (h HeaderValue) Double(v float64) { + h.float(v, 64) +} + +func (h HeaderValue) float(v float64, bitSize int) { + switch { + case math.IsNaN(v): + h.String(floatNaN) + case math.IsInf(v, 1): + h.String(floatInfinity) + case math.IsInf(v, -1): + h.String(floatNegInfinity) + default: + h.modifyHeader(strconv.FormatFloat(v, 'f', -1, bitSize)) + } +} + +// BigInteger encodes the value v as a query string value +func (h HeaderValue) BigInteger(v *big.Int) { + h.modifyHeader(v.String()) +} + +// BigDecimal encodes the value v as a query string value +func (h HeaderValue) BigDecimal(v *big.Float) { + if i, accuracy := v.Int64(); accuracy == big.Exact { + h.Long(i) + return + } + h.modifyHeader(v.Text('e', -1)) +} + +// Blob encodes the value v as a base64 header string value +func (h HeaderValue) Blob(v []byte) { + encodeToString := base64.StdEncoding.EncodeToString(v) + h.modifyHeader(encodeToString) +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/header_test.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/header_test.go new file mode 100644 index 0000000000..64e555c32b --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/header_test.go @@ -0,0 +1,295 @@ +package httpbinding + +import ( + "fmt" + "math/big" + "net/http" + "reflect" + "testing" +) + +func TestHeaderValue(t *testing.T) { + const keyName = "test-key" + const expectedKeyName = "test-key" + + cases := map[string]struct { + header http.Header + args []interface{} + append bool + expected http.Header + }{ + "set blob": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{[]byte("baz")}, + expected: map[string][]string{ + expectedKeyName: {"YmF6"}, + }, + }, + "set boolean": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{true}, + expected: map[string][]string{ + expectedKeyName: {"true"}, + }, + }, + "set string": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{"string value"}, + expected: map[string][]string{ + expectedKeyName: {"string value"}, + }, + }, + "set byte": { + header: http.Header{expectedKeyName: []string{"127"}}, + args: []interface{}{int8(127)}, + expected: map[string][]string{ + expectedKeyName: {"127"}, + }, + }, + "set short": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{int16(32767)}, + expected: map[string][]string{ + expectedKeyName: {"32767"}, + }, + }, + "set integer": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{int32(2147483647)}, + expected: map[string][]string{ + expectedKeyName: {"2147483647"}, + }, + }, + "set long": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{int64(9223372036854775807)}, + expected: map[string][]string{ + expectedKeyName: {"9223372036854775807"}, + }, + }, + "set float": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{float32(3.14159)}, + expected: map[string][]string{ + expectedKeyName: {"3.14159"}, + }, + }, + "set double": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{float64(3.14159)}, + expected: map[string][]string{ + expectedKeyName: {"3.14159"}, + }, + }, + "set bigInteger": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{new(big.Int).SetInt64(42)}, + expected: map[string][]string{ + expectedKeyName: {"42"}, + }, + }, + "set bigDecimal": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, + expected: map[string][]string{ + expectedKeyName: {"1.02410241024e+03"}, + }, + }, + "add blob": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{[]byte("baz")}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "YmF6"}, + }, + }, + "add bool": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{true}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "true"}, + }, + }, + "add string": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{"string value"}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "string value"}, + }, + }, + "add byte": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{int8(127)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "127"}, + }, + }, + "add short": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{int16(32767)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "32767"}, + }, + }, + "add integer": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{int32(2147483647)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "2147483647"}, + }, + }, + "add long": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{int64(9223372036854775807)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "9223372036854775807"}, + }, + }, + "add float": { + header: http.Header{expectedKeyName: []string{"1.61803"}}, + args: []interface{}{float32(3.14159)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"1.61803", "3.14159"}, + }, + }, + "add double": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{float64(3.14159)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "3.14159"}, + }, + }, + "add bigInteger": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{new(big.Int).SetInt64(42)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "42"}, + }, + }, + "add bigDecimal": { + header: http.Header{expectedKeyName: []string{"foobar"}}, + args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, + append: true, + expected: map[string][]string{ + expectedKeyName: {"foobar", "1.02410241024e+03"}, + }, + }, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + if tt.header == nil { + tt.header = http.Header{} + } + + hv := newHeaderValue(tt.header, keyName, tt.append) + + if err := setHeader(hv, tt.args); err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if e, a := tt.expected, hv.header; !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, got %v", e, a) + } + }) + } +} + +func TestHeaders(t *testing.T) { + const prefix = "X-Amzn-Meta-" + cases := map[string]struct { + headers http.Header + values map[string]string + append bool + expected http.Header + }{ + "set": { + headers: http.Header{ + "X-Amzn-Meta-Foo": {"bazValue"}, + }, + values: map[string]string{ + "Foo": "fooValue", + " Bar ": "barValue", + }, + expected: http.Header{ + "X-Amzn-Meta-Foo": {"fooValue"}, + "X-Amzn-Meta-Bar": {"barValue"}, + }, + }, + "add": { + headers: http.Header{ + "X-Amzn-Meta-Foo": {"bazValue"}, + }, + values: map[string]string{ + "Foo": "fooValue", + " Bar ": "barValue", + }, + append: true, + expected: http.Header{ + "X-Amzn-Meta-Foo": {"bazValue", "fooValue"}, + "X-Amzn-Meta-Bar": {"barValue"}, + }, + }, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + headers := Headers{header: tt.headers, prefix: prefix} + + var f func(key string) HeaderValue + if tt.append { + f = headers.AddHeader + } else { + f = headers.SetHeader + } + + for key, value := range tt.values { + f(key).String(value) + } + + if e, a := tt.expected, tt.headers; !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, but got %v", e, a) + } + }) + } +} + +func setHeader(hv HeaderValue, args []interface{}) error { + value := args[0] + + switch value.(type) { + case []byte: + return reflectCall(reflect.ValueOf(hv.Blob), args) + case bool: + return reflectCall(reflect.ValueOf(hv.Boolean), args) + case string: + return reflectCall(reflect.ValueOf(hv.String), args) + case int8: + return reflectCall(reflect.ValueOf(hv.Byte), args) + case int16: + return reflectCall(reflect.ValueOf(hv.Short), args) + case int32: + return reflectCall(reflect.ValueOf(hv.Integer), args) + case int64: + return reflectCall(reflect.ValueOf(hv.Long), args) + case float32: + return reflectCall(reflect.ValueOf(hv.Float), args) + case float64: + return reflectCall(reflect.ValueOf(hv.Double), args) + case *big.Int: + return reflectCall(reflect.ValueOf(hv.BigInteger), args) + case *big.Float: + return reflectCall(reflect.ValueOf(hv.BigDecimal), args) + default: + return fmt.Errorf("unhandled header value type") + } +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace.go new file mode 100644 index 0000000000..e78926c9a5 --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace.go @@ -0,0 +1,108 @@ +package httpbinding + +import ( + "bytes" + "fmt" +) + +const ( + uriTokenStart = '{' + uriTokenStop = '}' + uriTokenSkip = '+' +) + +func bufCap(b []byte, n int) []byte { + if cap(b) < n { + return make([]byte, 0, n) + } + + return b[0:0] +} + +// replacePathElement replaces a single element in the path []byte. +// Escape is used to control whether the value will be escaped using Amazon path escape style. +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...) + + start := bytes.Index(path, fieldBuf) + end := start + len(fieldBuf) + if start < 0 || len(path[end:]) == 0 { + // TODO what to do about error? + return path, fieldBuf, fmt.Errorf("invalid path index, start=%d,end=%d. %s", start, end, path) + } + + encodeSep := true + if path[end] == uriTokenSkip { + // '+' token means do not escape slashes + encodeSep = false + end++ + } + + if escape { + val = EscapePath(val, encodeSep) + } + + if path[end] != uriTokenStop { + return path, fieldBuf, fmt.Errorf("invalid path element, does not contain token stop, %s", path) + } + end++ + + fieldBuf = bufCap(fieldBuf, len(val)) + fieldBuf = append(fieldBuf, val...) + + keyLen := end - start + valLen := len(fieldBuf) + + if keyLen == valLen { + copy(path[start:], fieldBuf) + return path, fieldBuf, nil + } + + newLen := len(path) + (valLen - keyLen) + if len(path) < newLen { + path = path[:cap(path)] + } + if cap(path) < newLen { + newURI := make([]byte, newLen) + copy(newURI, path) + path = newURI + } + + // shift + copy(path[start+valLen:], path[end:]) + path = path[:newLen] + copy(path[start:], fieldBuf) + + return path, fieldBuf, nil +} + +// EscapePath escapes part of a URL path in Amazon style. +func EscapePath(path string, encodeSep bool) string { + var buf bytes.Buffer + for i := 0; i < len(path); i++ { + c := path[i] + if noEscape[c] || (c == '/' && !encodeSep) { + buf.WriteByte(c) + } else { + fmt.Fprintf(&buf, "%%%02X", c) + } + } + return buf.String() +} + +var noEscape [256]bool + +func init() { + for i := 0; i < len(noEscape); i++ { + // AWS expects every character except these to be escaped + noEscape[i] = (i >= 'A' && i <= 'Z') || + (i >= 'a' && i <= 'z') || + (i >= '0' && i <= '9') || + i == '-' || + i == '.' || + i == '_' || + i == '~' + } +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace_test.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace_test.go new file mode 100644 index 0000000000..689733ca00 --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace_test.go @@ -0,0 +1,67 @@ +package httpbinding + +import ( + "bytes" + "testing" +) + +func TestPathReplace(t *testing.T) { + cases := []struct { + Orig, ExpPath, ExpRawPath []byte + Key, Val string + }{ + { + Orig: []byte("/{bucket}/{key+}"), + ExpPath: []byte("/123/{key+}"), + ExpRawPath: []byte("/123/{key+}"), + Key: "bucket", Val: "123", + }, + { + Orig: []byte("/{bucket}/{key+}"), + ExpPath: []byte("/{bucket}/abc"), + ExpRawPath: []byte("/{bucket}/abc"), + Key: "key", Val: "abc", + }, + { + Orig: []byte("/{bucket}/{key+}"), + ExpPath: []byte("/{bucket}/a/b/c"), + ExpRawPath: []byte("/{bucket}/a/b/c"), + Key: "key", Val: "a/b/c", + }, + { + Orig: []byte("/{bucket}/{key+}"), + ExpPath: []byte("/1/2/3/{key+}"), + ExpRawPath: []byte("/1%2F2%2F3/{key+}"), + Key: "bucket", Val: "1/2/3", + }, + { + Orig: []byte("/{bucket}/{key+}"), + ExpPath: []byte("/reallylongvaluegoesheregrowingarray/{key+}"), + ExpRawPath: []byte("/reallylongvaluegoesheregrowingarray/{key+}"), + Key: "bucket", Val: "reallylongvaluegoesheregrowingarray", + }, + } + + var buffer [64]byte + + for i, c := range cases { + origRaw := make([]byte, len(c.Orig)) + copy(origRaw, c.Orig) + + path, _, err := replacePathElement(c.Orig, buffer[:0], c.Key, c.Val, false) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + rawPath, _, err := replacePathElement(origRaw, buffer[:0], c.Key, c.Val, true) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if e, a := c.ExpPath, path; bytes.Compare(e, a) != 0 { + t.Errorf("%d, expect uri path to be %q got %q", i, e, a) + } + if e, a := c.ExpRawPath, rawPath; bytes.Compare(e, a) != 0 { + t.Errorf("%d, expect uri raw path to be %q got %q", i, e, a) + } + } +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/query.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/query.go new file mode 100644 index 0000000000..c2e7d0a20f --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/query.go @@ -0,0 +1,107 @@ +package httpbinding + +import ( + "encoding/base64" + "math" + "math/big" + "net/url" + "strconv" +) + +// QueryValue is used to encode query key values +type QueryValue struct { + query url.Values + key string + append bool +} + +// NewQueryValue creates a new QueryValue which enables encoding +// a query value into the given url.Values. +func NewQueryValue(query url.Values, key string, append bool) QueryValue { + return QueryValue{ + query: query, + key: key, + append: append, + } +} + +func (qv QueryValue) updateKey(value string) { + if qv.append { + qv.query.Add(qv.key, value) + } else { + qv.query.Set(qv.key, value) + } +} + +// Blob encodes v as a base64 query string value +func (qv QueryValue) Blob(v []byte) { + encodeToString := base64.StdEncoding.EncodeToString(v) + qv.updateKey(encodeToString) +} + +// Boolean encodes v as a query string value +func (qv QueryValue) Boolean(v bool) { + qv.updateKey(strconv.FormatBool(v)) +} + +// String encodes v as a query string value +func (qv QueryValue) String(v string) { + qv.updateKey(v) +} + +// Byte encodes v as a query string value +func (qv QueryValue) Byte(v int8) { + qv.Long(int64(v)) +} + +// Short encodes v as a query string value +func (qv QueryValue) Short(v int16) { + qv.Long(int64(v)) +} + +// Integer encodes v as a query string value +func (qv QueryValue) Integer(v int32) { + qv.Long(int64(v)) +} + +// Long encodes v as a query string value +func (qv QueryValue) Long(v int64) { + qv.updateKey(strconv.FormatInt(v, 10)) +} + +// Float encodes v as a query string value +func (qv QueryValue) Float(v float32) { + qv.float(float64(v), 32) +} + +// Double encodes v as a query string value +func (qv QueryValue) Double(v float64) { + qv.float(v, 64) +} + +func (qv QueryValue) float(v float64, bitSize int) { + switch { + case math.IsNaN(v): + qv.String(floatNaN) + case math.IsInf(v, 1): + qv.String(floatInfinity) + case math.IsInf(v, -1): + qv.String(floatNegInfinity) + default: + qv.updateKey(strconv.FormatFloat(v, 'f', -1, bitSize)) + } +} + +// BigInteger encodes v as a query string value +func (qv QueryValue) BigInteger(v *big.Int) { + qv.updateKey(v.String()) +} + +// BigDecimal encodes v as a query string value +func (qv QueryValue) BigDecimal(v *big.Float) { + if i, accuracy := v.Int64(); accuracy == big.Exact { + qv.Long(i) + return + } + qv.updateKey(v.Text('e', -1)) +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/query_test.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/query_test.go new file mode 100644 index 0000000000..62cf25cd67 --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/query_test.go @@ -0,0 +1,234 @@ +package httpbinding + +import ( + "fmt" + "math/big" + "net/url" + "reflect" + "testing" +) + +func TestQueryValue(t *testing.T) { + const queryKey = "someKey" + + cases := map[string]struct { + values url.Values + args []interface{} + append bool + expected url.Values + }{ + "set blob": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{[]byte("baz")}, + expected: map[string][]string{ + queryKey: {"YmF6"}, + }, + }, + "set bool": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{true}, + expected: map[string][]string{ + queryKey: {"true"}, + }, + }, + "set string": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{"string value"}, + expected: map[string][]string{ + queryKey: {"string value"}, + }, + }, + "set byte": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int8(127)}, + expected: map[string][]string{ + queryKey: {"127"}, + }, + }, + "set short": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int16(32767)}, + expected: map[string][]string{ + queryKey: {"32767"}, + }, + }, + "set integer": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int32(2147483647)}, + expected: map[string][]string{ + queryKey: {"2147483647"}, + }, + }, + "set long": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int64(9223372036854775807)}, + expected: map[string][]string{ + queryKey: {"9223372036854775807"}, + }, + }, + "set float": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{float32(3.14159)}, + expected: map[string][]string{ + queryKey: {"3.14159"}, + }, + }, + "set double": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{float64(3.14159)}, + expected: map[string][]string{ + queryKey: {"3.14159"}, + }, + }, + "set bigInteger": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{new(big.Int).SetInt64(1)}, + expected: map[string][]string{ + queryKey: {"1"}, + }, + }, + "set bigDecimal": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, + expected: map[string][]string{ + queryKey: {"1.02410241024e+03"}, + }, + }, + "add blob": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{[]byte("baz")}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "YmF6"}, + }, + }, + "add bool": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{true}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "true"}, + }, + }, + "add string": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{"string value"}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "string value"}, + }, + }, + "add byte": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int8(127)}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "127"}, + }, + }, + "add short": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int16(32767)}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "32767"}, + }, + }, + "add integer": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int32(2147483647)}, + expected: map[string][]string{ + queryKey: {"2147483647"}, + }, + }, + "add long": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{int64(9223372036854775807)}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "9223372036854775807"}, + }, + }, + "add float": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{float32(3.14159)}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "3.14159"}, + }, + }, + "add double": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{float64(3.14159)}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "3.14159"}, + }, + }, + "add bigInteger": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{new(big.Int).SetInt64(1)}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "1"}, + }, + }, + "add bigDecimal": { + values: url.Values{queryKey: []string{"foobar"}}, + args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, + append: true, + expected: map[string][]string{ + queryKey: {"foobar", "1.02410241024e+03"}, + }, + }, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + if tt.values == nil { + tt.values = url.Values{} + } + + qv := NewQueryValue(tt.values, queryKey, tt.append) + + if err := setQueryValue(qv, tt.args); err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if e, a := tt.expected, qv.query; !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, got %v", e, a) + } + }) + } +} + +func setQueryValue(qv QueryValue, args []interface{}) error { + value := args[0] + + switch value.(type) { + case []byte: + return reflectCall(reflect.ValueOf(qv.Blob), args) + case bool: + return reflectCall(reflect.ValueOf(qv.Boolean), args) + case string: + return reflectCall(reflect.ValueOf(qv.String), args) + case int8: + return reflectCall(reflect.ValueOf(qv.Byte), args) + case int16: + return reflectCall(reflect.ValueOf(qv.Short), args) + case int32: + return reflectCall(reflect.ValueOf(qv.Integer), args) + case int64: + return reflectCall(reflect.ValueOf(qv.Long), args) + case float32: + return reflectCall(reflect.ValueOf(qv.Float), args) + case float64: + return reflectCall(reflect.ValueOf(qv.Double), args) + case *big.Int: + return reflectCall(reflect.ValueOf(qv.BigInteger), args) + case *big.Float: + return reflectCall(reflect.ValueOf(qv.BigDecimal), args) + default: + return fmt.Errorf("unhandled query value type") + } +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/shared_test.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/shared_test.go new file mode 100644 index 0000000000..2b7518e1c7 --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/shared_test.go @@ -0,0 +1,36 @@ +package httpbinding + +import ( + "fmt" + "reflect" +) + +func reflectCall(funcValue reflect.Value, args []interface{}) error { + argValues := make([]reflect.Value, len(args)) + + for i, v := range args { + value := reflect.ValueOf(v) + argValues[i] = value + } + + retValues := funcValue.Call(argValues) + if len(retValues) > 0 { + errValue := retValues[0] + + if typeName := errValue.Type().Name(); typeName != "error" { + panic(fmt.Sprintf("expected first return argument to be error but got %v", typeName)) + } + + if errValue.IsNil() { + return nil + } + + if err, ok := errValue.Interface().(error); ok { + return err + } + + panic(fmt.Sprintf("expected %v to return error type, but got %v", funcValue.Type().String(), retValues[0].Type().String())) + } + + return nil +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/uri.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/uri.go new file mode 100644 index 0000000000..f04e11984a --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/uri.go @@ -0,0 +1,111 @@ +package httpbinding + +import ( + "math" + "math/big" + "strconv" + "strings" +) + +// URIValue is used to encode named URI parameters +type URIValue struct { + path, rawPath, buffer *[]byte + + key string +} + +func newURIValue(path *[]byte, rawPath *[]byte, buffer *[]byte, key string) URIValue { + return URIValue{path: path, rawPath: rawPath, buffer: buffer, key: key} +} + +func (u URIValue) modifyURI(value string) (err error) { + *u.path, *u.buffer, err = replacePathElement(*u.path, *u.buffer, u.key, value, false) + if err != nil { + return err + } + *u.rawPath, *u.buffer, err = replacePathElement(*u.rawPath, *u.buffer, u.key, value, true) + return err +} + +// Boolean encodes v as a URI string value +func (u URIValue) Boolean(v bool) error { + return u.modifyURI(strconv.FormatBool(v)) +} + +// String encodes v as a URI string value +func (u URIValue) String(v string) error { + return u.modifyURI(v) +} + +// Byte encodes v as a URI string value +func (u URIValue) Byte(v int8) error { + return u.Long(int64(v)) +} + +// Short encodes v as a URI string value +func (u URIValue) Short(v int16) error { + return u.Long(int64(v)) +} + +// Integer encodes v as a URI string value +func (u URIValue) Integer(v int32) error { + return u.Long(int64(v)) +} + +// Long encodes v as a URI string value +func (u URIValue) Long(v int64) error { + return u.modifyURI(strconv.FormatInt(v, 10)) +} + +// Float encodes v as a query string value +func (u URIValue) Float(v float32) error { + return u.float(float64(v), 32) +} + +// Double encodes v as a query string value +func (u URIValue) Double(v float64) error { + return u.float(v, 64) +} + +func (u URIValue) float(v float64, bitSize int) error { + switch { + case math.IsNaN(v): + return u.String(floatNaN) + case math.IsInf(v, 1): + return u.String(floatInfinity) + case math.IsInf(v, -1): + return u.String(floatNegInfinity) + default: + return u.modifyURI(strconv.FormatFloat(v, 'f', -1, bitSize)) + } +} + +// BigInteger encodes v as a query string value +func (u URIValue) BigInteger(v *big.Int) error { + return u.modifyURI(v.String()) +} + +// BigDecimal encodes v as a query string value +func (u URIValue) BigDecimal(v *big.Float) error { + if i, accuracy := v.Int64(); accuracy == big.Exact { + return u.Long(i) + } + return u.modifyURI(v.Text('e', -1)) +} + +// SplitURI parses a Smithy HTTP binding trait URI +func SplitURI(uri string) (path, query string) { + queryStart := strings.IndexRune(uri, '?') + if queryStart == -1 { + path = uri + return path, query + } + + path = uri[:queryStart] + if queryStart+1 >= len(uri) { + return path, query + } + query = uri[queryStart+1:] + + return path, query +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/uri_test.go b/vendor/github.com/aws/smithy-go/encoding/httpbinding/uri_test.go new file mode 100644 index 0000000000..039e1e5baa --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/uri_test.go @@ -0,0 +1,198 @@ +package httpbinding + +import ( + "fmt" + "math/big" + "reflect" + "strconv" + "testing" +) + +func TestURIValue(t *testing.T) { + const uriKey = "someKey" + const path = "/some/{someKey}/{path+}" + + type expected struct { + path string + raw string + } + + cases := map[string]struct { + path string + args []interface{} + expected expected + }{ + "bool": { + path: path, + args: []interface{}{true}, + expected: expected{ + path: "/some/true/{path+}", + raw: "/some/true/{path+}", + }, + }, + "string": { + path: path, + args: []interface{}{"someValue"}, + expected: expected{ + path: "/some/someValue/{path+}", + raw: "/some/someValue/{path+}", + }, + }, + "byte": { + path: path, + args: []interface{}{int8(127)}, + expected: expected{ + path: "/some/127/{path+}", + raw: "/some/127/{path+}", + }, + }, + "short": { + path: path, + args: []interface{}{int16(32767)}, + expected: expected{ + path: "/some/32767/{path+}", + raw: "/some/32767/{path+}", + }, + }, + "integer": { + path: path, + args: []interface{}{int32(2147483647)}, + expected: expected{ + path: "/some/2147483647/{path+}", + raw: "/some/2147483647/{path+}", + }, + }, + "long": { + path: path, + args: []interface{}{int64(9223372036854775807)}, + expected: expected{ + path: "/some/9223372036854775807/{path+}", + raw: "/some/9223372036854775807/{path+}", + }, + }, + "float32": { + path: path, + args: []interface{}{float32(3.14159)}, + expected: expected{ + path: "/some/3.14159/{path+}", + raw: "/some/3.14159/{path+}", + }, + }, + "float64": { + path: path, + args: []interface{}{float64(3.14159)}, + expected: expected{ + path: "/some/3.14159/{path+}", + raw: "/some/3.14159/{path+}", + }, + }, + "bigInteger": { + path: path, + args: []interface{}{new(big.Int).SetInt64(1)}, + expected: expected{ + path: "/some/1/{path+}", + raw: "/some/1/{path+}", + }, + }, + "bigDecimal": { + path: path, + args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, + expected: expected{ + path: "/some/1.02410241024e+03/{path+}", + raw: "/some/1.02410241024e%2B03/{path+}", + }, + }, + } + + buffer := make([]byte, 1024) + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + pBytes, rBytes := []byte(tt.path), []byte(tt.path) + + uv := newURIValue(&pBytes, &rBytes, &buffer, uriKey) + + if err := setURI(uv, tt.args); err != nil { + t.Fatalf("expected no error, %v", err) + } + + if e, a := tt.expected.path, string(pBytes); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + if e, a := tt.expected.raw, string(rBytes); e != a { + t.Errorf("expected %v, got %v", e, a) + } + }) + } +} + +func setURI(uv URIValue, args []interface{}) error { + value := args[0] + + switch value.(type) { + case bool: + return reflectCall(reflect.ValueOf(uv.Boolean), args) + case string: + return reflectCall(reflect.ValueOf(uv.String), args) + case int8: + return reflectCall(reflect.ValueOf(uv.Byte), args) + case int16: + return reflectCall(reflect.ValueOf(uv.Short), args) + case int32: + return reflectCall(reflect.ValueOf(uv.Integer), args) + case int64: + return reflectCall(reflect.ValueOf(uv.Long), args) + case float32: + return reflectCall(reflect.ValueOf(uv.Float), args) + case float64: + return reflectCall(reflect.ValueOf(uv.Double), args) + case *big.Int: + return reflectCall(reflect.ValueOf(uv.BigInteger), args) + case *big.Float: + return reflectCall(reflect.ValueOf(uv.BigDecimal), args) + default: + return fmt.Errorf("unhandled value type") + } +} + +func TestParseURI(t *testing.T) { + cases := []struct { + Value string + Path string + Query string + }{ + { + Value: "/my/uri/foo/bar/baz", + Path: "/my/uri/foo/bar/baz", + Query: "", + }, + { + Value: "/path?requiredKey", + Path: "/path", + Query: "requiredKey", + }, + { + Value: "/path?", + Path: "/path", + Query: "", + }, + { + Value: "?", + Path: "", + Query: "", + }, + } + + for i, tt := range cases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + path, query := SplitURI(tt.Value) + if e, a := tt.Path, path; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := tt.Query, query; e != a { + t.Errorf("expected %v, got %v", e, a) + } + }) + } +} diff --git a/vendor/github.com/aws/smithy-go/encoding/httpbinding/ya.make b/vendor/github.com/aws/smithy-go/encoding/httpbinding/ya.make new file mode 100644 index 0000000000..f1f0698636 --- /dev/null +++ b/vendor/github.com/aws/smithy-go/encoding/httpbinding/ya.make @@ -0,0 +1,26 @@ +GO_LIBRARY() + +LICENSE(Apache-2.0) + +SRCS( + encode.go + header.go + path_replace.go + query.go + uri.go +) + +GO_TEST_SRCS( + encode_test.go + header_test.go + path_replace_test.go + query_test.go + shared_test.go + uri_test.go +) + +END() + +RECURSE( + gotest +) |