Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Commit

Permalink
Setting up the ORM (#1738)
Browse files Browse the repository at this point in the history
* setting up orm

* Add unit tests

* Added support for object serilization
added tests

* moving stuff aroung

* Fix handling of enum flags cases
removing dependency to CaseExtension
Adding more tests

* add support for property rename

* caching EntityInfo

* remove access to environment variable

* formatting

* renaming dbName to columnName
  • Loading branch information
chkeita authored Apr 4, 2022
1 parent fbff3fc commit 26911c8
Show file tree
Hide file tree
Showing 6 changed files with 655 additions and 19 deletions.
19 changes: 0 additions & 19 deletions src/ApiService/ApiService/QueueNodeHearbeat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,6 @@ record NodeHeartbeatEntry(string NodeId, Dictionary<string, HeartbeatType>[] dat
public class QueueNodeHearbeat
{

private (string, string) GetStorageAccountNameAndKey(string? accountId)
{
//TableEntity
return ("test", "test");
}

private async Task<TableServiceClient> GetStorageClient(string? table, string? accounId)
{
accounId ??= System.Environment.GetEnvironmentVariable("ONEFUZZ_FUNC_STORAGE");
if (accounId == null)
{
throw new Exception("ONEFUZZ_FUNC_STORAGE environment variable not set");
}
var (name, key) = GetStorageAccountNameAndKey(accounId);
var tableClient = new TableServiceClient(new Uri(accounId), new TableSharedKeyCredential(name, key));
await tableClient.CreateTableIfNotExistsAsync(table);
return tableClient;
}

private readonly ILogger _logger;

public QueueNodeHearbeat(ILoggerFactory loggerFactory)
Expand Down
54 changes: 54 additions & 0 deletions src/ApiService/ApiService/onefuzzlib/orm/CaseConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace ApiService.onefuzzlib.orm
{
public class CaseConverter
{
/// get the start indices of each word and the lat indice
static IEnumerable<int> getIndices(string input)
{

yield return 0;
for (var i = 1; i < input.Length; i++)
{

if (Char.IsDigit(input[i]))
{
continue;
}

if (i < input.Length - 1 && Char.IsDigit(input[i + 1]))
{
continue;
}

// is the current letter uppercase
if (Char.IsUpper(input[i]))
{
if (i < input.Length - 1 && !Char.IsUpper(input[i + 1]))
{
yield return i;
}
else if (!Char.IsUpper(input[i - 1]))
{
yield return i;
}
}
}
yield return input.Length;
}

public static string PascalToSnake(string input)
{
var indices = getIndices(input).ToArray();
return string.Join("_", Enumerable.Zip(indices, indices.Skip(1)).Select(x => input.Substring(x.First, x.Second - x.First).ToLowerInvariant()));

}
public static string SnakeToPascal(string input)
{
return string.Join("", input.Split('_', StringSplitOptions.RemoveEmptyEntries).Select(x => $"{Char.ToUpper(x[0])}{x.Substring(1)}"));
}
}
}
167 changes: 167 additions & 0 deletions src/ApiService/ApiService/onefuzzlib/orm/CustomConverterFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ApiService.onefuzzlib.orm
{
public sealed class CustomEnumConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;

public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
object[]? knownValues = null;

if (typeToConvert == typeof(BindingFlags))
{
knownValues = new object[] { BindingFlags.CreateInstance | BindingFlags.DeclaredOnly };
}

return (JsonConverter)Activator.CreateInstance(
typeof(CustomEnumConverter<>).MakeGenericType(typeToConvert),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object?[] { options.PropertyNamingPolicy, options, knownValues },
culture: null)!;
}
}

public sealed class CustomEnumConverter<T> : JsonConverter<T> where T : Enum
{
private readonly JsonNamingPolicy _namingPolicy;

private readonly Dictionary<string, T> _readCache = new();
private readonly Dictionary<T, JsonEncodedText> _writeCache = new();

// This converter will only support up to 64 enum values (including flags) on serialization and deserialization
private const int NameCacheLimit = 64;

private const string ValueSeparator = ",";

public CustomEnumConverter(JsonNamingPolicy namingPolicy, JsonSerializerOptions options, object[]? knownValues)
{
_namingPolicy = namingPolicy;

bool continueProcessing = true;
for (int i = 0; i < knownValues?.Length; i++)
{
if (!TryProcessValue((T)knownValues[i]))
{
continueProcessing = false;
break;
}
}

if (continueProcessing)
{
Array values = Enum.GetValues(typeof(T));

for (int i = 0; i < values.Length; i++)
{
T value = (T)values.GetValue(i)!;

if (!TryProcessValue(value))
{
break;
}
}
}

bool TryProcessValue(T value)
{
if (_readCache.Count == NameCacheLimit)
{
Debug.Assert(_writeCache.Count == NameCacheLimit);
return false;
}

FormatAndAddToCaches(value, options.Encoder);
return true;
}
}

public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string? json;

if (reader.TokenType != JsonTokenType.String || (json = reader.GetString()) == null)
{
throw new JsonException();
}

var value = json.Split(ValueSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(x =>
{
if (!_readCache.TryGetValue(x, out T value))
{
throw new JsonException();
}
return value;

}).ToArray();

if (value.Length == 1)
{
return value[0];
}

var result = default(T);

return (T)(object)value.Aggregate(0, (state, value) => (int)(object)state | (int)(object)value);
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (!_writeCache.TryGetValue(value, out JsonEncodedText formatted))
{
if (_writeCache.Count == NameCacheLimit)
{
Debug.Assert(_readCache.Count == NameCacheLimit);
throw new ArgumentOutOfRangeException();
}

formatted = FormatAndAddToCaches(value, options.Encoder);
}

writer.WriteStringValue(formatted);
}

private JsonEncodedText FormatAndAddToCaches(T value, JavaScriptEncoder? encoder)
{
(string valueFormattedToStr, JsonEncodedText valueEncoded) = FormatEnumValue(value.ToString(), _namingPolicy, encoder);
_readCache[valueFormattedToStr] = value;
_writeCache[value] = valueEncoded;
return valueEncoded;
}

private ValueTuple<string, JsonEncodedText> FormatEnumValue(string value, JsonNamingPolicy namingPolicy, JavaScriptEncoder? encoder)
{
string converted;

if (!value.Contains(ValueSeparator))
{
converted = namingPolicy.ConvertName(value);
}
else
{
// todo: optimize implementation here by leveraging https://github.com/dotnet/runtime/issues/934.
string[] enumValues = value.Split(ValueSeparator);

for (int i = 0; i < enumValues.Length; i++)
{
enumValues[i] = namingPolicy.ConvertName(enumValues[i].Trim());
}

converted = string.Join(ValueSeparator, enumValues);
}

return (converted, JsonEncodedText.Encode(converted, encoder));
}
}
}

Loading

0 comments on commit 26911c8

Please sign in to comment.