Skip to content

EnumAttributeConverter: enums can be identified by toString() or name() #3971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-AmazonDynamoDB-2bb1833.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"category": "Amazon DynamoDB",
"contributor": "martinKindall",
"type": "bugfix",
"description": "Created static method EnumAttributeConverter::createWithNameAsKeys which creates a converter based on the Enum::name method to identify enums, rather than Enum::toString. This is preferable because Enum::name is final and cannot be overwritten, as opposed to Enum::toString. EnumAttributeConverter::create is kept as it is, for backward compatibility."
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.DocumentAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.DoubleAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.DurationAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.FloatAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.InstantAsStringAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.IntegerAttributeConverter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.enhanced.dynamodb;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.utils.Validate;

/**
* A converter between an {@link Enum} and {@link AttributeValue}.
*
* <p>
* This stores values in DynamoDB as a string.
*
* <p>
* Use EnumAttributeConverter::create in order to use Enum::toString as the enum identifier
*
* <p>
* Use EnumAttributeConverter::createWithNameAsKeys in order to use Enum::name as the enum identifier
*
* <p>
* This can be created via {@link #create(Class)}.
*/
@SdkPublicApi
public final class EnumAttributeConverter<T extends Enum<T>> implements AttributeConverter<T> {

private final Class<T> enumClass;
private final Map<String, T> enumValueMap;

private final Function<T, String> keyExtractor;

private EnumAttributeConverter(Class<T> enumClass, Function<T, String> keyExtractor) {
this.enumClass = enumClass;
this.keyExtractor = keyExtractor;

Map<String, T> mutableEnumValueMap = new LinkedHashMap<>();
Arrays.stream(enumClass.getEnumConstants())
.forEach(enumConstant -> mutableEnumValueMap.put(keyExtractor.apply(enumConstant), enumConstant));

this.enumValueMap = Collections.unmodifiableMap(mutableEnumValueMap);
}

/**
* Creates an EnumAttributeConverter for an {@link Enum}.
*
* <p>
* Uses Enum::toString as the enum identifier.
*
* @param enumClass The enum class to be used
* @return an EnumAttributeConverter
* @param <T> the enum subclass
*/
public static <T extends Enum<T>> EnumAttributeConverter<T> create(Class<T> enumClass) {
return new EnumAttributeConverter<>(enumClass, Enum::toString);
}

/**
* Creates an EnumAttributeConverter for an {@link Enum}.
*
* <p>
* Uses Enum::name as the enum identifier.
*
* @param enumClass The enum class to be used
* @return an EnumAttributeConverter
* @param <T> the enum subclass
*/
public static <T extends Enum<T>> EnumAttributeConverter<T> createWithNameAsKeys(Class<T> enumClass) {
return new EnumAttributeConverter<>(enumClass, Enum::name);
}

/**
* Returns the proper {@link AttributeValue} for the given enum type.
*
* @param input the enum type to be converted
* @return AttributeValue
*/
@Override
public AttributeValue transformFrom(T input) {
return AttributeValue.builder().s(keyExtractor.apply(input)).build();
}

/**
* Returns the proper enum type for the given {@link AttributeValue} input.
*
* @param input the AttributeValue to be converted
* @return an enum type
*/
@Override
public T transformTo(AttributeValue input) {
Validate.isTrue(input.s() != null, "Cannot convert non-string value to enum.");
T returnValue = enumValueMap.get(input.s());

if (returnValue == null) {
throw new IllegalArgumentException(String.format("Unable to convert string value '%s' to enum type '%s'",
input.s(), enumClass));
}

return returnValue;
}

/**
* Returns the {@link EnhancedType} of the converter.
*
* @return EnhancedType
*/
@Override
public EnhancedType<T> type() {
return EnhancedType.of(enumClass);
}

/**
* Returns the {@link AttributeValueType} of the converter.
*
* @return AttributeValueType
*/
@Override
public AttributeValueType attributeValueType() {
return AttributeValueType.S;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.enhanced.dynamodb.converters.attribute;

import org.junit.jupiter.api.Test;
import software.amazon.awssdk.enhanced.dynamodb.EnumAttributeConverter;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

import static org.assertj.core.api.Assertions.assertThat;

public class EnumAttributeConverterTest {

@Test
public void transformFromDefault_returnsToString() {
EnumAttributeConverter<Vehicle> vehicleConverter = EnumAttributeConverter.create(Vehicle.class);
AttributeValue attribute = vehicleConverter.transformFrom(Vehicle.TRUCK);

assertThat(attribute.s()).isEqualTo("TRUCK");
}

@Test
public void transformToDefault_returnsEnum() {
EnumAttributeConverter<Vehicle> vehicleConverter = EnumAttributeConverter.create(Vehicle.class);

Vehicle bike = vehicleConverter.transformTo(AttributeValue.fromS("BIKE"));

assertThat(bike).isEqualTo(Vehicle.BIKE);
}

@Test
public void transformFromDefault_returnsToString_2() {
EnumAttributeConverter<Animal> animalConverter = EnumAttributeConverter.create(Animal.class);
AttributeValue attribute = animalConverter.transformFrom(Animal.CAT);

assertThat(attribute.s()).isEqualTo("I am a Cat!");
}

@Test
public void transformToDefault_returnsEnum_2() {
EnumAttributeConverter<Animal> animalConverter = EnumAttributeConverter.create(Animal.class);

Animal dog = animalConverter.transformTo(AttributeValue.fromS("I am a Dog!"));

assertThat(dog).isEqualTo(Animal.DOG);
}

@Test
public void transformFromWithNames_returnsName() {
EnumAttributeConverter<Person> personConverter = EnumAttributeConverter.createWithNameAsKeys(Person.class);
AttributeValue attribute = personConverter.transformFrom(Person.JANE);

assertThat(attribute.s()).isEqualTo("JANE");

assertThat(Person.JANE.toString()).isEqualTo("I am a cool person");
}

@Test
public void transformToWithNames_returnsEnum() {
EnumAttributeConverter<Person> personConverter = EnumAttributeConverter.createWithNameAsKeys(Person.class);

Person john = personConverter.transformTo(AttributeValue.fromS("JOHN"));

assertThat(Person.JOHN.toString()).isEqualTo("I am a cool person");

assertThat(john).isEqualTo(Person.JOHN);
}

private static enum Vehicle {
CAR,
BIKE,
TRUCK
}

private static enum Animal {
DOG,
CAT;

@Override
public String toString() {
switch (this) {
case DOG:
return "I am a Dog!";
case CAT:
return "I am a Cat!";
default:
return null;
}
}
}

private static enum Person {
JOHN,
JANE;

@Override
public String toString() {
return "I am a cool person";
}
}
}