diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4f6029fcb7..1936479806a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: ci on: push: - branches: [main] + branches: [main, smithy-rpc-v2] pull_request: - branches: [main] + branches: [main, smithy-rpc-v2] jobs: build: diff --git a/.gitignore b/.gitignore index 4b608569e63..ed1c10be16a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,5 +35,8 @@ build/ */*/out/ /wrapper +# jdt-ls +.metadata + # Smithy .smithy.lsp.log diff --git a/settings.gradle b/settings.gradle index 89a47440a83..d11fa606109 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,3 +33,4 @@ include ":smithy-rules-engine" include ":smithy-smoke-test-traits" include ":smithy-syntax" include ":smithy-aws-endpoints" +include ":smithy-protocol-traits" diff --git a/smithy-aws-protocol-tests/build.gradle b/smithy-aws-protocol-tests/build.gradle index a4110ceb9bd..dd7f90149fa 100644 --- a/smithy-aws-protocol-tests/build.gradle +++ b/smithy-aws-protocol-tests/build.gradle @@ -27,6 +27,7 @@ ext { dependencies { implementation project(path: ":smithy-cli", configuration: "shadow") implementation project(":smithy-protocol-test-traits") + implementation project(":smithy-protocol-traits") implementation project(":smithy-aws-traits") api project(":smithy-validation-model") } diff --git a/smithy-aws-protocol-tests/model/rpcV2/cbor-lists.smithy b/smithy-aws-protocol-tests/model/rpcV2/cbor-lists.smithy new file mode 100644 index 00000000000..7fe2289db09 --- /dev/null +++ b/smithy-aws-protocol-tests/model/rpcV2/cbor-lists.smithy @@ -0,0 +1,258 @@ +// This file defines test cases that serialize lists in JSON documents. + +$version: "2.0" + +namespace aws.protocoltests.rpcv2Cbor + +use aws.protocoltests.shared#BooleanList +use aws.protocoltests.shared#FooEnumList +use aws.protocoltests.shared#IntegerEnumList +use aws.protocoltests.shared#IntegerList +use aws.protocoltests.shared#NestedStringList +use aws.protocoltests.shared#SparseStringList +use aws.protocoltests.shared#StringList +use aws.protocoltests.shared#StringSet +use aws.protocoltests.shared#TimestampList +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +/// This test case serializes JSON lists for the following cases for both +/// input and output: +/// +/// 1. Normal lists. +/// 2. Normal sets. +/// 3. Lists of lists. +/// 4. Lists of structures. +@idempotent +operation RpcV2CborLists { + input: RpcV2CborListInputOutput, + output: RpcV2CborListInputOutput +} + +apply RpcV2CborLists @httpRequestTests([ + { + id: "RpcV2CborLists", + documentation: "Serializes RpcV2 Cbor lists", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "v2tib29sZWFuTGlzdJ/19P9oZW51bUxpc3SfY0Zvb2Ew/2tpbnRFbnVtTGlzdJ8BAv9raW50ZWdlckxpc3SfAQL/cG5lc3RlZFN0cmluZ0xpc3Sfn2Nmb29jYmFy/59jYmF6Y3F1eP//anN0cmluZ0xpc3SfY2Zvb2NiYXL/aXN0cmluZ1NldJ9jZm9vY2Jhcv9tc3RydWN0dXJlTGlzdJ+/YWFhMWFiYTL/v2FhYTNhYmE0//9tdGltZXN0YW1wTGlzdJ/B+0HU1/vzgAAAwftB1Nf784AAAP//", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "stringList": [ + "foo", + "bar" + ], + "stringSet": [ + "foo", + "bar" + ], + "integerList": [ + 1, + 2 + ], + "booleanList": [ + true, + false + ], + "timestampList": [ + 1398796238, + 1398796238 + ], + "enumList": [ + "Foo", + "0" + ], + "intEnumList": [ + 1, + 2 + ], + "nestedStringList": [ + [ + "foo", + "bar" + ], + [ + "baz", + "qux" + ] + ], + "structureList": [ + { + "a": "1", + "b": "2" + }, + { + "a": "3", + "b": "4" + } + ] + } + }, + { + id: "RpcV2CborListsEmpty", + documentation: "Serializes empty JSON lists", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "v2pzdHJpbmdMaXN0n///", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: [] + } + }, + { + id: "RpcV2CborListsSerializeNull", + documentation: "Serializes null values in lists", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborLists", + body: "v3BzcGFyc2VTdHJpbmdMaXN0n/ZiaGn//w==", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + sparseStringList: [null, "hi"] + } + } +]) + +apply RpcV2CborLists @httpResponseTests([ + { + id: "RpcV2CborLists", + documentation: "Serializes RpcV2 Cbor lists", + protocol: rpcv2Cbor, + code: 200, + body: "v2tib29sZWFuTGlzdJ/19P9oZW51bUxpc3SfY0Zvb2Ew/2tpbnRFbnVtTGlzdJ8BAv9raW50ZWdlckxpc3SfAQL/cG5lc3RlZFN0cmluZ0xpc3Sfn2Nmb29jYmFy/59jYmF6Y3F1eP//anN0cmluZ0xpc3SfY2Zvb2NiYXL/aXN0cmluZ1NldJ9jZm9vY2Jhcv9tc3RydWN0dXJlTGlzdJ+/YWFhMWFiYTL/v2FhYTNhYmE0//9tdGltZXN0YW1wTGlzdJ/B+0HU1/vzgAAAwftB1Nf784AAAP//", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "stringList": [ + "foo", + "bar" + ], + "stringSet": [ + "foo", + "bar" + ], + "integerList": [ + 1, + 2 + ], + "booleanList": [ + true, + false + ], + "timestampList": [ + 1398796238, + 1398796238 + ], + "enumList": [ + "Foo", + "0" + ], + "intEnumList": [ + 1, + 2 + ], + "nestedStringList": [ + [ + "foo", + "bar" + ], + [ + "baz", + "qux" + ] + ], + "structureList": [ + { + "a": "1", + "b": "2" + }, + { + "a": "3", + "b": "4" + } + ] + } + }, + { + id: "RpcV2CborListsEmpty", + documentation: "Serializes empty RpcV2 Cbor lists", + protocol: rpcv2Cbor, + code: 200, + body: "v2pzdHJpbmdMaXN0n///", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + stringList: [] + } + }, + { + id: "RpcV2CborListsSerializeNull", + documentation: "Serializes null values in sparse lists", + protocol: rpcv2Cbor, + code: 200, + body: "v3BzcGFyc2VTdHJpbmdMaXN0n/ZiaGn//w==", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + sparseStringList: [null, "hi"] + } + } +]) + +structure RpcV2CborListInputOutput { + stringList: StringList, + + sparseStringList: SparseStringList, + + stringSet: StringSet, + + integerList: IntegerList, + + booleanList: BooleanList, + + timestampList: TimestampList, + + enumList: FooEnumList, + + intEnumList: IntegerEnumList, + + nestedStringList: NestedStringList, + + structureList: StructureList +} + +list StructureList { + member: StructureListMember, +} + +structure StructureListMember { + a: String, + b: String, +} diff --git a/smithy-aws-protocol-tests/model/rpcV2/cbor-maps.smithy b/smithy-aws-protocol-tests/model/rpcV2/cbor-maps.smithy new file mode 100644 index 00000000000..2dabe26eb3d --- /dev/null +++ b/smithy-aws-protocol-tests/model/rpcV2/cbor-maps.smithy @@ -0,0 +1,393 @@ +$version: "2.0" + +namespace aws.protocoltests.rpcv2Cbor + +use aws.protocoltests.shared#FooEnumMap +use aws.protocoltests.shared#GreetingStruct +use aws.protocoltests.shared#SparseStringMap +use aws.protocoltests.shared#StringSet +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests +use smithy.protocols#rpcv2Cbor + +/// The example tests basic map serialization. +operation RpcV2CborMaps { + input: RpcV2CborMapsInputOutput, + output: RpcV2CborMapsInputOutput +} + +apply RpcV2CborMaps @httpRequestTests([ + { + id: "RpcV2CborMaps", + documentation: "Serializes maps", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborMaps", + body: "v25kZW5zZVN0cnVjdE1hcL9jYmF6v2JoaWNieWX/Y2Zvb79iaGlldGhlcmX//29zcGFyc2VTdHJ1Y3RNYXC/Y2Jher9iaGljYnll/2Nmb2+/YmhpZXRoZXJl////", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + }, + "sparseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + } + } + }, + { + id: "RpcV2CborSerializesNullMapValues", + documentation: "Serializes map values in sparse maps", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborMaps", + body: "v3BzcGFyc2VCb29sZWFuTWFwv2F49v9vc3BhcnNlTnVtYmVyTWFwv2F49v9vc3BhcnNlU3RydWN0TWFwv2F49v//", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseBooleanMap": { + "x": null + }, + "sparseNumberMap": { + "x": null + }, + "sparseStringMap": { + "x": null + }, + "sparseStructMap": { + "x": null + } + } + }, + { + id: "RpcV2CborSerializesZeroValuesInMaps", + documentation: "Ensure that 0 and false are sent over the wire in all maps and lists", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborMaps", + body: "v29kZW5zZUJvb2xlYW5NYXC/YXj0/25kZW5zZU51bWJlck1hcL9heAD/cHNwYXJzZUJvb2xlYW5NYXC/YXj0/29zcGFyc2VOdW1iZXJNYXC/YXgA//8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseNumberMap": { + "x": 0 + }, + "sparseNumberMap": { + "x": 0 + }, + "denseBooleanMap": { + "x": false + }, + "sparseBooleanMap": { + "x": false + } + } + }, + { + id: "RpcV2CborSerializesSparseSetMap", + documentation: "A request that contains a sparse map of sets", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborMaps", + body: "v2xzcGFyc2VTZXRNYXC/YXmfYWFhYv9heJ////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, + { + id: "RpcV2CborSerializesDenseSetMap", + documentation: "A request that contains a dense map of sets.", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborMaps", + body: "v2xzcGFyc2VTZXRNYXC/YXmfYWFhYv9heJ////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, + { + id: "RpcV2CborSerializesSparseSetMapAndRetainsNull", + documentation: "A request that contains a sparse map of sets.", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RpcV2CborMaps", + body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL/YXr2//8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"], + "z": null + } + } + } +]) + +apply RpcV2CborMaps @httpResponseTests([ + { + id: "RpcV2CborMaps", + documentation: "Deserializes maps", + protocol: rpcv2Cbor, + code: 200, + body: "v25kZW5zZVN0cnVjdE1hcL9jYmF6v2JoaWNieWX/Y2Zvb79iaGlldGhlcmX//29zcGFyc2VTdHJ1Y3RNYXC/Y2Jher9iaGljYnll/2Nmb2+/YmhpZXRoZXJl////", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + }, + "sparseStructMap": { + "foo": { + "hi": "there" + }, + "baz": { + "hi": "bye" + } + } + } + }, + { + id: "RpcV2CborDeserializesNullMapValues", + documentation: "Deserializes null map values", + protocol: rpcv2Cbor, + code: 200, + body: "v3BzcGFyc2VCb29sZWFuTWFwv2F49v9vc3BhcnNlTnVtYmVyTWFwv2F49v9vc3BhcnNlU3RydWN0TWFwv2F49v//", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseBooleanMap": { + "x": null + }, + "sparseNumberMap": { + "x": null + }, + "sparseStringMap": { + "x": null + }, + "sparseStructMap": { + "x": null + } + } + }, + { + id: "RpcV2CborDeserializesZeroValuesInMaps", + documentation: "Ensure that 0 and false are sent over the wire in all maps and lists", + protocol: rpcv2Cbor, + code: 200, + body: "v29kZW5zZUJvb2xlYW5NYXC/YXj0/25kZW5zZU51bWJlck1hcL9heAD/cHNwYXJzZUJvb2xlYW5NYXC/YXj0/29zcGFyc2VOdW1iZXJNYXC/YXgA//8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseNumberMap": { + "x": 0 + }, + "sparseNumberMap": { + "x": 0 + }, + "denseBooleanMap": { + "x": false + }, + "sparseBooleanMap": { + "x": false + } + } + }, + { + id: "RpcV2CborDeserializesSparseSetMap", + documentation: "A response that contains a sparse map of sets", + protocol: rpcv2Cbor, + code: 200, + body: "v2xzcGFyc2VTZXRNYXC/YXmfYWFhYv9heJ////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, + { + id: "RpcV2CborDeserializesDenseSetMap", + documentation: "A response that contains a dense map of sets.", + protocol: rpcv2Cbor, + code: 200, + body: "v2xzcGFyc2VTZXRNYXC/YXmfYWFhYv9heJ////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + }, + { + id: "RpcV2CborDeserializesSparseSetMapAndRetainsNull", + documentation: "A response that contains a sparse map of sets.", + protocol: rpcv2Cbor, + code: 200, + body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL/YXr2//8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "sparseSetMap": { + "x": [], + "y": ["a", "b"], + "z": null + } + } + }, + { + id: "RpcV2CborDeserializesDenseSetMapAndSkipsNull", + documentation: """ + Clients SHOULD tolerate seeing a null value in a dense map, and they SHOULD + drop the null key-value pair.""", + protocol: rpcv2Cbor, + appliesTo: "client", + code: 200, + body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL/YXr2//8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + "denseSetMap": { + "x": [], + "y": ["a", "b"] + } + } + } +]) + +structure RpcV2CborMapsInputOutput { + denseStructMap: DenseStructMap, + sparseStructMap: SparseStructMap, + denseNumberMap: DenseNumberMap, + denseBooleanMap: DenseBooleanMap, + denseStringMap: DenseStringMap, + sparseNumberMap: SparseNumberMap, + sparseBooleanMap: SparseBooleanMap, + sparseStringMap: SparseStringMap, + denseSetMap: DenseSetMap, + sparseSetMap: SparseSetMap, +} + +map DenseStructMap { + key: String, + value: GreetingStruct +} + +@sparse +map SparseStructMap { + key: String, + value: GreetingStruct +} + +map DenseBooleanMap { + key: String, + value: Boolean +} + +map DenseNumberMap { + key: String, + value: Integer +} + +map DenseStringMap { + key: String, + value: String +} + +@sparse +map SparseBooleanMap { + key: String, + value: Boolean +} + +@sparse +map SparseNumberMap { + key: String, + value: Integer +} + +map DenseSetMap { + key: String, + value: StringSet +} + +@sparse +map SparseSetMap { + key: String, + value: StringSet +} + diff --git a/smithy-aws-protocol-tests/model/rpcV2/cbor-structs.smithy b/smithy-aws-protocol-tests/model/rpcV2/cbor-structs.smithy new file mode 100644 index 00000000000..b359d73c1c5 --- /dev/null +++ b/smithy-aws-protocol-tests/model/rpcV2/cbor-structs.smithy @@ -0,0 +1,322 @@ +$version: "2.0" + +namespace aws.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + + +@httpRequestTests([ + { + id: "RpcV2CborSimpleScalarProperties", + protocol: rpcv2Cbor, + documentation: "Serializes simple scalar properties", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9f8=" + params: { + trueBooleanValue: true, + falseBooleanValue: false, + byteValue: 5, + doubleValue: 1.889, + floatValue: 7.624, + integerValue: 256, + shortValue: 9898, + longValue: 9873, + stringValue: "simple" + } + }, + { + id: "RpcV2CborClientDoesntSerializeNullStructureValues", + documentation: "RpcV2 Cbor should not serialize null structure values", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/SimpleScalarProperties", + body: "v/8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + params: { + stringValue: null + }, + appliesTo: "client" + }, + { + id: "RpcV2CborServerDoesntDeSerializeNullStructureValues", + documentation: "RpcV2 Cbor should not deserialize null structure values", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tzdHJpbmdWYWx1Zfb/", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + params: {}, + appliesTo: "server" + }, + { + id: "RpcV2CborSupportsNaNFloatInputs", + protocol: rpcv2Cbor, + documentation: "Supports handling NaN float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tkb3VibGVWYWx1Zft/+AAAAAAAAGpmbG9hdFZhbHVl+n/AAAD/" + params: { + doubleValue: "NaN", + floatValue: "NaN" + } + }, + { + id: "RpcV2CborSupportsInfinityFloatInputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tkb3VibGVWYWx1Zft/8AAAAAAAAGpmbG9hdFZhbHVl+n+AAAD/" + params: { + doubleValue: "Infinity", + floatValue: "Infinity" + } + }, + { + id: "RpcV2CborSupportsNegativeInfinityFloatInputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + bodyMediaType: "application/cbor", + uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties", + body: "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/" + params: { + doubleValue: "-Infinity", + floatValue: "-Infinity" + } + } +]) +@httpResponseTests([ + { + id: "RpcV2CborSimpleScalarProperties", + protocol: rpcv2Cbor, + documentation: "Serializes simple scalar properties", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + bodyMediaType: "application/cbor", + body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9f8=", + code: 200, + params: { + trueBooleanValue: true, + falseBooleanValue: false, + byteValue: 5, + doubleValue: 1.889, + floatValue: 7.624, + integerValue: 256, + shortValue: 9898, + stringValue: "simple" + } + }, + { + id: "RpcV2CborClientDoesntDeSerializeNullStructureValues", + documentation: "RpcV2 Cbor should deserialize null structure values", + protocol: rpcv2Cbor, + body: "v2tzdHJpbmdWYWx1Zfb/", + code: 200, + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + params: {} + appliesTo: "client" + }, + { + id: "RpcV2CborServerDoesntSerializeNullStructureValues", + documentation: "RpcV2 Cbor should not serialize null structure values", + protocol: rpcv2Cbor, + body: "v/8=", + code: 200, + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + params: { + stringValue: null + }, + appliesTo: "server" + }, + { + id: "RpcV2CborSupportsNaNFloatOutputs", + protocol: rpcv2Cbor, + documentation: "Supports handling NaN float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + code: 200, + bodyMediaType: "application/cbor", + body: "v2tkb3VibGVWYWx1Zft/+AAAAAAAAGpmbG9hdFZhbHVl+n/AAAD/" + params: { + doubleValue: "NaN", + floatValue: "NaN" + } + }, + { + id: "RpcV2CborSupportsInfinityFloatOutputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + code: 200, + bodyMediaType: "application/cbor", + body: "v2tkb3VibGVWYWx1Zft/8AAAAAAAAGpmbG9hdFZhbHVl+n+AAAD/" + params: { + doubleValue: "Infinity", + floatValue: "Infinity" + } + }, + { + id: "RpcV2CborSupportsNegativeInfinityFloatOutputs", + protocol: rpcv2Cbor, + documentation: "Supports handling Negative Infinity float values.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + code: 200, + bodyMediaType: "application/cbor", + body: "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/" + params: { + doubleValue: "-Infinity", + floatValue: "-Infinity" + } + } +]) +operation SimpleScalarProperties { + input: SimpleScalarStructure, + output: SimpleScalarStructure +} + +apply RecursiveShapes @httpRequestTests([ + { + id: "RpcV2CborRecursiveShapes", + documentation: "Serializes recursive structures", + protocol: rpcv2Cbor, + method: "POST", + uri: "/service/RpcV2Protocol/operation/RecursiveShapes", + body: "v2ZuZXN0ZWS/Y2Zvb2RGb28xZm5lc3RlZL9jYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyv2Nmb29kRm9vMmZuZXN0ZWS/Y2JhcmRCYXIy//////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + params: { + nested: { + foo: "Foo1", + nested: { + bar: "Bar1", + recursiveMember: { + foo: "Foo2", + nested: { + bar: "Bar2" + } + } + } + } + } + } +]) + +apply RecursiveShapes @httpResponseTests([ + { + id: "RpcV2CborRecursiveShapes", + documentation: "Serializes recursive structures", + protocol: rpcv2Cbor, + code: 200, + body: "v2ZuZXN0ZWS/Y2Zvb2RGb28xZm5lc3RlZL9jYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyv2Nmb29kRm9vMmZuZXN0ZWS/Y2JhcmRCYXIy//////8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + params: { + nested: { + foo: "Foo1", + nested: { + bar: "Bar1", + recursiveMember: { + foo: "Foo2", + nested: { + bar: "Bar2" + } + } + } + } + } + } +]) + +operation RecursiveShapes { + input: RecursiveShapesInputOutput, + output: RecursiveShapesInputOutput +} + +structure RecursiveShapesInputOutput { + nested: RecursiveShapesInputOutputNested1 +} + +structure RecursiveShapesInputOutputNested1 { + foo: String, + nested: RecursiveShapesInputOutputNested2 +} + +structure RecursiveShapesInputOutputNested2 { + bar: String, + recursiveMember: RecursiveShapesInputOutputNested1, +} + + +structure SimpleScalarStructure { + trueBooleanValue: Boolean, + falseBooleanValue: Boolean, + byteValue: Byte, + doubleValue: Double, + floatValue: Float, + integerValue: Integer, + longValue: Long, + shortValue: Short, + stringValue: String, +} \ No newline at end of file diff --git a/smithy-aws-protocol-tests/model/rpcV2/empty-input-output.smithy b/smithy-aws-protocol-tests/model/rpcV2/empty-input-output.smithy new file mode 100644 index 00000000000..1a03e77919e --- /dev/null +++ b/smithy-aws-protocol-tests/model/rpcV2/empty-input-output.smithy @@ -0,0 +1,187 @@ +$version: "2.0" + +namespace aws.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + + +@httpRequestTests([ + { + id: "no_input", + protocol: rpcv2Cbor, + documentation: "Body is empty and no Content-Type header if no input", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + }, + forbidHeaders: [ + "Content-Type", + "X-Amz-Target" + ] + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/NoInputOutput", + body: "" + }, + { + id: "no_input_server_allows_accept", + protocol: rpcv2Cbor, + documentation: "Servers should allow the Accept header to be set to the default content-type.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/NoInputOutput", + body: "", + appliesTo: "server" + }, + { + id: "no_input_server_allows_empty_cbor", + protocol: rpcv2Cbor, + documentation: "Servers should accept CBOR empty struct if no input.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/NoInputOutput", + body: "v/8=", + appliesTo: "server" + }, + { + id: "NoInputServerIngoresUnexpectedFields", + protocol: rpcv2Cbor, + documentation: "Servers should accept CBOR empty struct if no input.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/NoInputOutput", + body: "v/8=", + appliesTo: "server" + } +]) +@httpResponseTests([ + { + id: "no_output", + protocol: rpcv2Cbor, + documentation: "Body is empty and no Content-Type header if no response", + body: "", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + }, + forbidHeaders: [ + "Content-Type" + ] + code: 200, + }, + { + id: "no_output_client_allows_accept", + protocol: rpcv2Cbor, + documentation: "Servers should allow the accept header to be set to the default content-type.", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + body: "", + code: 200, + appliesTo: "client", + }, + { + id: "no_input_client_allows_empty_cbor", + protocol: rpcv2Cbor, + documentation: "Client should accept CBOR empty struct if no output", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + } + body: "v/8=", + code: 200, + appliesTo: "client", + } +]) +operation NoInputOutput {} + + +@httpRequestTests([ + { + id: "empty_input", + protocol: rpcv2Cbor, + documentation: "When Input structure is empty we write CBOR equivalent of {}", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + forbidHeaders: [ + "X-Amz-Target" + ] + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/EmptyInputOutput", + body: "v/8=", + }, +]) +@httpResponseTests([ + { + id: "empty_output", + protocol: rpcv2Cbor, + documentation: "When output structure is empty we write CBOR equivalent of {}", + body: "v/8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + }, +]) +operation EmptyInputOutput { + input: EmptyStructure, + output: EmptyStructure +} + +@httpRequestTests([ + { + id: "optional_input", + protocol: rpcv2Cbor, + documentation: "When input is empty we write CBOR equivalent of {}", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Accept": "application/cbor", + "Content-Type": "application/cbor" + }, + forbidHeaders: [ + "X-Amz-Target" + ] + method: "POST", + uri: "/service/aws.protocoltests.rpcv2Cbor.RpcV2Protocol/operation/OptionalInputOutput", + body: "v/8=", + }, +]) +@httpResponseTests([ + { + id: "optional_output", + protocol: rpcv2Cbor, + documentation: "When output is empty we write CBOR equivalent of {}", + body: "v/8=", + bodyMediaType: "application/cbor", + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + } + code: 200, + }, +]) +operation OptionalInputOutput { + input: SimpleStructure, + output: SimpleStructure +} diff --git a/smithy-aws-protocol-tests/model/rpcV2/errors.smithy b/smithy-aws-protocol-tests/model/rpcV2/errors.smithy new file mode 100644 index 00000000000..5010cfa50ef --- /dev/null +++ b/smithy-aws-protocol-tests/model/rpcV2/errors.smithy @@ -0,0 +1,93 @@ +$version: "2.0" + +namespace aws.protocoltests.rpcv2Cbor + +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests +use smithy.protocols#rpcv2Cbor + +/// This operation has three possible return values: +/// +/// 1. A successful response in the form of GreetingWithErrorsOutput +/// 2. An InvalidGreeting error. +/// 3. A ComplexError error. +/// +/// Implementations must be able to successfully take a response and +/// properly deserialize successful and error responses. +@idempotent +operation GreetingWithErrors { + output: GreetingWithErrorsOutput, + errors: [InvalidGreeting, ComplexError] +} + +structure GreetingWithErrorsOutput { + greeting: String, +} + +/// This error is thrown when an invalid greeting value is provided. +@error("client") +structure InvalidGreeting { + Message: String, +} + +apply InvalidGreeting @httpResponseTests([ + { + id: "RpcV2CborInvalidGreetingError", + documentation: "Parses simple RpcV2 Cbor errors", + protocol: rpcv2Cbor, + params: { + Message: "Hi" + }, + code: 400, + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v2ZfX3R5cGV4J2F3cy5wcm90b2NvbHRlc3RzLnJwY3YyI0ludmFsaWRHcmVldGluZ2dNZXNzYWdlYkhp/w==", + bodyMediaType: "application/cbor", + }, +]) + +/// This error is thrown when a request is invalid. +@error("client") +structure ComplexError { + TopLevel: String, + Nested: ComplexNestedErrorData, +} + +structure ComplexNestedErrorData { + Foo: String, +} + +apply ComplexError @httpResponseTests([ + { + id: "RpcV2CborComplexError", + documentation: "Parses a complex error with no message member", + protocol: rpcv2Cbor, + params: { + TopLevel: "Top level", + Nested: { + Foo: "bar" + } + }, + code: 400, + headers: { + "smithy-protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor" + }, + body: "v2ZfX3R5cGV4JGF3cy5wcm90b2NvbHRlc3RzLnJwY3YyI0NvbXBsZXhFcnJvcmZOZXN0ZWS/Y0Zvb2NiYXL/aFRvcExldmVsaVRvcCBsZXZlbP8=", + bodyMediaType: "application/cbor" + }, + { + id: "RpcV2CborEmptyComplexError", + protocol: rpcv2Cbor, + code: 400, + headers: { + "Content-Type": "application/x-amz-json-1.1" + }, + body: "v2ZfX3R5cGV4JGF3cy5wcm90b2NvbHRlc3RzLnJwY3YyI0NvbXBsZXhFcnJvcv8=", + bodyMediaType: "application/cbor" + }, +]) + + diff --git a/smithy-aws-protocol-tests/model/rpcV2/fractional-seconds.smithy b/smithy-aws-protocol-tests/model/rpcV2/fractional-seconds.smithy new file mode 100644 index 00000000000..8be731e963e --- /dev/null +++ b/smithy-aws-protocol-tests/model/rpcV2/fractional-seconds.smithy @@ -0,0 +1,30 @@ +$version: "2.0" + +namespace aws.protocoltests.rpcv2Cbor + +use smithy.protocols#rpcv2Cbor +use aws.protocoltests.shared#DateTime +use smithy.test#httpResponseTests + +// These tests verify that clients can parse `DateTime` timestamps with fractional seconds. +@tags(["client-only"]) +operation FractionalSeconds { + output: FractionalSecondsOutput +} + +apply FractionalSeconds @httpResponseTests([ + { + id: "RpcV2CborDateTimeWithFractionalSeconds", + documentation: "Ensures that clients can correctly parse timestamps with fractional seconds", + protocol: rpcv2Cbor, + code: 200, + body: "v2hkYXRldGltZcH7Qcw32zgPvnf/", + params: { datetime: 946845296.123 } + bodyMediaType: "application/cbor", + appliesTo: "client" + } +]) + +structure FractionalSecondsOutput { + datetime: DateTime +} diff --git a/smithy-aws-protocol-tests/model/rpcV2/main.smithy b/smithy-aws-protocol-tests/model/rpcV2/main.smithy new file mode 100644 index 00000000000..1ea3dfa683d --- /dev/null +++ b/smithy-aws-protocol-tests/model/rpcV2/main.smithy @@ -0,0 +1,33 @@ +$version: "2.0" + +namespace aws.protocoltests.rpcv2Cbor +use aws.api#service +use smithy.protocols#rpcv2Cbor +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +@service(sdkId: "Sample RpcV2 Protocol") +@rpcv2Cbor +@title("RpcV2 Protocol Service") +service RpcV2Protocol { + version: "2020-07-14", + operations: [ + NoInputOutput, + EmptyInputOutput, + OptionalInputOutput, + SimpleScalarProperties, + RpcV2CborLists, + RpcV2CborMaps, + RecursiveShapes, + GreetingWithErrors, + FractionalSeconds + ] +} + +structure EmptyStructure { + +} + +structure SimpleStructure { + value: String, +} \ No newline at end of file diff --git a/smithy-aws-protocol-tests/model/shared-types.smithy b/smithy-aws-protocol-tests/model/shared-types.smithy index 4e88e464e81..9b282b22959 100644 --- a/smithy-aws-protocol-tests/model/shared-types.smithy +++ b/smithy-aws-protocol-tests/model/shared-types.smithy @@ -55,6 +55,10 @@ list NestedStringList { member: StringList, } +list ShortList { + member: Short, +} + list IntegerList { member: Integer, } @@ -64,6 +68,10 @@ list IntegerSet { member: Integer, } +list FloatList { + member: Float, +} + list DoubleList { member: Double, } @@ -81,11 +89,19 @@ list TimestampList { member: Timestamp, } +list BlobList { + member: Blob, +} + @uniqueItems list BlobSet { member: Blob, } +list ByteList { + member: Byte, +} + @uniqueItems list ByteSet { member: Byte, @@ -95,16 +111,29 @@ list ShortSet { member: Short, } +@uniqueItems +list LongList { + member: Long, +} + @uniqueItems list LongSet { member: Long, } +list TimestampList { + member: Timestamp, +} + @uniqueItems list TimestampSet { member: Timestamp, } +list DateTimeList { + member: DateTime, +} + @uniqueItems list DateTimeSet { member: DateTime, @@ -192,10 +221,10 @@ list IntegerEnumList { @uniqueItems list IntegerEnumSet { - member: IntegerEnum + member: IntegerEnum } map IntegerEnumMap { - key: String, - value: IntegerEnum + key: String, + value: IntegerEnum } diff --git a/smithy-protocol-traits/README.md b/smithy-protocol-traits/README.md new file mode 100644 index 00000000000..51d00fe62b1 --- /dev/null +++ b/smithy-protocol-traits/README.md @@ -0,0 +1,7 @@ +# Smithy protocols traits + +This module provides the implementation of protocols traits for Smithy. + +Protocols: + +* RPC v2 - An RPC protocol with support for multiple wire formats. diff --git a/smithy-protocol-traits/build.gradle b/smithy-protocol-traits/build.gradle new file mode 100644 index 00000000000..c269a7e007a --- /dev/null +++ b/smithy-protocol-traits/build.gradle @@ -0,0 +1,16 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +description = "This module provides the implementation of protocol traits for Smithy." + +ext { + displayName = "Smithy :: Protocol Traits" + moduleName = "software.amazon.smithy.protocol.traits" +} + +dependencies { + api project(":smithy-utils") + api project(":smithy-model") +} diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java new file mode 100644 index 00000000000..af9a5ee8907 --- /dev/null +++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java @@ -0,0 +1,148 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AbstractTrait; +import software.amazon.smithy.model.traits.AbstractTraitBuilder; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.utils.ListUtils; +import software.amazon.smithy.utils.ToSmithyBuilder; + +public final class Rpcv2CborTrait extends AbstractTrait implements ToSmithyBuilder { + + public static final ShapeId ID = ShapeId.from("smithy.protocols#rpcv2Cbor"); + + private static final String HTTP = "http"; + private static final String EVENT_STREAM_HTTP = "eventStreamHttp"; + + private final List http; + private final List eventStreamHttp; + + private Rpcv2CborTrait(Builder builder) { + super(ID, builder.getSourceLocation()); + http = ListUtils.copyOf(builder.http); + eventStreamHttp = ListUtils.copyOf(builder.eventStreamHttp); + } + + /** + * Creates a new {@code Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Updates the builder from a Node. + * + * @param node Node object that must be a valid {@code ObjectNode}. + * @return Returns the updated builder. + */ + public static Rpcv2CborTrait fromNode(Node node) { + Builder builder = builder().sourceLocation(node); + ObjectNode objectNode = node.expectObjectNode(); + objectNode.getArrayMember(HTTP).map(values -> Node.loadArrayOfString(HTTP, values)) + .ifPresent(builder::http); + objectNode.getArrayMember(EVENT_STREAM_HTTP).map(values -> Node.loadArrayOfString(EVENT_STREAM_HTTP, values)) + .ifPresent(builder::eventStreamHttp); + return builder.build(); + } + + /** + * Gets the priority ordered list of supported HTTP protocol versions. + * + * @return Returns the supported HTTP protocol versions. + */ + public List getHttp() { + return http; + } + + /** + * Gets the priority ordered list of supported HTTP protocol versions that are required when + * using event streams. + * + * @return Returns the supported event stream HTTP protocol versions. + */ + public List getEventStreamHttp() { + return eventStreamHttp; + } + + @Override + protected Node createNode() { + ObjectNode.Builder builder = Node.objectNodeBuilder().sourceLocation(getSourceLocation()); + if (!getHttp().isEmpty()) { + builder.withMember(HTTP, Node.fromStrings(getHttp())); + } + if (!getEventStreamHttp().isEmpty()) { + builder.withMember(EVENT_STREAM_HTTP, Node.fromStrings(getEventStreamHttp())); + } + return builder.build(); + } + + @Override + public Builder toBuilder() { + return builder().http(http).eventStreamHttp(eventStreamHttp); + } + + /** + * Builder for creating a {@code Rpcv2CborTrait}. + */ + public static final class Builder extends AbstractTraitBuilder { + + private final List http = new ArrayList<>(); + private final List eventStreamHttp = new ArrayList<>(); + + @Override + public Rpcv2CborTrait build() { + return new Rpcv2CborTrait(this); + } + + /** + * Sets the list of supported HTTP protocols. + * + * @param http HTTP protocols to set and replace. + * @return Returns the builder. + */ + public Builder http(List http) { + this.http.clear(); + this.http.addAll(http); + return this; + } + + /** + * Sets the list of supported event stream HTTP protocols. + * + * @param eventStreamHttp Event stream HTTP protocols to set and replace. + * @return Returns the builder. + */ + public Builder eventStreamHttp(List eventStreamHttp) { + this.eventStreamHttp.clear(); + this.eventStreamHttp.addAll(eventStreamHttp); + return this; + } + } + + /** + * Implements the {@code AbstractTrait.Provider}. + */ + public static final class Provider extends AbstractTrait.Provider { + + public Provider() { + super(ID); + } + + @Override + public Trait createTrait(ShapeId target, Node value) { + Rpcv2CborTrait result = fromNode(value); + result.setNodeCache(value); + return result; + } + } +} diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java new file mode 100644 index 00000000000..e8049bd959a --- /dev/null +++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * Validates models implementing the {@code Rpcv2CborTrait} against its constraints by: + * + * - Ensuring that every entry in {@code eventStreamHttp} also appears in the {@code http} property + * of a protocol trait. + */ +@SmithyInternalApi +public final class Rpcv2CborTraitValidator extends AbstractValidator { + + @Override + public List validate(Model model) { + List events = new ArrayList<>(); + for (ServiceShape serviceShape : model.getServiceShapesWithTrait(Rpcv2CborTrait.class)) { + Rpcv2CborTrait protocolTrait = serviceShape.expectTrait(Rpcv2CborTrait.class); + + List invalid = new ArrayList<>(protocolTrait.getEventStreamHttp()); + invalid.removeAll(protocolTrait.getHttp()); + if (!invalid.isEmpty()) { + events.add(error(serviceShape, protocolTrait, + String.format("The following values of the `eventStreamHttp` property do " + + "not also appear in the `http` property of the %s protocol " + + "trait: %s", protocolTrait.toShapeId(), invalid))); + } + } + return events; + } +} diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java new file mode 100644 index 00000000000..38e4b7cf8b1 --- /dev/null +++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java @@ -0,0 +1,12 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Defines protocols for Smithy. + */ +@SmithyUnstableApi +package software.amazon.smithy.protocol.traits; + +import software.amazon.smithy.utils.SmithyUnstableApi; diff --git a/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService new file mode 100644 index 00000000000..807dd17ce19 --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -0,0 +1 @@ +software.amazon.smithy.protocol.traits.Rpcv2CborTrait$Provider diff --git a/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator new file mode 100644 index 00000000000..d764d47b993 --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator @@ -0,0 +1 @@ +software.amazon.smithy.protocol.traits.Rpcv2CborTraitValidator diff --git a/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest b/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest new file mode 100644 index 00000000000..d3ce2c739ec --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest @@ -0,0 +1 @@ +smithy.protocols.rpcv2.smithy diff --git a/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy b/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy new file mode 100644 index 00000000000..8fb2d37e5cc --- /dev/null +++ b/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy @@ -0,0 +1,31 @@ +$version: "2.0" + +namespace smithy.protocols + +use smithy.api#cors +use smithy.api#endpoint +use smithy.api#hostLabel +use smithy.api#httpError + +/// An RPC-based protocol that serializes CBOR payloads. +@trait(selector: "service") +@protocolDefinition(traits: [ + cors + endpoint + hostLabel + httpError +]) +structure rpcv2Cbor { + /// Priority ordered list of supported HTTP protocol versions. + http: StringList + + /// Priority ordered list of supported HTTP protocol versions + /// that are required when using event streams. + eventStreamHttp: StringList +} + +/// A list of String shapes. +@private +list StringList { + member: String +} diff --git a/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java new file mode 100644 index 00000000000..5897863b2bd --- /dev/null +++ b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.model.traits.TraitFactory; +import java.util.Optional; + +public class Rpcv2CborTraitTest { + + @Test + public void loadsTraitWithDefaults() { + Node node = Node.objectNode(); + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait(Rpcv2CborTrait.ID, ShapeId.from("ns.foo#foo"), node); + + Assertions.assertTrue(trait.isPresent()); + Assertions.assertTrue(trait.get() instanceof Rpcv2CborTrait); + Rpcv2CborTrait smithyRpcV2Trait = (Rpcv2CborTrait) trait.get(); + Assertions.assertEquals(smithyRpcV2Trait.toNode(), node); + } +} diff --git a/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java new file mode 100644 index 00000000000..8c29bfe12c8 --- /dev/null +++ b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.protocol.traits; + +import java.util.concurrent.Callable; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.model.validation.testrunner.SmithyTestCase; +import software.amazon.smithy.model.validation.testrunner.SmithyTestSuite; + +public class TestRunnerTest { + @ParameterizedTest(name = "{0}") + @MethodSource("source") + public void testRunner(String filename, Callable callable) + throws Exception { + callable.call(); + } + + public static Stream source() { + return SmithyTestSuite.defaultParameterizedTestSource(TestRunnerTest.class); + } +} diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors new file mode 100644 index 00000000000..5f569df65f3 --- /dev/null +++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors @@ -0,0 +1,3 @@ +[ERROR] smithy.example#InvalidService1: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait +[ERROR] smithy.example#InvalidService2: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait +[ERROR] smithy.example#InvalidService3: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1, h2c] | Rpcv2CborTrait diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy new file mode 100644 index 00000000000..5f0968710e5 --- /dev/null +++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy @@ -0,0 +1,40 @@ +$version: "2.0" + +namespace smithy.example + +use smithy.protocols#rpcv2Cbor + +@rpcv2Cbor(http: ["h2", "http/1.1"], eventStreamHttp: ["h2"]) +service ValidService1 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["h2"]) +service ValidService2 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: [], eventStreamHttp: []) +service ValidService3 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["http/1.1"], eventStreamHttp: []) +service ValidService4 { + version: "2023-02-10" +} + +@rpcv2Cbor(eventStreamHttp: ["http/1.1"]) +service InvalidService1 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["http/1.1"]) +service InvalidService2 { + version: "2023-02-10" +} + +@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["h2", "http/1.1", "h2c"]) +service InvalidService3 { + version: "2023-02-10" +}