DynamoDb enhanced TableSchema interface incorrectly types the AttributeConverter returned by `converterForAttribute`
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 1k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 51
Description
### Describe the bug
This was found while implementing a custom TableSchema in Kotlin. The `converterForAttribute` function is not needed for regular table operations or default extensions, but it is used in non-default extensions. In this case, the one that brought this to my attention is `AutoGeneratedTimestampRecordExtension`.
Relevant `TableSchema` code:
```java
/**
* ...
*
* @param The type of model object that is being mapped to records in the DynamoDb table.
*/
public interface TableSchema {
/**
* {@link AttributeConverter} that is applied to the given key.
*
* @param key Attribute of the modelled item.
* @return AttributeConverter defined for the given attribute key.
*/
default AttributeConverter converterForAttribute(Object key) {
throw new UnsupportedOperationException();
}
}
```
Relevant `AutoGeneratedTimestampRecordExtension` code:
```java
@Override
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
Collection customMetadataObject = context.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null);
if (customMetadataObject == null) {
return WriteModification.builder().build();
}
Map itemToTransform = new HashMap<>(context.items());
customMetadataObject.forEach(
key -> insertTimestampInItemToTransform(itemToTransform, key,
context.tableSchema().converterForAttribute(key)));
return WriteModification.builder()
.transformedItem(Collections.unmodifiableMap(itemToTransform))
.build();
}
private void insertTimestampInItemToTransform(Map itemToTransform,
String key,
AttributeConverter converter) {
itemToTransform.put(key, converter.transformFrom(clock.instant()));
}
```
The `AutoGeneratedTimestampRecordExtension` uses `converterForAttribute` to get the converter for the `Instant` field annotated with `DynamoDbAutoGeneratedTimestampAttribute`, which is `AttributeConverter` and not an `AttributeConverter`.
To implement this in Kotlin, we need to do an unchecked cast on the converter for the attribute to `AttributeConverter`, when the `AttributeConverter` that's returned is actually `AttributeConverter`. `AttrT` is the type of the attribute getting converted.
```kotlin
@Suppress("UNCHECKED_CAST")
override fun converterForAttribute(key: Any): AttributeConverter {
val attributeKey = key as String
val attribute: CustomAttribute = attributes[attributeKey]
return attribute.converter as AttributeConverter
}
```
### Expected Behavior
The signature of `TableSchema.converterForAttribute` returns an `AttributeConverter` for attributes of the item
### Current Behavior
The signature of `TableSchema.converterForAttribute` returns an `AttributeConverter` for the item itself
### Reproduction Steps
Difficult to create an SSCCE, but hopefully there is enough information to explain the issue
### Possible Solution
The current implementations work due to type erasure. The `StaticTableSchema` implementation uses `ResolvedImmutableAttribute.attributeConverter()` to return the appropriate `AttributeConverter`, however, the `ResolvedImmutableAttribute` `attributeConverter` is a raw `AttributeConverter` without a type specified so this is not caught by the compiler.
```java
private final AttributeConverter attributeConverter;
public AttributeConverter attributeConverter() {
return attributeConverter;
}
```
### Additional Information/Context
_No response_
### AWS Java SDK version used
2.20.40
### JDK version used
11
### Operating System and version
MacOS Ventura 13.4
Contributor guide
Assessment
This issue has not been assessed yet.