Skip to content
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

Implement UPPER_SNAKE_CASE enum converter for control plane API #223

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Text.Json;
using EfficientDynamoDb.Internal.JsonConverters;
using EfficientDynamoDb.Operations.DescribeTable.Models.Enums;
using NUnit.Framework;

namespace EfficientDynamoDb.Tests.Internal.JsonConverters
{
[TestFixture]
public class DdbEnumJsonConverter
{
private readonly JsonSerializerOptions _options = new JsonSerializerOptions
{ Converters = { new DdbEnumJsonConverterFactory() } };

[TestCase(KeyType.Hash, "HASH")]
[TestCase(SseType.Kms, "KMS")]
[TestCase(SseType.Aes256, "AES256")]

Check warning on line 17 in EfficientDynamoDb.Tests/Internal/JsonConverters/DdbEnumJsonConverter.cs

View workflow job for this annotation

GitHub Actions / build

'SseType.Aes256' is obsolete: 'Not supported according to DDB docs'

Check warning on line 17 in EfficientDynamoDb.Tests/Internal/JsonConverters/DdbEnumJsonConverter.cs

View workflow job for this annotation

GitHub Actions / build

'SseType.Aes256' is obsolete: 'Not supported according to DDB docs'
[TestCase(StreamViewType.NewAndOldImages, "NEW_AND_OLD_IMAGES")]
[TestCase(StreamViewType.KeysOnly, "KEYS_ONLY")]
public void EnumDeserializationTest<T>(T enumValue, string jsonValue) where T : struct, Enum
{
var json = $"\"{jsonValue}\"";

var result = JsonSerializer.Deserialize<T>(json, _options);

Assert.That(result, Is.EqualTo(enumValue));
}

[TestCase(KeyType.Hash, "HASH")]
[TestCase(SseType.Kms, "KMS")]
[TestCase(SseType.Aes256, "AES256")]

Check warning on line 31 in EfficientDynamoDb.Tests/Internal/JsonConverters/DdbEnumJsonConverter.cs

View workflow job for this annotation

GitHub Actions / build

'SseType.Aes256' is obsolete: 'Not supported according to DDB docs'

Check warning on line 31 in EfficientDynamoDb.Tests/Internal/JsonConverters/DdbEnumJsonConverter.cs

View workflow job for this annotation

GitHub Actions / build

'SseType.Aes256' is obsolete: 'Not supported according to DDB docs'
[TestCase(StreamViewType.NewAndOldImages, "NEW_AND_OLD_IMAGES")]
[TestCase(StreamViewType.KeysOnly, "KEYS_ONLY")]
public void EnumSerializationTest<T>(T enumValue, string expectedJsonValue) where T : struct, Enum
{
var result = JsonSerializer.Serialize(enumValue, _options);
var expectedJson = $"\"{expectedJsonValue}\"";

Assert.That(result, Is.EqualTo(expectedJson));
}
}
}
2 changes: 1 addition & 1 deletion src/Benchmarks/Benchmarks/Query/QueryBenchmarkBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public abstract class QueryBenchmarkBase
_describeTableBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new DescribeTableResponse(new TableDescription
{
TableName = "production_" + Tables.TestTable,
KeySchema = new[] {new KeySchemaElement("pk", KeyType.HASH), new KeySchemaElement("sk", KeyType.RANGE)},
KeySchema = new[] {new KeySchemaElement("pk", KeyType.Hash), new KeySchemaElement("sk", KeyType.Range)},
AttributeDefinitions = new[] {new AttributeDefinition("pk", "S"), new AttributeDefinition("sk", "S")}
}), new JsonSerializerOptions
{
Expand Down
2 changes: 1 addition & 1 deletion src/Benchmarks/Mocks/DescribeTableResponseFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static byte[] CreateResponse()
return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new DescribeTableResponse(new TableDescription
{
TableName = "production_" + Tables.TestTable,
KeySchema = new[] {new KeySchemaElement("pk", EfficientDynamoDb.Operations.DescribeTable.Models.Enums.KeyType.HASH), new KeySchemaElement("sk", KeyType.RANGE)},
KeySchema = new[] {new KeySchemaElement("pk", EfficientDynamoDb.Operations.DescribeTable.Models.Enums.KeyType.Hash), new KeySchemaElement("sk", KeyType.Range)},
AttributeDefinitions = new[] {new AttributeDefinition("pk", "S"), new AttributeDefinition("sk", "S")}
}), new JsonSerializerOptions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ private async ValueTask<HttpContent> BuildHttpContentAsync(UpdateItemRequest req
.ConfigureAwait(false);

var keySchema = response.Table.KeySchema;
return (keySchema.First(x => x.KeyType == KeyType.HASH).AttributeName,
keySchema.FirstOrDefault(x => x.KeyType == KeyType.RANGE)?.AttributeName);
return (keySchema.First(x => x.KeyType == KeyType.Hash).AttributeName,
keySchema.FirstOrDefault(x => x.KeyType == KeyType.Range)?.AttributeName);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace EfficientDynamoDb.Internal.Extensions
{
internal static class DateTimeConverterExtensions
{
public static long ToUnixSeconds(this DateTime dateTime) => (long) (dateTime - UnixEpochStart).TotalSeconds;
public static double ToUnixSeconds(this DateTime dateTime) => (dateTime - UnixEpochStart).TotalSeconds;

public static DateTime FromUnixSeconds(this double seconds) => UnixEpochStart.AddSeconds(seconds);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using EfficientDynamoDb.Internal.Core;

namespace EfficientDynamoDb.Internal.Extensions
{
internal static class StringNormalizationExtensions
Expand Down Expand Up @@ -39,5 +41,19 @@ public static string NormalizeWhiteSpace(this string self)

return new string(output, 0, currentIndex);
}

/// <summary>
/// Converts the string to UPPER_SNAKE_CASE and appends it to the <paramref name="builder"/>.
/// </summary>
public static void ToUpperSnakeCase(this string self, ref NoAllocStringBuilder builder)
{
for (var i = 0; i < self.Length; i++)
{
var c = self[i];
if (i != 0 && char.IsUpper(c))
builder.Append("_");
builder.Append(char.ToUpperInvariant(c));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using EfficientDynamoDb.Internal.Core;
using EfficientDynamoDb.Internal.Extensions;
using EfficientDynamoDb.Internal.TypeParsers;

namespace EfficientDynamoDb.Internal.JsonConverters
Expand All @@ -11,17 +13,21 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
{
var enumString = reader.GetString();

if (!EnumParser.TryParseCaseInsensitive(enumString, out T value))
{
return default;
}

return value;
return EnumParser.TryParseUpperSnakeCase(enumString, out T value)
? value
: default;
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
var enumString = value.ToString();

Span<char> buffer = stackalloc char[enumString.Length * 2]; // Allocate enough space to account for new underscores
var sb = new NoAllocStringBuilder(in buffer, true);

enumString.ToUpperSnakeCase(ref sb);

writer.WriteStringValue(sb.GetBuffer());
}
}
}
29 changes: 29 additions & 0 deletions src/EfficientDynamoDb/Internal/TypeParsers/EnumParser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using EfficientDynamoDb.Internal.Core;

namespace EfficientDynamoDb.Internal.TypeParsers
{
Expand All @@ -8,5 +9,33 @@ public static class EnumParser
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseCaseInsensitive<TEnum>(string? value, out TEnum result) where TEnum : struct, Enum =>
Enum.TryParse(value, out result) || Enum.TryParse(value, true, out result);

public static bool TryParseUpperSnakeCase<TEnum>(string? value, out TEnum result) where TEnum : struct, Enum
{
if (value == null)
{
result = default;
return false;
}

Span<char> buffer = stackalloc char[value.Length];
var sb = new NoAllocStringBuilder(in buffer, true);

var isNextUpper = false;
foreach (var c in value)
{
if (c == '_')
{
isNextUpper = true;
continue;
}

var nextChar = isNextUpper ? c : char.ToLowerInvariant(c);
sb.Append(nextChar);
isNextUpper = false;
}

return Enum.TryParse(sb.ToString(), true, out result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum BillingMode
{
_UNKNOWN = 0,
PROVISIONED = 10,
PAY_PER_REQUEST = 20
Undefined = 0,
Provisioned = 10,
PayPerRequest = 20
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum IndexStatus
{
_UNKNOWN = 0,
CREATING = 10,
UPDATING = 20,
DELETING = 30,
ACTIVE = 40
Undefined = 0,
Creating = 10,
Updating = 20,
Deleting = 30,
Active = 40
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum KeyType
{
_UNKNOWN = 0,
HASH = 10,
RANGE = 20
Undefined = 0,
Hash = 10,
Range = 20
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum ProjectionType
{
_UNKNOWN = 0,
KEYS_ONLY = 10,
INCLUDE = 20,
ALL = 30
Undefined = 0,
KeysOnly = 10,
Include = 20,
All = 30
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum ReplicaStatus
{
_UNKNOWN = 0,
CREATING = 10,
UPDATING = 20,
DELETING = 30,
ACTIVE = 40,
REGION_DISABLED = 50
Undefined = 0,
Creating = 10,
Updating = 20,
Deleting = 30,
Active = 40,
RegionDisabled = 50
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum SseStatus
{
_UNKNOWN = 0,
ENABLED = 10,
Undefined = 0,
Enabled = 10,
[Obsolete("Not supported according to DDB docs")]
ENABLING = 20,
Enabling = 20,
[Obsolete("Not supported according to DDB docs")]
DISABLED = 30,
Disabled = 30,
[Obsolete("Not supported according to DDB docs")]
DISABLING = 40,
UPDATING = 50
Disabling = 40,
Updating = 50
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum SseType
{
_UNKNOWN = 0,
KMS = 10,
Undefined = 0,
Kms = 10,
[Obsolete("Not supported according to DDB docs")]
AES256 = 20
Aes256 = 20
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum StreamViewType
{
_UNKNOWN = 0,
KEYS_ONLY = 10,
NEW_IMAGE = 20,
OLD_IMAGE = 30,
NEW_AND_OLD_IMAGES = 40
Undefined = 0,
KeysOnly = 10,
NewImage = 20,
OldImage = 30,
NewAndOldImages = 40
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ namespace EfficientDynamoDb.Operations.DescribeTable.Models.Enums
{
public enum TableStatus
{
_UNKNOWN = 0,
CREATING = 10,
UPDATING = 20,
DELETING = 30,
ACTIVE = 40,
INACCESSIBLE_ENCRYPTION_CREDENTIALS = 50,
ARCHIVING = 60,
ARCHIVED = 70,
Undefined = 0,
Creating = 10,
Updating = 20,
Deleting = 30,
Active = 40,
InaccessibleEncryptionCredentials = 50,
Archiving = 60,
Archived = 70,
}
}
Loading