Description
Describe the bug
When using UpdateItemRequest with ConditionExpression and specifying ReturnValuesOnConditionCheckFailure and the existing document fail the condition and also contain a a map/dictionary where a key ends with the strings "code" or "message" JsonErrorResponseUnmarshaller throws an exception.
Regression Issue
- Select this option if this issue appears to be a regression.
Expected Behavior
When using ReturnValuesOnConditionCheckFailure I expect ConditionalCheckFailedException to be thrown and the Item property on the exception to be set even if a dictionary key has a name ending with __type, code or message.
Current Behavior
Throws an AmazonClientException
because the ConditionalCheckFailedException
cannot be deserialized
Unhandled exception. Amazon.Runtime.AmazonClientException: We expected a VALUE token but got: ObjectStart
at Amazon.Runtime.Internal.Transform.JsonUnmarshallerContext.ReadText()
at Amazon.Runtime.Internal.Transform.SimpleTypeUnmarshaller`1.Unmarshall(JsonUnmarshallerContext context)
at Amazon.Runtime.Internal.Transform.JsonErrorResponseUnmarshaller.GetValuesFromJsonIfPossible(JsonUnmarshallerContext context, String& type, String& message, String& code)
at Amazon.Runtime.Internal.Transform.JsonErrorResponseUnmarshaller.Unmarshall(JsonUnmarshallerContext context)
at Amazon.DynamoDBv2.Model.Internal.MarshallTransformations.UpdateItemResponseUnmarshaller.UnmarshallException(JsonUnmarshallerContext context, Exception innerException, HttpStatusCode statusCode)
at Amazon.Runtime.Internal.Transform.JsonResponseUnmarshaller.UnmarshallException(UnmarshallerContext input, Exception innerException, HttpStatusCode statusCode)
at Amazon.Runtime.Internal.HttpErrorResponseExceptionHandler.HandleExceptionStream(IRequestContext requestContext, IWebResponseData httpErrorResponse, HttpErrorResponseException exception, Stream responseStream)
at Amazon.Runtime.Internal.HttpErrorResponseExceptionHandler.HandleExceptionAsync(IExecutionContext executionContext, HttpErrorResponseException exception)
at Amazon.Runtime.Internal.ExceptionHandler`1.HandleAsync(IExecutionContext executionContext, Exception exception)
at Amazon.Runtime.Internal.ErrorHandler.ProcessExceptionAsync(IExecutionContext executionContext, Exception exception)
at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.Signer.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
at DynamoDbBug.DynamoDbBug.ConditionalUpdate() in /Users/bjornsallarp/Code/DynamoDbBug/DynamoDbBug.cs:line 51
at DynamoDbBug.DynamoDbBug.Run() in /Users/bjornsallarp/Code/DynamoDbBug/DynamoDbBug.cs:line 19
at Program.<Main>$(String[] args) in /Users/bjornsallarp/Code/DynamoDbBug/Main.cs:line 301
at Program.<Main>(String[] args)
Reproduction Steps
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.Model;
using ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure;
namespace DynamoDbBug;
public class DynamoDbBug(AmazonDynamoDBClient db, IDynamoDBContext context)
{
private const string TableName = "BuggedTable";
private const string DocumentKey = "Key";
public async Task Run()
{
await InsertDocument();
await ConditionalUpdate();
}
private async Task ConditionalUpdate()
{
var test = new MyObject { Foo = "foo" };
var document = context.ToDocument(test);
var request = new UpdateItemRequest
{
TableName = TableName,
Key = new Dictionary<string, AttributeValue>
{
{ "Key", new AttributeValue(DocumentKey) }
},
UpdateExpression = "SET Dictionary.#key = :document",
ExpressionAttributeNames = new Dictionary<string, string>
{
{ "#key", "keyThatEndsWithCode" }
},
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
[":document"] = new() { M = document.ToAttributeMap() },
[":maxSize"] = new() { N = "1" }
},
ConditionExpression = "size(Dictionary) < :maxSize",
ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure.ALL_OLD,
ReturnValues = ReturnValue.NONE
};
try
{
await db.UpdateItemAsync(request);
}
catch (ConditionalCheckFailedException e)
{
Console.WriteLine(e.Item?["Key"]?.S);
}
}
private async Task InsertDocument()
{
var item = new MyDocument
{
Key = DocumentKey,
Dictionary = new Dictionary<string, MyObject>
{
["keyThatEndsWithCode"] = new MyObject { Foo = "bar" }
}
};
await db.PutItemAsync(new PutItemRequest(TableName, context.ToDocument(item).ToAttributeMap()));
}
}
public class MyDocument
{
public required string Key { get; set; }
public Dictionary<string, MyObject> Dictionary { get; set; } = new();
}
public class MyObject
{
public string Foo { get; set; }
}
Possible Solution
The issue is here:
The loop keeps digging down into the json looking for something ending with __type, message or code. I assume an error response has a certain format, so why look for properties deep down, something like this could work:
while (TryReadContext(context))
{
if (context.CurrentDepth > 1)
continue;
if (context.TestExpression("__type"))
{
type = StringUnmarshaller.GetInstance().Unmarshall(context);
continue;
}
if (context.TestExpression("message"))
{
message = StringUnmarshaller.GetInstance().Unmarshall(context);
continue;
}
if (context.TestExpression("code"))
{
code = StringUnmarshaller.GetInstance().Unmarshall(context);
continue;
}
}
Additional Information/Context
No response
AWS .NET SDK and/or Package version used
AWSSDK.DynamoDBv2 3.7.400.8
Targeted .NET Platform
.NET 9
Operating System and version
OSX