From 549578a33d5d41df38903e6a7c138d1b2762aca8 Mon Sep 17 00:00:00 2001 From: Jason Del Ponte Date: Thu, 16 Nov 2017 16:08:56 -0800 Subject: [PATCH] private/protocol/restjson: Define JSONValue marshaling for body and querystring (#1640) Adds support for APIs which use JSONValue for body and querystring targets. Fix #1636 --- models/protocol_tests/generate.go | 3 +- models/protocol_tests/input/rest-json.json | 68 +++++- models/protocol_tests/output/rest-json.json | 56 ++++- private/model/api/param_filler.go | 14 ++ private/protocol/json/jsonutil/build.go | 21 +- private/protocol/json/jsonutil/unmarshal.go | 15 +- private/protocol/jsonvalue.go | 76 +++++++ private/protocol/jsonvalue_test.go | 93 ++++++++ private/protocol/rest/build.go | 21 +- private/protocol/rest/unmarshal.go | 14 +- private/protocol/restjson/build_test.go | 203 +++++++++++++++-- private/protocol/restjson/unmarshal_test.go | 231 +++++++++++++++++++- 12 files changed, 745 insertions(+), 70 deletions(-) create mode 100644 private/protocol/jsonvalue.go create mode 100644 private/protocol/jsonvalue_test.go diff --git a/models/protocol_tests/generate.go b/models/protocol_tests/generate.go index 4f9e103fa97..7fd48bed9e8 100644 --- a/models/protocol_tests/generate.go +++ b/models/protocol_tests/generate.go @@ -459,7 +459,7 @@ func GenerateAssertions(out interface{}, shape *api.Shape, prefix string) string code += GenerateAssertions(v, s, prefix+"[\""+k+"\"]") } } else if shape.Type == "jsonvalue" { - code += fmt.Sprintf("reflect.DeepEqual(%s, map[string]interface{}%s)", prefix, walkMap(out.(map[string]interface{}))) + code += fmt.Sprintf("reflect.DeepEqual(%s, map[string]interface{}%s)\n", prefix, walkMap(out.(map[string]interface{}))) } else { for _, k := range keys { v := t[k] @@ -517,6 +517,7 @@ func getType(t string) uint { } func main() { + fmt.Println("Generating test suite", os.Args[1:]) out := generateTestSuite(os.Args[1]) if len(os.Args) == 3 { f, err := os.Create(os.Args[2]) diff --git a/models/protocol_tests/input/rest-json.json b/models/protocol_tests/input/rest-json.json index fe7933c24ef..73f960370f9 100644 --- a/models/protocol_tests/input/rest-json.json +++ b/models/protocol_tests/input/rest-json.json @@ -1310,17 +1310,46 @@ "shapes": { "InputShape": { "type": "structure", + "payload": "Body", "members": { - "Attr": { + "HeaderField": { "shape": "StringType", - "jsonvalue": true, - "location": "header", - "locationName": "X-Amz-Foo" + "jsonvalue": true, + "location": "header", + "locationName": "X-Amz-Foo" + }, + "QueryField": { + "shape": "StringType", + "jsonvalue": true, + "location": "querystring", + "locationName": "Bar" + }, + "Body": { + "shape": "BodyStructure" } } }, "StringType": { "type": "string" + }, + "ListType": { + "type": "list", + "member": { + "shape": "StringType", + "jsonvalue": true + } + }, + "BodyStructure": { + "type": "structure", + "members": { + "BodyField": { + "shape": "StringType", + "jsonvalue": true + }, + "BodyListField": { + "shape": "ListType" + } + } } }, "cases": [ @@ -1335,12 +1364,16 @@ "name": "OperationName" }, "params": { - "Attr": {"Foo":"Bar"} + "HeaderField": {"Foo":"Bar"}, + "QueryField": {"Foo":"Bar"}, + "Body": { + "BodyField": {"Foo":"Bar"} + } }, "serialized": { - "uri": "/", + "uri": "/?Bar=%7B%22Foo%22%3A%22Bar%22%7D", "headers": {"X-Amz-Foo": "eyJGb28iOiJCYXIifQ=="}, - "body": "" + "body": "{\"BodyField\":\"{\\\"Foo\\\":\\\"Bar\\\"}\"}" } }, { @@ -1353,6 +1386,27 @@ }, "name": "OperationName" }, + "params": { + "Body": { + "BodyListField": [{"Foo":"Bar"}] + } + }, + "serialized": { + "uri": "/", + "headers": {}, + "body": "{\"BodyListField\":[\"{\\\"Foo\\\":\\\"Bar\\\"}\"]}" + } + }, + { + "given": { + "input": { + "shape": "InputShape" + }, + "http": { + "method": "POST" + }, + "name": "OperationName" + }, "params": { }, "serialized": { diff --git a/models/protocol_tests/output/rest-json.json b/models/protocol_tests/output/rest-json.json index edd3733d9ee..cb6bfc31240 100644 --- a/models/protocol_tests/output/rest-json.json +++ b/models/protocol_tests/output/rest-json.json @@ -614,16 +614,30 @@ "OutputShape": { "type": "structure", "members": { - "Attr": { + "HeaderField": { "shape": "StringType", - "jsonvalue": true, - "location": "header", - "locationName": "X-Amz-Foo" + "jsonvalue": true, + "location": "header", + "locationName": "X-Amz-Foo" + }, + "BodyField":{ + "shape": "StringType", + "jsonvalue": true + }, + "BodyListField": { + "shape": "ListType" } } }, "StringType": { "type": "string" + }, + "ListType": { + "type": "list", + "member": { + "shape": "StringType", + "jsonvalue": true + } } }, "cases": [ @@ -635,11 +649,43 @@ "name": "OperationName" }, "result": { - "Attr": {"Foo":"Bar"} + "HeaderField": {"Foo":"Bar"}, + "BodyField": {"Foo":"Bar"} }, "response": { "status_code": 200, "headers": {"X-Amz-Foo": "eyJGb28iOiJCYXIifQ=="}, + "body": "{\"BodyField\":\"{\\\"Foo\\\":\\\"Bar\\\"}\"}" + } + }, + { + "given": { + "output": { + "shape": "OutputShape" + }, + "name": "OperationName" + }, + "result": { + "BodyListField": [{"Foo":"Bar"}] + }, + "response": { + "status_code": 200, + "headers": {}, + "body": "{\"BodyListField\":[\"{\\\"Foo\\\":\\\"Bar\\\"}\"]}" + } + }, + { + "given": { + "output": { + "shape": "OutputShape" + }, + "name": "OperationName" + }, + "result": { + }, + "response": { + "status_code": 200, + "headers": {}, "body": "" } } diff --git a/private/model/api/param_filler.go b/private/model/api/param_filler.go index eb3edb9d2a5..f4fde6700a3 100644 --- a/private/model/api/param_filler.go +++ b/private/model/api/param_filler.go @@ -4,6 +4,7 @@ package api import ( "fmt" + "encoding/json" "reflect" "strings" @@ -79,6 +80,19 @@ func (f paramFiller) paramsStructAny(value interface{}, shape *Shape) string { if v.IsValid() { return fmt.Sprintf("aws.Time(time.Unix(%d, 0))", int(v.Float())) } + case "jsonvalue": + v, err := json.Marshal(value) + if err != nil { + panic("failed to marshal JSONValue, "+err.Error()) + } + const tmpl = `func() aws.JSONValue { + var m aws.JSONValue + if err := json.Unmarshal([]byte(%q), &m); err != nil { + panic("failed to unmarshal JSONValue, "+err.Error()) + } + return m + }()` + return fmt.Sprintf(tmpl, string(v)) default: panic("Unhandled type " + shape.Type) } diff --git a/private/protocol/json/jsonutil/build.go b/private/protocol/json/jsonutil/build.go index 6efe43d5f39..ec765ba257e 100644 --- a/private/protocol/json/jsonutil/build.go +++ b/private/protocol/json/jsonutil/build.go @@ -12,6 +12,7 @@ import ( "strconv" "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/private/protocol" ) @@ -49,7 +50,10 @@ func buildAny(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) err t = "list" } case reflect.Map: - t = "map" + // cannot be a JSONValue map + if _, ok := value.Interface().(aws.JSONValue); !ok { + t = "map" + } } } @@ -210,14 +214,11 @@ func buildScalar(v reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) erro } buf.Write(strconv.AppendFloat(scratch[:0], f, 'f', -1, 64)) default: - switch value.Type() { - case timeType: - converted := v.Interface().(*time.Time) - + switch converted := value.Interface().(type) { + case time.Time: buf.Write(strconv.AppendInt(scratch[:0], converted.UTC().Unix(), 10)) - case byteSliceType: + case []byte: if !value.IsNil() { - converted := value.Interface().([]byte) buf.WriteByte('"') if len(converted) < 1024 { // for small buffers, using Encode directly is much faster. @@ -233,6 +234,12 @@ func buildScalar(v reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) erro } buf.WriteByte('"') } + case aws.JSONValue: + str, err := protocol.EncodeJSONValue(converted, protocol.QuotedEscape) + if err != nil { + return fmt.Errorf("unable to encode JSONValue, %v", err) + } + buf.WriteString(str) default: return fmt.Errorf("unsupported JSON value %v (%s)", value.Interface(), value.Type()) } diff --git a/private/protocol/json/jsonutil/unmarshal.go b/private/protocol/json/jsonutil/unmarshal.go index fea53561366..037e1e7be78 100644 --- a/private/protocol/json/jsonutil/unmarshal.go +++ b/private/protocol/json/jsonutil/unmarshal.go @@ -8,6 +8,9 @@ import ( "io/ioutil" "reflect" "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/private/protocol" ) // UnmarshalJSON reads a stream and unmarshals the results in object v. @@ -50,7 +53,10 @@ func unmarshalAny(value reflect.Value, data interface{}, tag reflect.StructTag) t = "list" } case reflect.Map: - t = "map" + // cannot be a JSONValue map + if _, ok := value.Interface().(aws.JSONValue); !ok { + t = "map" + } } } @@ -183,6 +189,13 @@ func unmarshalScalar(value reflect.Value, data interface{}, tag reflect.StructTa return err } value.Set(reflect.ValueOf(b)) + case aws.JSONValue: + // No need to use escaping as the value is a non-quoted string. + v, err := protocol.DecodeJSONValue(d, protocol.NoEscape) + if err != nil { + return err + } + value.Set(reflect.ValueOf(v)) default: return errf() } diff --git a/private/protocol/jsonvalue.go b/private/protocol/jsonvalue.go new file mode 100644 index 00000000000..776d1101843 --- /dev/null +++ b/private/protocol/jsonvalue.go @@ -0,0 +1,76 @@ +package protocol + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strconv" + + "github.com/aws/aws-sdk-go/aws" +) + +// EscapeMode is the mode that should be use for escaping a value +type EscapeMode uint + +// The modes for escaping a value before it is marshaled, and unmarshaled. +const ( + NoEscape EscapeMode = iota + Base64Escape + QuotedEscape +) + +// EncodeJSONValue marshals the value into a JSON string, and optionally base64 +// encodes the string before returning it. +// +// Will panic if the escape mode is unknown. +func EncodeJSONValue(v aws.JSONValue, escape EscapeMode) (string, error) { + b, err := json.Marshal(v) + if err != nil { + return "", err + } + + switch escape { + case NoEscape: + return string(b), nil + case Base64Escape: + return base64.StdEncoding.EncodeToString(b), nil + case QuotedEscape: + return strconv.Quote(string(b)), nil + } + + panic(fmt.Sprintf("EncodeJSONValue called with unknown EscapeMode, %v", escape)) +} + +// DecodeJSONValue will attempt to decode the string input as a JSONValue. +// Optionally decoding base64 the value first before JSON unmarshaling. +// +// Will panic if the escape mode is unknown. +func DecodeJSONValue(v string, escape EscapeMode) (aws.JSONValue, error) { + var b []byte + var err error + + switch escape { + case NoEscape: + b = []byte(v) + case Base64Escape: + b, err = base64.StdEncoding.DecodeString(v) + case QuotedEscape: + var u string + u, err = strconv.Unquote(v) + b = []byte(u) + default: + panic(fmt.Sprintf("DecodeJSONValue called with unknown EscapeMode, %v", escape)) + } + + if err != nil { + return nil, err + } + + m := aws.JSONValue{} + err = json.Unmarshal(b, &m) + if err != nil { + return nil, err + } + + return m, nil +} diff --git a/private/protocol/jsonvalue_test.go b/private/protocol/jsonvalue_test.go new file mode 100644 index 00000000000..66a3c8cde11 --- /dev/null +++ b/private/protocol/jsonvalue_test.go @@ -0,0 +1,93 @@ +package protocol + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" +) + +var testJSONValueCases = []struct { + Value aws.JSONValue + Mode EscapeMode + String string +}{ + { + Value: aws.JSONValue{ + "abc": 123., + }, + Mode: NoEscape, + String: `{"abc":123}`, + }, + { + Value: aws.JSONValue{ + "abc": 123., + }, + Mode: Base64Escape, + String: `eyJhYmMiOjEyM30=`, + }, + { + Value: aws.JSONValue{ + "abc": 123., + }, + Mode: QuotedEscape, + String: `"{\"abc\":123}"`, + }, +} + +func TestEncodeJSONValue(t *testing.T) { + for i, c := range testJSONValueCases { + str, err := EncodeJSONValue(c.Value, c.Mode) + if err != nil { + t.Fatalf("%d, expect no error, got %v", i, err) + } + if e, a := c.String, str; e != a { + t.Errorf("%d, expect %v encoded value, got %v", i, e, a) + } + } +} + +func TestDecodeJSONValue(t *testing.T) { + for i, c := range testJSONValueCases { + val, err := DecodeJSONValue(c.String, c.Mode) + if err != nil { + t.Fatalf("%d, expect no error, got %v", i, err) + } + if e, a := c.Value, val; !reflect.DeepEqual(e, a) { + t.Errorf("%d, expect %v encoded value, got %v", i, e, a) + } + } +} + +func TestEncodeJSONValue_PanicUnkownMode(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expect panic, got none") + } else { + reason := fmt.Sprintf("%v", r) + if e, a := "unknown EscapeMode", reason; !strings.Contains(a, e) { + t.Errorf("expect %q to be in %v", e, a) + } + } + }() + + val := aws.JSONValue{} + + EncodeJSONValue(val, 123456) +} +func TestDecodeJSONValue_PanicUnkownMode(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expect panic, got none") + } else { + reason := fmt.Sprintf("%v", r) + if e, a := "unknown EscapeMode", reason; !strings.Contains(a, e) { + t.Errorf("expect %q to be in %v", e, a) + } + } + }() + + DecodeJSONValue(`{"abc":123}`, 123456) +} diff --git a/private/protocol/rest/build.go b/private/protocol/rest/build.go index 71618356493..c405288d742 100644 --- a/private/protocol/rest/build.go +++ b/private/protocol/rest/build.go @@ -4,7 +4,6 @@ package rest import ( "bytes" "encoding/base64" - "encoding/json" "fmt" "io" "net/http" @@ -18,6 +17,7 @@ import ( "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" ) // RFC822 returns an RFC822 formatted timestamp for AWS protocols @@ -252,13 +252,12 @@ func EscapePath(path string, encodeSep bool) string { return buf.String() } -func convertType(v reflect.Value, tag reflect.StructTag) (string, error) { +func convertType(v reflect.Value, tag reflect.StructTag) (str string, err error) { v = reflect.Indirect(v) if !v.IsValid() { return "", errValueNotSet } - var str string switch value := v.Interface().(type) { case string: str = value @@ -273,17 +272,19 @@ func convertType(v reflect.Value, tag reflect.StructTag) (string, error) { case time.Time: str = value.UTC().Format(RFC822) case aws.JSONValue: - b, err := json.Marshal(value) - if err != nil { - return "", err + if len(value) == 0 { + return "", errValueNotSet } + escaping := protocol.NoEscape if tag.Get("location") == "header" { - str = base64.StdEncoding.EncodeToString(b) - } else { - str = string(b) + 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()) + err := fmt.Errorf("unsupported value for param %v (%s)", v.Interface(), v.Type()) return "", err } return str, nil diff --git a/private/protocol/rest/unmarshal.go b/private/protocol/rest/unmarshal.go index 7a779ee2260..823f045eed7 100644 --- a/private/protocol/rest/unmarshal.go +++ b/private/protocol/rest/unmarshal.go @@ -3,7 +3,6 @@ package rest import ( "bytes" "encoding/base64" - "encoding/json" "fmt" "io" "io/ioutil" @@ -16,6 +15,7 @@ import ( "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" ) // UnmarshalHandler is a named request handler for unmarshaling rest protocol requests @@ -204,17 +204,11 @@ func unmarshalHeader(v reflect.Value, header string, tag reflect.StructTag) erro } v.Set(reflect.ValueOf(&t)) case aws.JSONValue: - b := []byte(header) - var err error + escaping := protocol.NoEscape if tag.Get("location") == "header" { - b, err = base64.StdEncoding.DecodeString(header) - if err != nil { - return err - } + escaping = protocol.Base64Escape } - - m := aws.JSONValue{} - err = json.Unmarshal(b, &m) + m, err := protocol.DecodeJSONValue(header, escaping) if err != nil { return err } diff --git a/private/protocol/restjson/build_test.go b/private/protocol/restjson/build_test.go index ac4e4d18e6f..100b6ded943 100644 --- a/private/protocol/restjson/build_test.go +++ b/private/protocol/restjson/build_test.go @@ -4101,7 +4101,7 @@ const opInputService21TestCaseOperation1 = "OperationName" // if err == nil { // resp is now filled // fmt.Println(resp) // } -func (c *InputService21ProtocolTest) InputService21TestCaseOperation1Request(input *InputService21TestShapeInputService21TestCaseOperation2Input) (req *request.Request, output *InputService21TestShapeInputService21TestCaseOperation1Output) { +func (c *InputService21ProtocolTest) InputService21TestCaseOperation1Request(input *InputService21TestShapeInputService21TestCaseOperation3Input) (req *request.Request, output *InputService21TestShapeInputService21TestCaseOperation1Output) { op := &request.Operation{ Name: opInputService21TestCaseOperation1, HTTPMethod: "POST", @@ -4109,7 +4109,7 @@ func (c *InputService21ProtocolTest) InputService21TestCaseOperation1Request(inp } if input == nil { - input = &InputService21TestShapeInputService21TestCaseOperation2Input{} + input = &InputService21TestShapeInputService21TestCaseOperation3Input{} } output = &InputService21TestShapeInputService21TestCaseOperation1Output{} @@ -4127,7 +4127,7 @@ func (c *InputService21ProtocolTest) InputService21TestCaseOperation1Request(inp // // See the AWS API reference guide for 's // API operation InputService21TestCaseOperation1 for usage and error information. -func (c *InputService21ProtocolTest) InputService21TestCaseOperation1(input *InputService21TestShapeInputService21TestCaseOperation2Input) (*InputService21TestShapeInputService21TestCaseOperation1Output, error) { +func (c *InputService21ProtocolTest) InputService21TestCaseOperation1(input *InputService21TestShapeInputService21TestCaseOperation3Input) (*InputService21TestShapeInputService21TestCaseOperation1Output, error) { req, out := c.InputService21TestCaseOperation1Request(input) return out, req.Send() } @@ -4141,7 +4141,7 @@ func (c *InputService21ProtocolTest) InputService21TestCaseOperation1(input *Inp // the context is nil a panic will occur. In the future the SDK may create // sub-contexts for http.Requests. See https://golang.org/pkg/context/ // for more information on using Contexts. -func (c *InputService21ProtocolTest) InputService21TestCaseOperation1WithContext(ctx aws.Context, input *InputService21TestShapeInputService21TestCaseOperation2Input, opts ...request.Option) (*InputService21TestShapeInputService21TestCaseOperation1Output, error) { +func (c *InputService21ProtocolTest) InputService21TestCaseOperation1WithContext(ctx aws.Context, input *InputService21TestShapeInputService21TestCaseOperation3Input, opts ...request.Option) (*InputService21TestShapeInputService21TestCaseOperation1Output, error) { req, out := c.InputService21TestCaseOperation1Request(input) req.SetContext(ctx) req.ApplyOptions(opts...) @@ -4172,7 +4172,7 @@ const opInputService21TestCaseOperation2 = "OperationName" // if err == nil { // resp is now filled // fmt.Println(resp) // } -func (c *InputService21ProtocolTest) InputService21TestCaseOperation2Request(input *InputService21TestShapeInputService21TestCaseOperation2Input) (req *request.Request, output *InputService21TestShapeInputService21TestCaseOperation2Output) { +func (c *InputService21ProtocolTest) InputService21TestCaseOperation2Request(input *InputService21TestShapeInputService21TestCaseOperation3Input) (req *request.Request, output *InputService21TestShapeInputService21TestCaseOperation2Output) { op := &request.Operation{ Name: opInputService21TestCaseOperation2, HTTPMethod: "POST", @@ -4180,7 +4180,7 @@ func (c *InputService21ProtocolTest) InputService21TestCaseOperation2Request(inp } if input == nil { - input = &InputService21TestShapeInputService21TestCaseOperation2Input{} + input = &InputService21TestShapeInputService21TestCaseOperation3Input{} } output = &InputService21TestShapeInputService21TestCaseOperation2Output{} @@ -4198,7 +4198,7 @@ func (c *InputService21ProtocolTest) InputService21TestCaseOperation2Request(inp // // See the AWS API reference guide for 's // API operation InputService21TestCaseOperation2 for usage and error information. -func (c *InputService21ProtocolTest) InputService21TestCaseOperation2(input *InputService21TestShapeInputService21TestCaseOperation2Input) (*InputService21TestShapeInputService21TestCaseOperation2Output, error) { +func (c *InputService21ProtocolTest) InputService21TestCaseOperation2(input *InputService21TestShapeInputService21TestCaseOperation3Input) (*InputService21TestShapeInputService21TestCaseOperation2Output, error) { req, out := c.InputService21TestCaseOperation2Request(input) return out, req.Send() } @@ -4212,30 +4212,141 @@ func (c *InputService21ProtocolTest) InputService21TestCaseOperation2(input *Inp // the context is nil a panic will occur. In the future the SDK may create // sub-contexts for http.Requests. See https://golang.org/pkg/context/ // for more information on using Contexts. -func (c *InputService21ProtocolTest) InputService21TestCaseOperation2WithContext(ctx aws.Context, input *InputService21TestShapeInputService21TestCaseOperation2Input, opts ...request.Option) (*InputService21TestShapeInputService21TestCaseOperation2Output, error) { +func (c *InputService21ProtocolTest) InputService21TestCaseOperation2WithContext(ctx aws.Context, input *InputService21TestShapeInputService21TestCaseOperation3Input, opts ...request.Option) (*InputService21TestShapeInputService21TestCaseOperation2Output, error) { req, out := c.InputService21TestCaseOperation2Request(input) req.SetContext(ctx) req.ApplyOptions(opts...) return out, req.Send() } +const opInputService21TestCaseOperation3 = "OperationName" + +// InputService21TestCaseOperation3Request generates a "aws/request.Request" representing the +// client's request for the InputService21TestCaseOperation3 operation. The "output" return +// value will be populated with the request's response once the request complets +// successfuly. +// +// Use "Send" method on the returned Request to send the API call to the service. +// the "output" return value is not valid until after Send returns without error. +// +// See InputService21TestCaseOperation3 for more information on using the InputService21TestCaseOperation3 +// API call, and error handling. +// +// This method is useful when you want to inject custom logic or configuration +// into the SDK's request lifecycle. Such as custom headers, or retry logic. +// +// +// // Example sending a request using the InputService21TestCaseOperation3Request method. +// req, resp := client.InputService21TestCaseOperation3Request(params) +// +// err := req.Send() +// if err == nil { // resp is now filled +// fmt.Println(resp) +// } +func (c *InputService21ProtocolTest) InputService21TestCaseOperation3Request(input *InputService21TestShapeInputService21TestCaseOperation3Input) (req *request.Request, output *InputService21TestShapeInputService21TestCaseOperation3Output) { + op := &request.Operation{ + Name: opInputService21TestCaseOperation3, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &InputService21TestShapeInputService21TestCaseOperation3Input{} + } + + output = &InputService21TestShapeInputService21TestCaseOperation3Output{} + req = c.newRequest(op, input, output) + req.Handlers.Unmarshal.Remove(restjson.UnmarshalHandler) + req.Handlers.Unmarshal.PushBackNamed(protocol.UnmarshalDiscardBodyHandler) + return +} + +// InputService21TestCaseOperation3 API operation for . +// +// Returns awserr.Error for service API and SDK errors. Use runtime type assertions +// with awserr.Error's Code and Message methods to get detailed information about +// the error. +// +// See the AWS API reference guide for 's +// API operation InputService21TestCaseOperation3 for usage and error information. +func (c *InputService21ProtocolTest) InputService21TestCaseOperation3(input *InputService21TestShapeInputService21TestCaseOperation3Input) (*InputService21TestShapeInputService21TestCaseOperation3Output, error) { + req, out := c.InputService21TestCaseOperation3Request(input) + return out, req.Send() +} + +// InputService21TestCaseOperation3WithContext is the same as InputService21TestCaseOperation3 with the addition of +// the ability to pass a context and additional request options. +// +// See InputService21TestCaseOperation3 for details on how to use this API operation. +// +// The context must be non-nil and will be used for request cancellation. If +// the context is nil a panic will occur. In the future the SDK may create +// sub-contexts for http.Requests. See https://golang.org/pkg/context/ +// for more information on using Contexts. +func (c *InputService21ProtocolTest) InputService21TestCaseOperation3WithContext(ctx aws.Context, input *InputService21TestShapeInputService21TestCaseOperation3Input, opts ...request.Option) (*InputService21TestShapeInputService21TestCaseOperation3Output, error) { + req, out := c.InputService21TestCaseOperation3Request(input) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return out, req.Send() +} + +type InputService21TestShapeBodyStructure struct { + _ struct{} `type:"structure"` + + BodyField aws.JSONValue `type:"jsonvalue"` + + BodyListField []aws.JSONValue `type:"list"` +} + +// SetBodyField sets the BodyField field's value. +func (s *InputService21TestShapeBodyStructure) SetBodyField(v aws.JSONValue) *InputService21TestShapeBodyStructure { + s.BodyField = v + return s +} + +// SetBodyListField sets the BodyListField field's value. +func (s *InputService21TestShapeBodyStructure) SetBodyListField(v []aws.JSONValue) *InputService21TestShapeBodyStructure { + s.BodyListField = v + return s +} + type InputService21TestShapeInputService21TestCaseOperation1Output struct { _ struct{} `type:"structure"` } -type InputService21TestShapeInputService21TestCaseOperation2Input struct { +type InputService21TestShapeInputService21TestCaseOperation2Output struct { _ struct{} `type:"structure"` +} + +type InputService21TestShapeInputService21TestCaseOperation3Input struct { + _ struct{} `type:"structure" payload:"Body"` + + Body *InputService21TestShapeBodyStructure `type:"structure"` + + HeaderField aws.JSONValue `location:"header" locationName:"X-Amz-Foo" type:"jsonvalue"` + + QueryField aws.JSONValue `location:"querystring" locationName:"Bar" type:"jsonvalue"` +} - Attr aws.JSONValue `location:"header" locationName:"X-Amz-Foo" type:"jsonvalue"` +// SetBody sets the Body field's value. +func (s *InputService21TestShapeInputService21TestCaseOperation3Input) SetBody(v *InputService21TestShapeBodyStructure) *InputService21TestShapeInputService21TestCaseOperation3Input { + s.Body = v + return s } -// SetAttr sets the Attr field's value. -func (s *InputService21TestShapeInputService21TestCaseOperation2Input) SetAttr(v aws.JSONValue) *InputService21TestShapeInputService21TestCaseOperation2Input { - s.Attr = v +// SetHeaderField sets the HeaderField field's value. +func (s *InputService21TestShapeInputService21TestCaseOperation3Input) SetHeaderField(v aws.JSONValue) *InputService21TestShapeInputService21TestCaseOperation3Input { + s.HeaderField = v return s } -type InputService21TestShapeInputService21TestCaseOperation2Output struct { +// SetQueryField sets the QueryField field's value. +func (s *InputService21TestShapeInputService21TestCaseOperation3Input) SetQueryField(v aws.JSONValue) *InputService21TestShapeInputService21TestCaseOperation3Input { + s.QueryField = v + return s +} + +type InputService21TestShapeInputService21TestCaseOperation3Output struct { _ struct{} `type:"structure"` } @@ -5099,8 +5210,19 @@ func TestInputService20ProtocolTestIdempotencyTokenAutoFillCase2(t *testing.T) { func TestInputService21ProtocolTestJSONValueTraitCase1(t *testing.T) { svc := NewInputService21ProtocolTest(unit.Session, &aws.Config{Endpoint: aws.String("https://test")}) - input := &InputService21TestShapeInputService21TestCaseOperation2Input{} - input.Attr = aws.JSONValue{"Foo": "Bar"} + input := &InputService21TestShapeInputService21TestCaseOperation3Input{ + Body: &InputService21TestShapeBodyStructure{ + BodyField: func() aws.JSONValue { + var m aws.JSONValue + if err := json.Unmarshal([]byte("{\"Foo\":\"Bar\"}"), &m); err != nil { + panic("failed to unmarshal JSONValue, " + err.Error()) + } + return m + }(), + }, + } + input.HeaderField = aws.JSONValue{"Foo": "Bar"} + input.QueryField = aws.JSONValue{"Foo": "Bar"} req, _ := svc.InputService21TestCaseOperation1Request(input) r := req.HTTPRequest @@ -5110,8 +5232,15 @@ func TestInputService21ProtocolTestJSONValueTraitCase1(t *testing.T) { t.Errorf("expect no error, got %v", req.Error) } + // assert body + if r.Body == nil { + t.Errorf("expect body not to be nil") + } + body, _ := ioutil.ReadAll(r.Body) + awstesting.AssertJSON(t, `{"BodyField":"{\"Foo\":\"Bar\"}"}`, util.Trim(string(body))) + // assert URL - awstesting.AssertURL(t, "https://test/", r.URL.String()) + awstesting.AssertURL(t, "https://test/?Bar=%7B%22Foo%22%3A%22Bar%22%7D", r.URL.String()) // assert headers if e, a := "eyJGb28iOiJCYXIifQ==", r.Header.Get("X-Amz-Foo"); e != a { @@ -5122,7 +5251,19 @@ func TestInputService21ProtocolTestJSONValueTraitCase1(t *testing.T) { func TestInputService21ProtocolTestJSONValueTraitCase2(t *testing.T) { svc := NewInputService21ProtocolTest(unit.Session, &aws.Config{Endpoint: aws.String("https://test")}) - input := &InputService21TestShapeInputService21TestCaseOperation2Input{} + input := &InputService21TestShapeInputService21TestCaseOperation3Input{ + Body: &InputService21TestShapeBodyStructure{ + BodyListField: []aws.JSONValue{ + func() aws.JSONValue { + var m aws.JSONValue + if err := json.Unmarshal([]byte("{\"Foo\":\"Bar\"}"), &m); err != nil { + panic("failed to unmarshal JSONValue, " + err.Error()) + } + return m + }(), + }, + }, + } req, _ := svc.InputService21TestCaseOperation2Request(input) r := req.HTTPRequest @@ -5132,6 +5273,32 @@ func TestInputService21ProtocolTestJSONValueTraitCase2(t *testing.T) { t.Errorf("expect no error, got %v", req.Error) } + // assert body + if r.Body == nil { + t.Errorf("expect body not to be nil") + } + body, _ := ioutil.ReadAll(r.Body) + awstesting.AssertJSON(t, `{"BodyListField":["{\"Foo\":\"Bar\"}"]}`, util.Trim(string(body))) + + // assert URL + awstesting.AssertURL(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService21ProtocolTestJSONValueTraitCase3(t *testing.T) { + svc := NewInputService21ProtocolTest(unit.Session, &aws.Config{Endpoint: aws.String("https://test")}) + input := &InputService21TestShapeInputService21TestCaseOperation3Input{} + req, _ := svc.InputService21TestCaseOperation3Request(input) + r := req.HTTPRequest + + // build request + restjson.Build(req) + if req.Error != nil { + t.Errorf("expect no error, got %v", req.Error) + } + // assert URL awstesting.AssertURL(t, "https://test/", r.URL.String()) diff --git a/private/protocol/restjson/unmarshal_test.go b/private/protocol/restjson/unmarshal_test.go index 134a1ae3ad9..a65ee73c5a2 100644 --- a/private/protocol/restjson/unmarshal_test.go +++ b/private/protocol/restjson/unmarshal_test.go @@ -1862,7 +1862,7 @@ const opOutputService12TestCaseOperation1 = "OperationName" // if err == nil { // resp is now filled // fmt.Println(resp) // } -func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1Request(input *OutputService12TestShapeOutputService12TestCaseOperation1Input) (req *request.Request, output *OutputService12TestShapeOutputService12TestCaseOperation1Output) { +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1Request(input *OutputService12TestShapeOutputService12TestCaseOperation1Input) (req *request.Request, output *OutputService12TestShapeOutputService12TestCaseOperation3Output) { op := &request.Operation{ Name: opOutputService12TestCaseOperation1, HTTPPath: "/", @@ -1872,7 +1872,7 @@ func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1Request(i input = &OutputService12TestShapeOutputService12TestCaseOperation1Input{} } - output = &OutputService12TestShapeOutputService12TestCaseOperation1Output{} + output = &OutputService12TestShapeOutputService12TestCaseOperation3Output{} req = c.newRequest(op, input, output) return } @@ -1885,7 +1885,7 @@ func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1Request(i // // See the AWS API reference guide for 's // API operation OutputService12TestCaseOperation1 for usage and error information. -func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1(input *OutputService12TestShapeOutputService12TestCaseOperation1Input) (*OutputService12TestShapeOutputService12TestCaseOperation1Output, error) { +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1(input *OutputService12TestShapeOutputService12TestCaseOperation1Input) (*OutputService12TestShapeOutputService12TestCaseOperation3Output, error) { req, out := c.OutputService12TestCaseOperation1Request(input) return out, req.Send() } @@ -1899,26 +1899,186 @@ func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1(input *O // the context is nil a panic will occur. In the future the SDK may create // sub-contexts for http.Requests. See https://golang.org/pkg/context/ // for more information on using Contexts. -func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1WithContext(ctx aws.Context, input *OutputService12TestShapeOutputService12TestCaseOperation1Input, opts ...request.Option) (*OutputService12TestShapeOutputService12TestCaseOperation1Output, error) { +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1WithContext(ctx aws.Context, input *OutputService12TestShapeOutputService12TestCaseOperation1Input, opts ...request.Option) (*OutputService12TestShapeOutputService12TestCaseOperation3Output, error) { req, out := c.OutputService12TestCaseOperation1Request(input) req.SetContext(ctx) req.ApplyOptions(opts...) return out, req.Send() } +const opOutputService12TestCaseOperation2 = "OperationName" + +// OutputService12TestCaseOperation2Request generates a "aws/request.Request" representing the +// client's request for the OutputService12TestCaseOperation2 operation. The "output" return +// value will be populated with the request's response once the request complets +// successfuly. +// +// Use "Send" method on the returned Request to send the API call to the service. +// the "output" return value is not valid until after Send returns without error. +// +// See OutputService12TestCaseOperation2 for more information on using the OutputService12TestCaseOperation2 +// API call, and error handling. +// +// This method is useful when you want to inject custom logic or configuration +// into the SDK's request lifecycle. Such as custom headers, or retry logic. +// +// +// // Example sending a request using the OutputService12TestCaseOperation2Request method. +// req, resp := client.OutputService12TestCaseOperation2Request(params) +// +// err := req.Send() +// if err == nil { // resp is now filled +// fmt.Println(resp) +// } +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation2Request(input *OutputService12TestShapeOutputService12TestCaseOperation2Input) (req *request.Request, output *OutputService12TestShapeOutputService12TestCaseOperation3Output) { + op := &request.Operation{ + Name: opOutputService12TestCaseOperation2, + HTTPPath: "/", + } + + if input == nil { + input = &OutputService12TestShapeOutputService12TestCaseOperation2Input{} + } + + output = &OutputService12TestShapeOutputService12TestCaseOperation3Output{} + req = c.newRequest(op, input, output) + return +} + +// OutputService12TestCaseOperation2 API operation for . +// +// Returns awserr.Error for service API and SDK errors. Use runtime type assertions +// with awserr.Error's Code and Message methods to get detailed information about +// the error. +// +// See the AWS API reference guide for 's +// API operation OutputService12TestCaseOperation2 for usage and error information. +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation2(input *OutputService12TestShapeOutputService12TestCaseOperation2Input) (*OutputService12TestShapeOutputService12TestCaseOperation3Output, error) { + req, out := c.OutputService12TestCaseOperation2Request(input) + return out, req.Send() +} + +// OutputService12TestCaseOperation2WithContext is the same as OutputService12TestCaseOperation2 with the addition of +// the ability to pass a context and additional request options. +// +// See OutputService12TestCaseOperation2 for details on how to use this API operation. +// +// The context must be non-nil and will be used for request cancellation. If +// the context is nil a panic will occur. In the future the SDK may create +// sub-contexts for http.Requests. See https://golang.org/pkg/context/ +// for more information on using Contexts. +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation2WithContext(ctx aws.Context, input *OutputService12TestShapeOutputService12TestCaseOperation2Input, opts ...request.Option) (*OutputService12TestShapeOutputService12TestCaseOperation3Output, error) { + req, out := c.OutputService12TestCaseOperation2Request(input) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return out, req.Send() +} + +const opOutputService12TestCaseOperation3 = "OperationName" + +// OutputService12TestCaseOperation3Request generates a "aws/request.Request" representing the +// client's request for the OutputService12TestCaseOperation3 operation. The "output" return +// value will be populated with the request's response once the request complets +// successfuly. +// +// Use "Send" method on the returned Request to send the API call to the service. +// the "output" return value is not valid until after Send returns without error. +// +// See OutputService12TestCaseOperation3 for more information on using the OutputService12TestCaseOperation3 +// API call, and error handling. +// +// This method is useful when you want to inject custom logic or configuration +// into the SDK's request lifecycle. Such as custom headers, or retry logic. +// +// +// // Example sending a request using the OutputService12TestCaseOperation3Request method. +// req, resp := client.OutputService12TestCaseOperation3Request(params) +// +// err := req.Send() +// if err == nil { // resp is now filled +// fmt.Println(resp) +// } +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation3Request(input *OutputService12TestShapeOutputService12TestCaseOperation3Input) (req *request.Request, output *OutputService12TestShapeOutputService12TestCaseOperation3Output) { + op := &request.Operation{ + Name: opOutputService12TestCaseOperation3, + HTTPPath: "/", + } + + if input == nil { + input = &OutputService12TestShapeOutputService12TestCaseOperation3Input{} + } + + output = &OutputService12TestShapeOutputService12TestCaseOperation3Output{} + req = c.newRequest(op, input, output) + return +} + +// OutputService12TestCaseOperation3 API operation for . +// +// Returns awserr.Error for service API and SDK errors. Use runtime type assertions +// with awserr.Error's Code and Message methods to get detailed information about +// the error. +// +// See the AWS API reference guide for 's +// API operation OutputService12TestCaseOperation3 for usage and error information. +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation3(input *OutputService12TestShapeOutputService12TestCaseOperation3Input) (*OutputService12TestShapeOutputService12TestCaseOperation3Output, error) { + req, out := c.OutputService12TestCaseOperation3Request(input) + return out, req.Send() +} + +// OutputService12TestCaseOperation3WithContext is the same as OutputService12TestCaseOperation3 with the addition of +// the ability to pass a context and additional request options. +// +// See OutputService12TestCaseOperation3 for details on how to use this API operation. +// +// The context must be non-nil and will be used for request cancellation. If +// the context is nil a panic will occur. In the future the SDK may create +// sub-contexts for http.Requests. See https://golang.org/pkg/context/ +// for more information on using Contexts. +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation3WithContext(ctx aws.Context, input *OutputService12TestShapeOutputService12TestCaseOperation3Input, opts ...request.Option) (*OutputService12TestShapeOutputService12TestCaseOperation3Output, error) { + req, out := c.OutputService12TestCaseOperation3Request(input) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return out, req.Send() +} + type OutputService12TestShapeOutputService12TestCaseOperation1Input struct { _ struct{} `type:"structure"` } -type OutputService12TestShapeOutputService12TestCaseOperation1Output struct { +type OutputService12TestShapeOutputService12TestCaseOperation2Input struct { + _ struct{} `type:"structure"` +} + +type OutputService12TestShapeOutputService12TestCaseOperation3Input struct { + _ struct{} `type:"structure"` +} + +type OutputService12TestShapeOutputService12TestCaseOperation3Output struct { _ struct{} `type:"structure"` - Attr aws.JSONValue `location:"header" locationName:"X-Amz-Foo" type:"jsonvalue"` + BodyField aws.JSONValue `type:"jsonvalue"` + + BodyListField []aws.JSONValue `type:"list"` + + HeaderField aws.JSONValue `location:"header" locationName:"X-Amz-Foo" type:"jsonvalue"` } -// SetAttr sets the Attr field's value. -func (s *OutputService12TestShapeOutputService12TestCaseOperation1Output) SetAttr(v aws.JSONValue) *OutputService12TestShapeOutputService12TestCaseOperation1Output { - s.Attr = v +// SetBodyField sets the BodyField field's value. +func (s *OutputService12TestShapeOutputService12TestCaseOperation3Output) SetBodyField(v aws.JSONValue) *OutputService12TestShapeOutputService12TestCaseOperation3Output { + s.BodyField = v + return s +} + +// SetBodyListField sets the BodyListField field's value. +func (s *OutputService12TestShapeOutputService12TestCaseOperation3Output) SetBodyListField(v []aws.JSONValue) *OutputService12TestShapeOutputService12TestCaseOperation3Output { + s.BodyListField = v + return s +} + +// SetHeaderField sets the HeaderField field's value. +func (s *OutputService12TestShapeOutputService12TestCaseOperation3Output) SetHeaderField(v aws.JSONValue) *OutputService12TestShapeOutputService12TestCaseOperation3Output { + s.HeaderField = v return s } @@ -2287,7 +2447,7 @@ func TestOutputService11ProtocolTestStreamingPayloadCase1(t *testing.T) { func TestOutputService12ProtocolTestJSONValueTraitCase1(t *testing.T) { svc := NewOutputService12ProtocolTest(unit.Session, &aws.Config{Endpoint: aws.String("https://test")}) - buf := bytes.NewReader([]byte("")) + buf := bytes.NewReader([]byte("{\"BodyField\":\"{\\\"Foo\\\":\\\"Bar\\\"}\"}")) req, out := svc.OutputService12TestCaseOperation1Request(nil) req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} @@ -2305,5 +2465,54 @@ func TestOutputService12ProtocolTestJSONValueTraitCase1(t *testing.T) { if out == nil { t.Errorf("expect not to be nil") } - reflect.DeepEqual(out.Attr, map[string]interface{}{"Foo": "Bar"}) + reflect.DeepEqual(out.BodyField, map[string]interface{}{"Foo": "Bar"}) + reflect.DeepEqual(out.HeaderField, map[string]interface{}{"Foo": "Bar"}) + +} + +func TestOutputService12ProtocolTestJSONValueTraitCase2(t *testing.T) { + svc := NewOutputService12ProtocolTest(unit.Session, &aws.Config{Endpoint: aws.String("https://test")}) + + buf := bytes.NewReader([]byte("{\"BodyListField\":[\"{\\\"Foo\\\":\\\"Bar\\\"}\"]}")) + req, out := svc.OutputService12TestCaseOperation2Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + restjson.UnmarshalMeta(req) + restjson.Unmarshal(req) + if req.Error != nil { + t.Errorf("expect not error, got %v", req.Error) + } + + // assert response + if out == nil { + t.Errorf("expect not to be nil") + } + reflect.DeepEqual(out.BodyListField[0], map[string]interface{}{"Foo": "Bar"}) + +} + +func TestOutputService12ProtocolTestJSONValueTraitCase3(t *testing.T) { + svc := NewOutputService12ProtocolTest(unit.Session, &aws.Config{Endpoint: aws.String("https://test")}) + + buf := bytes.NewReader([]byte("")) + req, out := svc.OutputService12TestCaseOperation3Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + restjson.UnmarshalMeta(req) + restjson.Unmarshal(req) + if req.Error != nil { + t.Errorf("expect not error, got %v", req.Error) + } + + // assert response + if out == nil { + t.Errorf("expect not to be nil") + } + }