Skip to content

Commit 74051ae

Browse files
Implement CborErrorResponseUnmarshaller with proper error type mapping (#3947)
1 parent a7e190d commit 74051ae

File tree

6 files changed

+202
-17
lines changed

6 files changed

+202
-17
lines changed

extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Collections.Generic;
2222
using System.Formats.Cbor;
2323
using System.IO;
24+
using System.Net;
2425

2526
namespace Amazon.Extensions.CborProtocol.Internal.Transform
2627
{
@@ -37,8 +38,75 @@ public class CborErrorResponseUnmarshaller : ICborUnmarshaller<ErrorResponse, Cb
3738
/// <returns>An <c>ErrorResponse</c> object.</returns>
3839
public ErrorResponse Unmarshall(CborUnmarshallerContext context)
3940
{
40-
// Placeholder until CBOR exception implementation.
41-
return null;
41+
var errorType = ErrorType.Unknown;
42+
43+
if (context.ResponseData.StatusCode == HttpStatusCode.BadRequest)
44+
{
45+
errorType = ErrorType.Sender;
46+
}
47+
else if (context.ResponseData.StatusCode == HttpStatusCode.InternalServerError)
48+
{
49+
errorType = ErrorType.Receiver;
50+
}
51+
52+
var response = new ErrorResponse
53+
{
54+
Type = errorType,
55+
StatusCode = context.ResponseData.StatusCode,
56+
};
57+
58+
var reader = context.Reader;
59+
reader.ReadStartMap();
60+
while (reader.PeekState() != CborReaderState.EndMap)
61+
{
62+
string propertyName = reader.ReadTextString().ToLowerInvariant();
63+
switch (propertyName)
64+
{
65+
case "__type":
66+
{
67+
context.AddPathSegment("__type");
68+
var unmarshaller = CborStringUnmarshaller.Instance;
69+
var type = unmarshaller.Unmarshall(context);
70+
response.Code = SanitizeErrorType(type);
71+
context.PopPathSegment();
72+
break;
73+
}
74+
case "message":
75+
{
76+
context.AddPathSegment("message");
77+
var unmarshaller = CborStringUnmarshaller.Instance;
78+
response.Message = unmarshaller.Unmarshall(context);
79+
context.PopPathSegment();
80+
break;
81+
}
82+
default:
83+
reader.SkipValue();
84+
break;
85+
}
86+
}
87+
88+
if (context.ResponseData.IsHeaderPresent(HeaderKeys.RequestIdHeader))
89+
{
90+
response.RequestId = context.ResponseData.GetHeaderValue(HeaderKeys.RequestIdHeader);
91+
}
92+
93+
return response;
94+
}
95+
96+
/// <summary>
97+
/// Extracts the error type from a Smithy shape identifier string.
98+
/// The input is expected to be in the format "namespace#ErrorType[:additionalInfo]".
99+
/// Returns the error type portion (e.g., "ErrorType").
100+
/// </summary>
101+
private string SanitizeErrorType(string type)
102+
{
103+
int start = type.IndexOf('#');
104+
start = start == -1 ? 0 : start + 1;
105+
106+
int end = type.IndexOf(':', start);
107+
end = end == -1 ? type.Length : end;
108+
109+
return type.Substring(start, end - start).Trim();
42110
}
43111

44112
private static CborErrorResponseUnmarshaller instance;

generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ private void generateErrorResponseTests(OperationShape operation, OperationIndex
106106
for (StructureShape error : index.getErrors(operation, service)) {
107107
error.getTrait(HttpResponseTestsTrait.class).ifPresent(trait -> {
108108
for (HttpResponseTestCase httpResponseTestCase : trait.getTestCasesFor(AppliesTo.CLIENT)) {
109-
if(!ProtocolTestCustomizations.TestsToSkip.contains(httpResponseTestCase.getId())&&
110-
!trait.getTestCasesFor(AppliesTo.CLIENT).getFirst().getProtocol().getName().toLowerCase().contains("cbor")) // Skip CBOR response tests until the unmarshallers are ready
109+
if(!ProtocolTestCustomizations.TestsToSkip.contains(httpResponseTestCase.getId()))
111110
generateErrorResponseTest(operation, error, httpResponseTestCase);
112111
}
113112
});

generator/ServiceClientGeneratorLib/ExceptionShape.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ public string Code
8282
}
8383
}
8484

85+
/// <summary>
86+
/// Returns the original shape name of the exception specified in the json model.
87+
/// This is used to find the exception type for CBOR as the exception response contains
88+
/// the Shape ID rather than the error code.
89+
/// https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#operation-error-serialization
90+
/// </summary>
91+
public string ShapeOriginalName
92+
{
93+
get
94+
{
95+
return base.Name;
96+
}
97+
}
98+
8599
/// <summary>
86100
/// Determines if the exception is marked retryable
87101
/// </summary>

generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.cs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -398,21 +398,47 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte
398398
this.Write(" if (errorResponse.Code != null && errorResponse.Code.Equals(\"");
399399

400400
#line 179 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
401-
this.Write(this.ToStringHelper.ToStringWithCulture(exception.Code));
401+
this.Write(this.ToStringHelper.ToStringWithCulture(exception.ShapeOriginalName));
402402

403403
#line default
404404
#line hidden
405-
this.Write("\"))\r\n {\r\n return ");
405+
this.Write("\"))\r\n {\r\n");
406406

407407
#line 181 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
408+
409+
if (exception.ShapeOriginalName != exception.Code)
410+
{
411+
412+
413+
#line default
414+
#line hidden
415+
this.Write(" errorResponse.Code = \"");
416+
417+
#line 185 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
418+
this.Write(this.ToStringHelper.ToStringWithCulture(exception.Code));
419+
420+
#line default
421+
#line hidden
422+
this.Write("\";\r\n");
423+
424+
#line 186 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
425+
426+
}
427+
428+
429+
#line default
430+
#line hidden
431+
this.Write(" return ");
432+
433+
#line 189 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
408434
this.Write(this.ToStringHelper.ToStringWithCulture(exception.Name));
409435

410436
#line default
411437
#line hidden
412438
this.Write("Unmarshaller.Instance.Unmarshall(contextCopy, errorResponse);\r\n }\r" +
413439
"\n");
414440

415-
#line 183 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
441+
#line 191 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
416442

417443
}
418444

@@ -421,7 +447,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte
421447
#line hidden
422448
this.Write(" }\r\n");
423449

424-
#line 187 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
450+
#line 195 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
425451

426452
if (this.Config.ServiceModel.IsAwsQueryCompatible)
427453
{
@@ -432,15 +458,15 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte
432458
#line hidden
433459
this.Write(" return new ");
434460

435-
#line 192 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
461+
#line 200 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
436462
this.Write(this.ToStringHelper.ToStringWithCulture(this.BaseException));
437463

438464
#line default
439465
#line hidden
440466
this.Write("(errorResponse.Message, errorResponse.InnerException, errorType, errorCode, error" +
441467
"Response.RequestId, errorResponse.StatusCode);\r\n");
442468

443-
#line 193 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
469+
#line 201 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
444470

445471
}
446472
else
@@ -451,15 +477,15 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte
451477
#line hidden
452478
this.Write(" return new ");
453479

454-
#line 198 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
480+
#line 206 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
455481
this.Write(this.ToStringHelper.ToStringWithCulture(this.BaseException));
456482

457483
#line default
458484
#line hidden
459485
this.Write("(errorResponse.Message, errorResponse.InnerException, errorResponse.Type, errorRe" +
460486
"sponse.Code, errorResponse.RequestId, errorResponse.StatusCode);\r\n");
461487

462-
#line 199 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
488+
#line 207 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
463489

464490
}
465491

@@ -468,7 +494,7 @@ public override AmazonServiceException UnmarshallException(CborUnmarshallerConte
468494
#line hidden
469495
this.Write(" }\r\n\r\n");
470496

471-
#line 204 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
497+
#line 212 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
472498

473499
if (payload != null && payload.Shape.IsStreaming)
474500
{
@@ -489,7 +515,7 @@ public override bool HasStreamingProperty
489515
490516
");
491517

492-
#line 219 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
518+
#line 227 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
493519

494520
}
495521
this.AddResponseSingletonMethod();
@@ -498,7 +524,7 @@ public override bool HasStreamingProperty
498524
#line default
499525
#line hidden
500526

501-
#line 223 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
527+
#line 231 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
502528

503529
if(isEventStreamOutput)
504530
{
@@ -522,7 +548,7 @@ protected override bool ShouldReadEntireResponse(IWebResponseData response, bool
522548
public override bool HasStreamingProperty => true;
523549
");
524550

525-
#line 241 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
551+
#line 249 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborResponseUnmarshaller.tt"
526552

527553
}
528554

generator/ServiceClientGeneratorLib/Generators/Marshallers/CborResponseUnmarshaller.tt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,16 @@ namespace <#=this.Config.Namespace #>.Model.Internal.MarshallTransformations
176176
foreach (var exception in this.Operation.Exceptions)
177177
{
178178
#>
179-
if (errorResponse.Code != null && errorResponse.Code.Equals("<#=exception.Code #>"))
179+
if (errorResponse.Code != null && errorResponse.Code.Equals("<#=exception.ShapeOriginalName #>"))
180180
{
181+
<#
182+
if (exception.ShapeOriginalName != exception.Code)
183+
{
184+
#>
185+
errorResponse.Code = "<#=exception.Code#>";
186+
<#
187+
}
188+
#>
181189
return <#=exception.Name#>Unmarshaller.Instance.Unmarshall(contextCopy, errorResponse);
182190
}
183191
<#

sdk/test/ProtocolTests/Generated/RpcV2Protocol/dotnet-protocol-test-codegen/GreetingWithErrors.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,82 @@
3131
using System.Collections.Generic;
3232
using System.IO;
3333
using System.Linq;
34+
using System.Net;
3435
using System.Text;
3536

3637
namespace AWSSDK.ProtocolTests.RpcV2Protocol
3738
{
3839
[TestClass]
3940
public class GreetingWithErrors
4041
{
42+
/// <summary>
43+
/// Parses simple RpcV2 Cbor errors
44+
/// </summary>
45+
[TestMethod]
46+
[TestCategory("ProtocolTest")]
47+
[TestCategory("ErrorTest")]
48+
[TestCategory("RpcV2Protocol")]
49+
public void RpcV2CborInvalidGreetingErrorErrorResponse()
50+
{
51+
// Arrange
52+
var webResponseData = new WebResponseData();
53+
webResponseData.StatusCode = (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400);
54+
webResponseData.Headers["Content-Type"] = "application/cbor";
55+
webResponseData.Headers["smithy-protocol"] = "rpc-v2-cbor";
56+
byte[] bytes = Convert.FromBase64String("v2ZfX3R5cGV4LnNtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNJbnZhbGlkR3JlZXRpbmdnTWVzc2FnZWJIaf8=");
57+
var stream = new MemoryStream(bytes);
58+
var context = new CborUnmarshallerContext(stream,true,webResponseData);
59+
// Act
60+
var errorResponse = new GreetingWithErrorsResponseUnmarshaller().UnmarshallException(context, null, (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400));
61+
// Assert
62+
Assert.IsInstanceOfType(errorResponse, typeof(InvalidGreetingException));
63+
Assert.AreEqual(errorResponse.StatusCode,(HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400));
64+
}
65+
66+
/// <summary>
67+
/// Parses a complex error with no message member
68+
/// </summary>
69+
[TestMethod]
70+
[TestCategory("ProtocolTest")]
71+
[TestCategory("ErrorTest")]
72+
[TestCategory("RpcV2Protocol")]
73+
public void RpcV2CborComplexErrorErrorResponse()
74+
{
75+
// Arrange
76+
var webResponseData = new WebResponseData();
77+
webResponseData.StatusCode = (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400);
78+
webResponseData.Headers["Content-Type"] = "application/cbor";
79+
webResponseData.Headers["smithy-protocol"] = "rpc-v2-cbor";
80+
byte[] bytes = Convert.FromBase64String("v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3JoVG9wTGV2ZWxpVG9wIGxldmVsZk5lc3RlZL9jRm9vY2Jhcv//");
81+
var stream = new MemoryStream(bytes);
82+
var context = new CborUnmarshallerContext(stream,true,webResponseData);
83+
// Act
84+
var errorResponse = new GreetingWithErrorsResponseUnmarshaller().UnmarshallException(context, null, (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400));
85+
// Assert
86+
Assert.IsInstanceOfType(errorResponse, typeof(ComplexErrorException));
87+
Assert.AreEqual(errorResponse.StatusCode,(HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400));
88+
}
89+
90+
[TestMethod]
91+
[TestCategory("ProtocolTest")]
92+
[TestCategory("ErrorTest")]
93+
[TestCategory("RpcV2Protocol")]
94+
public void RpcV2CborEmptyComplexErrorErrorResponse()
95+
{
96+
// Arrange
97+
var webResponseData = new WebResponseData();
98+
webResponseData.StatusCode = (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400);
99+
webResponseData.Headers["Content-Type"] = "application/cbor";
100+
webResponseData.Headers["smithy-protocol"] = "rpc-v2-cbor";
101+
byte[] bytes = Convert.FromBase64String("v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3L/");
102+
var stream = new MemoryStream(bytes);
103+
var context = new CborUnmarshallerContext(stream,true,webResponseData);
104+
// Act
105+
var errorResponse = new GreetingWithErrorsResponseUnmarshaller().UnmarshallException(context, null, (HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400));
106+
// Assert
107+
Assert.IsInstanceOfType(errorResponse, typeof(ComplexErrorException));
108+
Assert.AreEqual(errorResponse.StatusCode,(HttpStatusCode)Enum.ToObject(typeof(HttpStatusCode), 400));
109+
}
110+
41111
}
42112
}

0 commit comments

Comments
 (0)