diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs index fd1ef5234ae4..06c336c692f3 100644 --- a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Formats.Cbor; using System.IO; +using System.Net; namespace Amazon.Extensions.CborProtocol.Internal.Transform { @@ -37,8 +38,75 @@ public class CborErrorResponseUnmarshaller : ICborUnmarshallerAn ErrorResponse object. public ErrorResponse Unmarshall(CborUnmarshallerContext context) { - // Placeholder until CBOR exception implementation. - return null; + var errorType = ErrorType.Unknown; + + if (context.ResponseData.StatusCode == HttpStatusCode.BadRequest) + { + errorType = ErrorType.Sender; + } + else if (context.ResponseData.StatusCode == HttpStatusCode.InternalServerError) + { + errorType = ErrorType.Receiver; + } + + var response = new ErrorResponse + { + Type = errorType, + StatusCode = context.ResponseData.StatusCode, + }; + + var reader = context.Reader; + reader.ReadStartMap(); + while (reader.PeekState() != CborReaderState.EndMap) + { + string propertyName = reader.ReadTextString().ToLowerInvariant(); + switch (propertyName) + { + case "__type": + { + context.AddPathSegment("__type"); + var unmarshaller = CborStringUnmarshaller.Instance; + var type = unmarshaller.Unmarshall(context); + response.Code = SanitizeErrorType(type); + context.PopPathSegment(); + break; + } + case "message": + { + context.AddPathSegment("message"); + var unmarshaller = CborStringUnmarshaller.Instance; + response.Message = unmarshaller.Unmarshall(context); + context.PopPathSegment(); + break; + } + default: + reader.SkipValue(); + break; + } + } + + if (context.ResponseData.IsHeaderPresent(HeaderKeys.RequestIdHeader)) + { + response.RequestId = context.ResponseData.GetHeaderValue(HeaderKeys.RequestIdHeader); + } + + return response; + } + + /// + /// Extracts the error type from a Smithy shape identifier string. + /// The input is expected to be in the format "namespace#ErrorType[:additionalInfo]". + /// Returns the error type portion (e.g., "ErrorType"). + /// + private string SanitizeErrorType(string type) + { + int start = type.IndexOf('#'); + start = start == -1 ? 0 : start + 1; + + int end = type.IndexOf(':', start); + end = end == -1 ? type.Length : end; + + return type.Substring(start, end - start).Trim(); } private static CborErrorResponseUnmarshaller instance; diff --git a/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java b/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java index f054fe4217f0..c8b3f1924b74 100644 --- a/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java +++ b/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java @@ -106,8 +106,7 @@ private void generateErrorResponseTests(OperationShape operation, OperationIndex for (StructureShape error : index.getErrors(operation, service)) { error.getTrait(HttpResponseTestsTrait.class).ifPresent(trait -> { for (HttpResponseTestCase httpResponseTestCase : trait.getTestCasesFor(AppliesTo.CLIENT)) { - if(!ProtocolTestCustomizations.TestsToSkip.contains(httpResponseTestCase.getId())&& - !trait.getTestCasesFor(AppliesTo.CLIENT).getFirst().getProtocol().getName().toLowerCase().contains("cbor")) // Skip CBOR response tests until the unmarshallers are ready + if(!ProtocolTestCustomizations.TestsToSkip.contains(httpResponseTestCase.getId())) generateErrorResponseTest(operation, error, httpResponseTestCase); } }); diff --git a/generator/ServiceClientGeneratorLib/ExceptionShape.cs b/generator/ServiceClientGeneratorLib/ExceptionShape.cs index a8b92f27d5ee..d9d344ff9cae 100644 --- a/generator/ServiceClientGeneratorLib/ExceptionShape.cs +++ b/generator/ServiceClientGeneratorLib/ExceptionShape.cs @@ -82,6 +82,20 @@ public string Code } } + /// + /// Returns the original shape name of the exception specified in the json model. + /// This is used to find the exception type for CBOR as the exception response contains + /// the Shape ID rather than the error code. + /// https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#operation-error-serialization + /// + public string ShapeOriginalName + { + get + { + return base.Name; + } + } + /// /// Determines if the exception is marked retryable /// diff --git a/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.cs b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.cs index 074d9680f2fc..51284f726375 100644 --- a/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.cs +++ b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.cs @@ -398,13 +398,39 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte this.Write(" if (errorResponse.Code != null && errorResponse.Code.Equals(\""); #line 179 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(exception.Code)); + this.Write(this.ToStringHelper.ToStringWithCulture(exception.ShapeOriginalName)); #line default #line hidden - this.Write("\"))\r\n {\r\n return "); + this.Write("\"))\r\n {\r\n"); #line 181 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + + if (exception.ShapeOriginalName != exception.Code) + { + + + #line default + #line hidden + this.Write(" errorResponse.Code = \""); + + #line 185 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(exception.Code)); + + #line default + #line hidden + this.Write("\";\r\n"); + + #line 186 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + + } + + + #line default + #line hidden + this.Write(" return "); + + #line 189 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" this.Write(this.ToStringHelper.ToStringWithCulture(exception.Name)); #line default @@ -412,7 +438,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte this.Write("Unmarshaller.Instance.Unmarshall(contextCopy, errorResponse);\r\n }\r" + "\n"); - #line 183 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 191 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" } @@ -421,7 +447,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte #line hidden this.Write(" }\r\n"); - #line 187 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 195 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" if (this.Config.ServiceModel.IsAwsQueryCompatible) { @@ -432,7 +458,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte #line hidden this.Write(" return new "); - #line 192 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 200 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.BaseException)); #line default @@ -440,7 +466,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte this.Write("(errorResponse.Message, errorResponse.InnerException, errorType, errorCode, error" + "Response.RequestId, errorResponse.StatusCode);\r\n"); - #line 193 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 201 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" } else @@ -451,7 +477,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte #line hidden this.Write(" return new "); - #line 198 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 206 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.BaseException)); #line default @@ -459,7 +485,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte this.Write("(errorResponse.Message, errorResponse.InnerException, errorResponse.Type, errorRe" + "sponse.Code, errorResponse.RequestId, errorResponse.StatusCode);\r\n"); - #line 199 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 207 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" } @@ -468,7 +494,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte #line hidden this.Write(" }\r\n\r\n"); - #line 204 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 212 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" if (payload != null && payload.Shape.IsStreaming) { @@ -489,7 +515,7 @@ public override bool HasStreamingProperty "); - #line 219 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 227 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" } this.AddResponseSingletonMethod(); @@ -498,7 +524,7 @@ public override bool HasStreamingProperty #line default #line hidden - #line 223 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 231 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" if(isEventStreamOutput) { @@ -522,7 +548,7 @@ protected override bool ShouldReadEntireResponse(IWebResponseData response, bool public override bool HasStreamingProperty => true; "); - #line 241 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" + #line 249 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt" } diff --git a/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.tt b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.tt index d328118fb9d8..016b60cbf93e 100644 --- a/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.tt +++ b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.tt @@ -176,8 +176,16 @@ namespace <#=this.Config.Namespace #>.Model.Internal.MarshallTransformations foreach (var exception in this.Operation.Exceptions) { #> - if (errorResponse.Code != null && errorResponse.Code.Equals("<#=exception.Code #>")) + if (errorResponse.Code != null && errorResponse.Code.Equals("<#=exception.ShapeOriginalName #>")) { +<# + if (exception.ShapeOriginalName != exception.Code) + { +#> + errorResponse.Code = "<#=exception.Code#>"; +<# + } +#> return <#=exception.Name#>Unmarshaller.Instance.Unmarshall(contextCopy, errorResponse); } <# diff --git a/sdk/test/ProtocolTests/Generated/RpcV2Protocol/dotnet-protocol-test-codegen/GreetingWithErrors.cs b/sdk/test/ProtocolTests/Generated/RpcV2Protocol/dotnet-protocol-test-codegen/GreetingWithErrors.cs index 642cd2917cc6..246d1d748c38 100644 --- a/sdk/test/ProtocolTests/Generated/RpcV2Protocol/dotnet-protocol-test-codegen/GreetingWithErrors.cs +++ b/sdk/test/ProtocolTests/Generated/RpcV2Protocol/dotnet-protocol-test-codegen/GreetingWithErrors.cs @@ -31,6 +31,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Text; namespace AWSSDK.ProtocolTests.RpcV2Protocol @@ -38,5 +39,74 @@ namespace AWSSDK.ProtocolTests.RpcV2Protocol [TestClass] public class GreetingWithErrors { + /// + /// Parses simple RpcV2 Cbor errors + /// + [TestMethod] + [TestCategory("ProtocolTest")] + [TestCategory("ErrorTest")] + [TestCategory("RpcV2Protocol")] + public void RpcV2CborInvalidGreetingErrorErrorResponse() + { + // Arrange + var webResponseData = new WebResponseData(); + webResponseData.StatusCode = (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400); + webResponseData.Headers["Content-Type"] = "application/cbor"; + webResponseData.Headers["smithy-protocol"] = "rpc-v2-cbor"; + byte[] bytes = Convert.FromBase64String("v2ZfX3R5cGV4LnNtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNJbnZhbGlkR3JlZXRpbmdnTWVzc2FnZWJIaf8="); + var stream = new MemoryStream(bytes); + var context = new CborUnmarshallerContext(stream,true,webResponseData); + // Act + var errorResponse = new GreetingWithErrorsResponseUnmarshaller().UnmarshallException(context, null, (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400)); + // Assert + Assert.IsInstanceOfType(errorResponse, typeof(InvalidGreetingException)); + Assert.AreEqual(errorResponse.StatusCode,(HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400)); + } + + /// + /// Parses a complex error with no message member + /// + [TestMethod] + [TestCategory("ProtocolTest")] + [TestCategory("ErrorTest")] + [TestCategory("RpcV2Protocol")] + public void RpcV2CborComplexErrorErrorResponse() + { + // Arrange + var webResponseData = new WebResponseData(); + webResponseData.StatusCode = (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400); + webResponseData.Headers["Content-Type"] = "application/cbor"; + webResponseData.Headers["smithy-protocol"] = "rpc-v2-cbor"; + byte[] bytes = Convert.FromBase64String("v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3JoVG9wTGV2ZWxpVG9wIGxldmVsZk5lc3RlZL9jRm9vY2Jhcv//"); + var stream = new MemoryStream(bytes); + var context = new CborUnmarshallerContext(stream,true,webResponseData); + // Act + var errorResponse = new GreetingWithErrorsResponseUnmarshaller().UnmarshallException(context, null, (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400)); + // Assert + Assert.IsInstanceOfType(errorResponse, typeof(ComplexErrorException)); + Assert.AreEqual(errorResponse.StatusCode,(HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400)); + } + + [TestMethod] + [TestCategory("ProtocolTest")] + [TestCategory("ErrorTest")] + [TestCategory("RpcV2Protocol")] + public void RpcV2CborEmptyComplexErrorErrorResponse() + { + // Arrange + var webResponseData = new WebResponseData(); + webResponseData.StatusCode = (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400); + webResponseData.Headers["Content-Type"] = "application/cbor"; + webResponseData.Headers["smithy-protocol"] = "rpc-v2-cbor"; + byte[] bytes = Convert.FromBase64String("v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3L/"); + var stream = new MemoryStream(bytes); + var context = new CborUnmarshallerContext(stream,true,webResponseData); + // Act + var errorResponse = new GreetingWithErrorsResponseUnmarshaller().UnmarshallException(context, null, (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400)); + // Assert + Assert.IsInstanceOfType(errorResponse, typeof(ComplexErrorException)); + Assert.AreEqual(errorResponse.StatusCode,(HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400)); + } + } }