mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-16 16:20:02 +00:00
961c878e0d
Switch to using Go modules. This migrates our vendor.json to use Go 1.11's modules system, and replaces the vendor folder with the output of go mod vendor. The vendored code should remain basically the same; I believe some tree shaking of packages and support scripts/licenses/READMEs/etc. happened. This also fixes Travis and our Makefile to no longer use govendor.
296 lines
7.4 KiB
Go
296 lines
7.4 KiB
Go
// Package rest provides RESTful serialization of AWS requests and responses.
|
|
package rest
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
"github.com/aws/aws-sdk-go/aws/request"
|
|
"github.com/aws/aws-sdk-go/private/protocol"
|
|
)
|
|
|
|
// Whether the byte value can be sent without escaping in AWS URLs
|
|
var noEscape [256]bool
|
|
|
|
var errValueNotSet = fmt.Errorf("value not set")
|
|
|
|
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 == '~'
|
|
}
|
|
}
|
|
|
|
// BuildHandler is a named request handler for building rest protocol requests
|
|
var BuildHandler = request.NamedHandler{Name: "awssdk.rest.Build", Fn: Build}
|
|
|
|
// Build builds the REST component of a service request.
|
|
func Build(r *request.Request) {
|
|
if r.ParamsFilled() {
|
|
v := reflect.ValueOf(r.Params).Elem()
|
|
buildLocationElements(r, v, false)
|
|
buildBody(r, v)
|
|
}
|
|
}
|
|
|
|
// BuildAsGET builds the REST component of a service request with the ability to hoist
|
|
// data from the body.
|
|
func BuildAsGET(r *request.Request) {
|
|
if r.ParamsFilled() {
|
|
v := reflect.ValueOf(r.Params).Elem()
|
|
buildLocationElements(r, v, true)
|
|
buildBody(r, v)
|
|
}
|
|
}
|
|
|
|
func buildLocationElements(r *request.Request, v reflect.Value, buildGETQuery bool) {
|
|
query := r.HTTPRequest.URL.Query()
|
|
|
|
// Setup the raw path to match the base path pattern. This is needed
|
|
// so that when the path is mutated a custom escaped version can be
|
|
// stored in RawPath that will be used by the Go client.
|
|
r.HTTPRequest.URL.RawPath = r.HTTPRequest.URL.Path
|
|
|
|
for i := 0; i < v.NumField(); i++ {
|
|
m := v.Field(i)
|
|
if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) {
|
|
continue
|
|
}
|
|
|
|
if m.IsValid() {
|
|
field := v.Type().Field(i)
|
|
name := field.Tag.Get("locationName")
|
|
if name == "" {
|
|
name = field.Name
|
|
}
|
|
if kind := m.Kind(); kind == reflect.Ptr {
|
|
m = m.Elem()
|
|
} else if kind == reflect.Interface {
|
|
if !m.Elem().IsValid() {
|
|
continue
|
|
}
|
|
}
|
|
if !m.IsValid() {
|
|
continue
|
|
}
|
|
if field.Tag.Get("ignore") != "" {
|
|
continue
|
|
}
|
|
|
|
var err error
|
|
switch field.Tag.Get("location") {
|
|
case "headers": // header maps
|
|
err = buildHeaderMap(&r.HTTPRequest.Header, m, field.Tag)
|
|
case "header":
|
|
err = buildHeader(&r.HTTPRequest.Header, m, name, field.Tag)
|
|
case "uri":
|
|
err = buildURI(r.HTTPRequest.URL, m, name, field.Tag)
|
|
case "querystring":
|
|
err = buildQueryString(query, m, name, field.Tag)
|
|
default:
|
|
if buildGETQuery {
|
|
err = buildQueryString(query, m, name, field.Tag)
|
|
}
|
|
}
|
|
r.Error = err
|
|
}
|
|
if r.Error != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
r.HTTPRequest.URL.RawQuery = query.Encode()
|
|
if !aws.BoolValue(r.Config.DisableRestProtocolURICleaning) {
|
|
cleanPath(r.HTTPRequest.URL)
|
|
}
|
|
}
|
|
|
|
func buildBody(r *request.Request, v reflect.Value) {
|
|
if field, ok := v.Type().FieldByName("_"); ok {
|
|
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
|
pfield, _ := v.Type().FieldByName(payloadName)
|
|
if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
|
|
payload := reflect.Indirect(v.FieldByName(payloadName))
|
|
if payload.IsValid() && payload.Interface() != nil {
|
|
switch reader := payload.Interface().(type) {
|
|
case io.ReadSeeker:
|
|
r.SetReaderBody(reader)
|
|
case []byte:
|
|
r.SetBufferBody(reader)
|
|
case string:
|
|
r.SetStringBody(reader)
|
|
default:
|
|
r.Error = awserr.New("SerializationError",
|
|
"failed to encode REST request",
|
|
fmt.Errorf("unknown payload type %s", payload.Type()))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func buildHeader(header *http.Header, v reflect.Value, name string, tag reflect.StructTag) error {
|
|
str, err := convertType(v, tag)
|
|
if err == errValueNotSet {
|
|
return nil
|
|
} else if err != nil {
|
|
return awserr.New("SerializationError", "failed to encode REST request", err)
|
|
}
|
|
|
|
header.Add(name, str)
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildHeaderMap(header *http.Header, v reflect.Value, tag reflect.StructTag) error {
|
|
prefix := tag.Get("locationName")
|
|
for _, key := range v.MapKeys() {
|
|
str, err := convertType(v.MapIndex(key), tag)
|
|
if err == errValueNotSet {
|
|
continue
|
|
} else if err != nil {
|
|
return awserr.New("SerializationError", "failed to encode REST request", err)
|
|
|
|
}
|
|
|
|
header.Add(prefix+key.String(), str)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func buildURI(u *url.URL, v reflect.Value, name string, tag reflect.StructTag) error {
|
|
value, err := convertType(v, tag)
|
|
if err == errValueNotSet {
|
|
return nil
|
|
} else if err != nil {
|
|
return awserr.New("SerializationError", "failed to encode REST request", err)
|
|
}
|
|
|
|
u.Path = strings.Replace(u.Path, "{"+name+"}", value, -1)
|
|
u.Path = strings.Replace(u.Path, "{"+name+"+}", value, -1)
|
|
|
|
u.RawPath = strings.Replace(u.RawPath, "{"+name+"}", EscapePath(value, true), -1)
|
|
u.RawPath = strings.Replace(u.RawPath, "{"+name+"+}", EscapePath(value, false), -1)
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildQueryString(query url.Values, v reflect.Value, name string, tag reflect.StructTag) error {
|
|
switch value := v.Interface().(type) {
|
|
case []*string:
|
|
for _, item := range value {
|
|
query.Add(name, *item)
|
|
}
|
|
case map[string]*string:
|
|
for key, item := range value {
|
|
query.Add(key, *item)
|
|
}
|
|
case map[string][]*string:
|
|
for key, items := range value {
|
|
for _, item := range items {
|
|
query.Add(key, *item)
|
|
}
|
|
}
|
|
default:
|
|
str, err := convertType(v, tag)
|
|
if err == errValueNotSet {
|
|
return nil
|
|
} else if err != nil {
|
|
return awserr.New("SerializationError", "failed to encode REST request", err)
|
|
}
|
|
query.Set(name, str)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func cleanPath(u *url.URL) {
|
|
hasSlash := strings.HasSuffix(u.Path, "/")
|
|
|
|
// clean up path, removing duplicate `/`
|
|
u.Path = path.Clean(u.Path)
|
|
u.RawPath = path.Clean(u.RawPath)
|
|
|
|
if hasSlash && !strings.HasSuffix(u.Path, "/") {
|
|
u.Path += "/"
|
|
u.RawPath += "/"
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
func convertType(v reflect.Value, tag reflect.StructTag) (str string, err error) {
|
|
v = reflect.Indirect(v)
|
|
if !v.IsValid() {
|
|
return "", errValueNotSet
|
|
}
|
|
|
|
switch value := v.Interface().(type) {
|
|
case string:
|
|
str = value
|
|
case []byte:
|
|
str = base64.StdEncoding.EncodeToString(value)
|
|
case bool:
|
|
str = strconv.FormatBool(value)
|
|
case int64:
|
|
str = strconv.FormatInt(value, 10)
|
|
case float64:
|
|
str = strconv.FormatFloat(value, 'f', -1, 64)
|
|
case time.Time:
|
|
format := tag.Get("timestampFormat")
|
|
if len(format) == 0 {
|
|
format = protocol.RFC822TimeFormatName
|
|
if tag.Get("location") == "querystring" {
|
|
format = protocol.ISO8601TimeFormatName
|
|
}
|
|
}
|
|
str = protocol.FormatTime(format, value)
|
|
case aws.JSONValue:
|
|
if len(value) == 0 {
|
|
return "", errValueNotSet
|
|
}
|
|
escaping := protocol.NoEscape
|
|
if tag.Get("location") == "header" {
|
|
escaping = protocol.Base64Escape
|
|
}
|
|
str, err = protocol.EncodeJSONValue(value, escaping)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to encode JSONValue, %v", err)
|
|
}
|
|
default:
|
|
err := fmt.Errorf("unsupported value for param %v (%s)", v.Interface(), v.Type())
|
|
return "", err
|
|
}
|
|
return str, nil
|
|
}
|