diff --git a/smithy-aws-protocol-tests/model/restJson1/defaults.smithy b/smithy-aws-protocol-tests/model/restJson1/defaults.smithy new file mode 100644 index 00000000000..f1436329350 --- /dev/null +++ b/smithy-aws-protocol-tests/model/restJson1/defaults.smithy @@ -0,0 +1,460 @@ +$version: "2.0" + +namespace aws.protocoltests.restjson + +use aws.protocols#restJson1 +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +apply OperationWithDefaults @httpRequestTests([ + { + id: "RestJsonClientPopulatesDefaultValuesInInput" + documentation: "Client populates default values in input." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithDefaults" + headers: {"Content-Type": "application/json"} + body: """ + { + "defaults": { + "defaultString": "hi", + "defaultBoolean": true, + "defaultList": [], + "defaultDocumentMap": {}, + "defaultDocumentString": "hi", + "defaultDocumentBoolean": true, + "defaultDocumentList": [], + "defaultTimestamp": 0, + "defaultBlob": "YWJj", + "defaultByte": 1, + "defaultShort": 1, + "defaultInteger": 10, + "defaultLong": 100, + "defaultFloat": 1.0, + "defaultDouble": 1.0, + "defaultMap": {}, + "defaultEnum": "FOO", + "defaultIntEnum": 1, + "emptyString": "", + "falseBoolean": false, + "emptyBlob": "", + "zeroByte": 0, + "zeroShort": 0, + "zeroInteger": 0, + "zeroLong": 0, + "zeroFloat": 0.0, + "zeroDouble": 0.0 + } + }""" + params: { + defaults: {} + } + } + { + id: "RestJsonClientSkipsTopLevelDefaultValuesInInput" + documentation: "Client skips top level default values in input." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithDefaults" + headers: {"Content-Type": "application/json"} + body: """ + { + }""" + params: { + } + } + { + id: "RestJsonClientUsesExplicitlyProvidedMemberValuesOverDefaults" + documentation: "Client uses explicitly provided member values over defaults" + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithDefaults" + headers: {"Content-Type": "application/json"} + params: { + defaults: { + defaultString: "bye" + defaultBoolean: true + defaultList: ["a"] + defaultDocumentMap: {name: "Jack"} + defaultDocumentString: "bye" + defaultDocumentBoolean: true + defaultDocumentList: ["b"] + defaultNullDocument: "notNull" + defaultTimestamp: 1 + defaultBlob: "hi" + defaultByte: 2 + defaultShort: 2 + defaultInteger: 20 + defaultLong: 200 + defaultFloat: 2.0 + defaultDouble: 2.0 + defaultMap: {name: "Jack"} + defaultEnum: "BAR" + defaultIntEnum: 2 + emptyString: "foo" + falseBoolean: true + emptyBlob: "hi" + zeroByte: 1 + zeroShort: 1 + zeroInteger: 1 + zeroLong: 1 + zeroFloat: 1.0 + zeroDouble: 1.0 + } + } + body: """ + { + "defaults": { + "defaultString": "bye", + "defaultBoolean": true, + "defaultList": ["a"], + "defaultDocumentMap": {"name": "Jack"}, + "defaultDocumentString": "bye", + "defaultDocumentBoolean": true, + "defaultDocumentList": ["b"], + "defaultNullDocument": "notNull", + "defaultTimestamp": 1, + "defaultBlob": "aGk=", + "defaultByte": 2, + "defaultShort": 2, + "defaultInteger": 20, + "defaultLong": 200, + "defaultFloat": 2.0, + "defaultDouble": 2.0, + "defaultMap": {"name": "Jack"}, + "defaultEnum": "BAR", + "defaultIntEnum": 2, + "emptyString": "foo", + "falseBoolean": true, + "emptyBlob": "aGk=", + "zeroByte": 1, + "zeroShort": 1, + "zeroInteger": 1, + "zeroLong": 1, + "zeroFloat": 1.0, + "zeroDouble": 1.0 + } + }""" + } + { + id: "RestJsonServerPopulatesDefaultsWhenMissingInRequestBody" + documentation: "Server populates default values when missing in request body." + appliesTo: "server" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithDefaults" + headers: {"Content-Type": "application/json"} + body: """ + { + "defaults": {} + }""" + params: { + defaults: { + defaultString: "hi" + defaultBoolean: true + defaultList: [] + defaultDocumentMap: {} + defaultDocumentString: "hi" + defaultDocumentBoolean: true + defaultDocumentList: [] + defaultTimestamp: 0 + defaultBlob: "abc" + defaultByte: 1 + defaultShort: 1 + defaultInteger: 10 + defaultLong: 100 + defaultFloat: 1.0 + defaultDouble: 1.0 + defaultMap: {} + defaultEnum: "FOO" + defaultIntEnum: 1 + emptyString: "" + falseBoolean: false + emptyBlob: "" + zeroByte: 0 + zeroShort: 0 + zeroInteger: 0 + zeroLong: 0 + zeroFloat: 0.0 + zeroDouble: 0.0 + }, + topLevelDefault: "hi" + otherTopLevelDefault: 0 + } + } + { + id: "RestJsonClientUsesExplicitlyProvidedValuesInTopLevel" + documentation: "Any time a value is provided for a member in the top level of input, it is used, regardless of if its the default." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithDefaults" + headers: {"Content-Type": "application/json"} + body: """ + { + "topLevelDefault": "hi", + "otherTopLevelDefault": 0 + }""" + params: { + topLevelDefault: "hi" + otherTopLevelDefault: 0 + } + } + { + id: "RestJsonClientIgnoresNonTopLevelDefaultsOnMembersWithClientOptional" + documentation: "Typically, non top-level members would have defaults filled in, but if they have the clientOptional trait, the defaults should be ignored." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithDefaults" + headers: {"Content-Type": "application/json"} + body: """ + { + "clientOptionalDefaults": {} + }""" + params: { + clientOptionalDefaults: {} + } + } +]) + +apply OperationWithDefaults @httpResponseTests([ + { + id: "RestJsonClientPopulatesDefaultsValuesWhenMissingInResponse" + documentation: "Client populates default values when missing in response." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + code: 200 + bodyMediaType: "application/json" + headers: {"Content-Type": "application/json"} + body: "{}" + params: { + defaultString: "hi" + defaultBoolean: true + defaultList: [] + defaultDocumentMap: {} + defaultDocumentString: "hi" + defaultDocumentBoolean: true + defaultDocumentList: [] + defaultTimestamp: 0 + defaultBlob: "abc" + defaultByte: 1 + defaultShort: 1 + defaultInteger: 10 + defaultLong: 100 + defaultFloat: 1.0 + defaultDouble: 1.0 + defaultMap: {} + defaultEnum: "FOO" + defaultIntEnum: 1 + emptyString: "" + falseBoolean: false + emptyBlob: "" + zeroByte: 0 + zeroShort: 0 + zeroInteger: 0 + zeroLong: 0 + zeroFloat: 0.0 + zeroDouble: 0.0 + } + } + { + id: "RestJsonClientIgnoresDefaultValuesIfMemberValuesArePresentInResponse" + documentation: "Client ignores default values if member values are present in the response." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + code: 200 + bodyMediaType: "application/json" + headers: {"Content-Type": "application/json"} + body: """ + { + "defaultString": "bye", + "defaultBoolean": false, + "defaultList": ["a"], + "defaultDocumentMap": {"name": "Jack"}, + "defaultDocumentString": "bye", + "defaultDocumentBoolean": false, + "defaultDocumentList": ["b"], + "defaultNullDocument": "notNull", + "defaultTimestamp": 2, + "defaultBlob": "aGk=", + "defaultByte": 2, + "defaultShort": 2, + "defaultInteger": 20, + "defaultLong": 200, + "defaultFloat": 2.0, + "defaultDouble": 2.0, + "defaultMap": {"name": "Jack"}, + "defaultEnum": "BAR", + "defaultIntEnum": 2, + "emptyString": "foo", + "falseBoolean": true, + "emptyBlob": "aGk=", + "zeroByte": 1, + "zeroShort": 1, + "zeroInteger": 1, + "zeroLong": 1, + "zeroFloat": 1.0, + "zeroDouble": 1.0 + }""" + params: { + defaultString: "bye" + defaultBoolean: false + defaultList: ["a"] + defaultDocumentMap: {name: "Jack"} + defaultDocumentString: "bye" + defaultDocumentBoolean: false + defaultDocumentList: ["b"] + defaultNullDocument: "notNull" + defaultTimestamp: 1 + defaultBlob: "hi" + defaultByte: 2 + defaultShort: 2 + defaultInteger: 20 + defaultLong: 200 + defaultFloat: 2.0 + defaultDouble: 2.0 + defaultMap: {name: "Jack"} + defaultEnum: "BAR" + defaultIntEnum: 2 + emptyString: "foo" + falseBoolean: true + emptyBlob: "hi" + zeroByte: 1 + zeroShort: 1 + zeroInteger: 1 + zeroLong: 1 + zeroFloat: 1.0 + zeroDouble: 1.0 + } + } + { + id: "RestJsonServerPopulatesDefaultsInResponseWhenMissingInParams" + documentation: "Server populates default values in response when missing in params." + appliesTo: "server" + tags: ["defaults"] + protocol: restJson1 + code: 200 + bodyMediaType: "application/json" + headers: {"Content-Type": "application/json"} + body: """ + { + "defaultString": "hi", + "defaultBoolean": true, + "defaultList": [], + "defaultDocumentMap": {}, + "defaultDocumentString": "hi", + "defaultDocumentBoolean": true, + "defaultDocumentList": [], + "defaultTimestamp": 0, + "defaultBlob": "YWJj", + "defaultByte": 1, + "defaultShort": 1, + "defaultInteger": 10, + "defaultLong": 100, + "defaultFloat": 1.0, + "defaultDouble": 1.0, + "defaultMap": {}, + "defaultEnum": "FOO", + "defaultIntEnum": 1, + "emptyString": "", + "falseBoolean": false, + "emptyBlob": "", + "zeroByte": 0, + "zeroShort": 0, + "zeroInteger": 0, + "zeroLong": 0, + "zeroFloat": 0.0, + "zeroDouble": 0.0 + }""" + params: {} + } +]) + +@http(uri: "/OperationWithDefaults", method: "POST") +operation OperationWithDefaults { + input := { + defaults: Defaults + clientOptionalDefaults: ClientOptionalDefaults + topLevelDefault: String = "hi" // Client should ignore default values in input shape + otherTopLevelDefault: Integer = 0 + } + + output := with [DefaultsMixin] {} +} + +structure Defaults with [DefaultsMixin] {} + +structure ClientOptionalDefaults { + @clientOptional + member: Integer = 0 +} + +@mixin +structure DefaultsMixin { + defaultString: String = "hi" + defaultBoolean: Boolean = true + defaultList: TestStringList = [] + defaultDocumentMap: Document = {} + defaultDocumentString: Document = "hi" + defaultDocumentBoolean: Document = true + defaultDocumentList: Document = [] + defaultNullDocument: Document = null + defaultTimestamp: Timestamp = 0 + defaultBlob: Blob = "abc" + defaultByte: Byte = 1 + defaultShort: Short = 1 + defaultInteger: Integer = 10 + defaultLong: Long = 100 + defaultFloat: Float = 1.0 + defaultDouble: Double = 1.0 + defaultMap: TestStringMap = {} + defaultEnum: TestEnum = "FOO" + defaultIntEnum: TestIntEnum = 1 + emptyString: String = "" + falseBoolean: Boolean = false + emptyBlob: Blob = "" + zeroByte: Byte = 0 + zeroShort: Short = 0 + zeroInteger: Integer = 0 + zeroLong: Long = 0 + zeroFloat: Float = 0.0 + zeroDouble: Double = 0.0 +} + +list TestStringList { + member: String +} + +map TestStringMap { + key: String + value: String +} + +enum TestEnum { + FOO + BAR + BAZ +} + +intEnum TestIntEnum { + ONE = 1 + TWO = 2 +} \ No newline at end of file diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy index e5ae6f979ae..10c35886db2 100644 --- a/smithy-aws-protocol-tests/model/restJson1/main.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy @@ -152,5 +152,9 @@ service RestJson { // requestCompression trait tests PutWithContentEncoding + + // defaults + OperationWithDefaults + OperationWithNestedStructure ] } diff --git a/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy b/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy new file mode 100644 index 00000000000..bba92f85e2d --- /dev/null +++ b/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy @@ -0,0 +1,409 @@ +$version: "2.0" + +namespace aws.protocoltests.restjson + +use aws.protocols#restJson1 +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +apply OperationWithNestedStructure @httpRequestTests([ + { + id: "RestJsonClientPopulatesNestedDefaultValuesWhenMissing" + documentation: "Client populates nested default values when missing." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithNestedStructure" + headers: {"Content-Type": "application/json"} + body: """ + { + "topLevel": { + "dialog": { + "language": "en", + "greeting": "hi" + }, + "dialogList": [ + { + "greeting": "hi" + }, + { + "greeting": "hi", + "farewell": { + "phrase": "bye" + } + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + "greeting": "hi" + }, + "partialEmptyDialog": { + "language": "en", + "greeting": "hi", + "farewell": { + "phrase": "bye" + } + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + } + }""" + params: { + "topLevel": { + "dialog": { + "language": "en" + }, + "dialogList": [ + { + }, + { + "farewell": {} + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + }, + "partialEmptyDialog": { + "language": "en", + "farewell": {} + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + } + } + } + { + id: "RestJsonServerPopulatesNestedDefaultsWhenMissingInRequestBody" + documentation: "Server populates nested default values when missing in request body." + appliesTo: "server" + tags: ["defaults"] + protocol: restJson1 + method: "POST" + bodyMediaType: "application/json" + uri: "/OperationWithNestedStructure" + headers: {"Content-Type": "application/json"} + body: """ + { + "topLevel": { + "dialog": { + "language": "en" + }, + "dialogList": [ + { + }, + { + "farewell": {} + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + }, + "partialEmptyDialog": { + "language": "en", + "farewell": {} + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + } + }""" + params: { + "topLevel": { + "dialog": { + "language": "en" + "greeting": "hi", + } + "dialogList": [ + { + "greeting": "hi", + }, + { + "greeting": "hi", + "farewell": { + "phrase": "bye", + } + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + "greeting": "hi", + }, + "partialEmptyDialog": { + "language": "en", + "greeting": "hi", + "farewell": { + "phrase": "bye", + } + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + } + } + } +]) + +apply OperationWithNestedStructure @httpResponseTests([ + { + id: "RestJsonClientPopulatesNestedDefaultsWhenMissingInResponseBody" + documentation: "Client populates nested default values when missing in response body." + appliesTo: "client" + tags: ["defaults"] + protocol: restJson1 + code: 200 + bodyMediaType: "application/json" + headers: {"Content-Type": "application/json"} + body: """ + { + "dialog": { + "language": "en" + }, + "dialogList": [ + { + }, + { + "farewell": {} + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + }, + "partialEmptyDialog": { + "language": "en", + "farewell": {} + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + }""" + params: { + "dialog": { + "language": "en" + "greeting": "hi", + } + "dialogList": [ + { + "greeting": "hi", + }, + { + "greeting": "hi", + "farewell": { + "phrase": "bye", + } + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + "greeting": "hi", + }, + "partialEmptyDialog": { + "language": "en", + "greeting": "hi", + "farewell": { + "phrase": "bye", + } + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + } + } + { + id: "RestJsonServerPopulatesNestedDefaultValuesWhenMissingInInResponseParams" + documentation: "Server populates nested default values when missing in response params." + appliesTo: "server" + tags: ["defaults"] + protocol: restJson1 + code: 200 + bodyMediaType: "application/json" + headers: {"Content-Type": "application/json"} + body: """ + { + "dialog": { + "language": "en", + "greeting": "hi" + }, + "dialogList": [ + { + "greeting": "hi" + }, + { + "greeting": "hi", + "farewell": { + "phrase": "bye" + } + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + "greeting": "hi" + }, + "partialEmptyDialog": { + "language": "en", + "greeting": "hi", + "farewell": { + "phrase": "bye" + } + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + }""" + params: { + "dialog": { + "language": "en" + }, + "dialogList": [ + { + }, + { + "farewell": {} + }, + { + "language": "it", + "greeting": "ciao", + "farewell": { + "phrase": "arrivederci" + } + } + ], + "dialogMap": { + "emptyDialog": { + }, + "partialEmptyDialog": { + "language": "en", + "farewell": {} + }, + "nonEmptyDialog": { + "greeting": "konnichiwa", + "farewell": { + "phrase": "sayonara" + } + } + } + } + } +]) + +@http(uri: "/OperationWithNestedStructure", method: "POST") +operation OperationWithNestedStructure { + input := { + @required + topLevel: TopLevel + } + + output := with [NestedDefaultsMixin] { + } +} + +structure TopLevel with [NestedDefaultsMixin] { + +} + +@mixin +structure NestedDefaultsMixin { + @required + dialog: Dialog + dialogList: DialogList = [] + dialogMap: DialogMap = {} +} + +structure Dialog { + language: String + greeting: String = "hi" + farewell: Farewell +} + +structure Farewell { + phrase: String = "bye" +} + +list DialogList { + member: Dialog +} + +map DialogMap { + key: String + value: Dialog +} \ No newline at end of file