Skip to content

Commit

Permalink
✨ add Vectors converters
Browse files Browse the repository at this point in the history
  • Loading branch information
Odonno committed Aug 14, 2023
1 parent 0d0894c commit 8067a6a
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 1 deletion.
151 changes: 150 additions & 1 deletion SurrealDb.Tests/ParserTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using FluentAssertions.Extensions;
using System.Numerics;
using System.Text;
using System.Text.Json;

namespace SurrealDb.Tests;

Expand All @@ -26,6 +28,9 @@ public class DurationRecord : Record<TimeSpan> { }
public class TimeOnlyRecord : Record<TimeOnly> { }
public class DateTimeRecord : Record<DateTime?> { }
public class DateOnlyRecord : Record<DateOnly?> { }
public class Vector2Record : Record<Vector2?> { }
public class Vector3Record : Record<Vector3?> { }
public class Vector4Record : Record<Vector4?> { }

public class ParserTests
{
Expand Down Expand Up @@ -286,7 +291,7 @@ public async Task ShouldParseDecimal(string url)
{
var decimalRecord = records.Find(r => r.Name == "decimal");
decimalRecord.Should().NotBeNull();
decimalRecord!.Value.Should().Be(41.5M);
decimalRecord!.Value.Should().Be(41.5m);
}

{
Expand Down Expand Up @@ -765,4 +770,148 @@ public async Task ShouldParseDateOnly(string url)
fullNanoRecord!.Value.Should().Be(new DateOnly(2022, 7, 3));
}
}

[Theory]
[InlineData("http://localhost:8000")]
[InlineData("ws://localhost:8000/rpc")]
public async Task ShouldParseVector2(string url)
{
await using var surrealDbClientGenerator = new SurrealDbClientGenerator();
var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo();

string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Schemas/vector.surql");
string fileContent = File.ReadAllText(filePath, Encoding.UTF8);

string query = fileContent;

using var client = surrealDbClientGenerator.Create(url);
await client.SignIn(new RootAuth { Username = "root", Password = "root" });
await client.Use(dbInfo.Namespace, dbInfo.Database);

await client.Query(query);

{
var noneRecord = await client.Select<Vector2Record>("vector", "none");
noneRecord.Should().NotBeNull();
noneRecord!.Value.Should().BeNull();
}

{
Func<Task> act = async () => await client.Select<Vector2Record>("vector", "empty");
await act.Should().ThrowAsync<JsonException>().WithMessage("Cannot deserialize Vector2");
}

{
var vector2Record = await client.Select<Vector2Record>("vector", "vector2");
vector2Record.Should().NotBeNull();
vector2Record!.Value.Should().Be(new Vector2(2.5f, 0.5f));
}

{
var vector3Record = await client.Select<Vector2Record>("vector", "vector3");
vector3Record.Should().NotBeNull();
vector3Record!.Value.Should().Be(new Vector2(4, 9));
}

{
var vector4Record = await client.Select<Vector2Record>("vector", "vector4");
vector4Record.Should().NotBeNull();
vector4Record!.Value.Should().Be(new Vector2(2, 3));
}
}

[Theory]
[InlineData("http://localhost:8000")]
[InlineData("ws://localhost:8000/rpc")]
public async Task ShouldParseVector3(string url)
{
await using var surrealDbClientGenerator = new SurrealDbClientGenerator();
var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo();

string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Schemas/vector.surql");
string fileContent = File.ReadAllText(filePath, Encoding.UTF8);

string query = fileContent;

using var client = surrealDbClientGenerator.Create(url);
await client.SignIn(new RootAuth { Username = "root", Password = "root" });
await client.Use(dbInfo.Namespace, dbInfo.Database);

await client.Query(query);

{
var noneRecord = await client.Select<Vector3Record>("vector", "none");
noneRecord.Should().NotBeNull();
noneRecord!.Value.Should().BeNull();
}

{
Func<Task> act = async () => await client.Select<Vector3Record>("vector", "empty");
await act.Should().ThrowAsync<JsonException>().WithMessage("Cannot deserialize Vector3");
}

{
Func<Task> act = async () => await client.Select<Vector3Record>("vector", "vector2");
await act.Should().ThrowAsync<JsonException>().WithMessage("Cannot deserialize Vector3");
}

{
var vector3Record = await client.Select<Vector3Record>("vector", "vector3");
vector3Record.Should().NotBeNull();
vector3Record!.Value.Should().Be(new Vector3(4, 9, 16));
}

{
var vector4Record = await client.Select<Vector3Record>("vector", "vector4");
vector4Record.Should().NotBeNull();
vector4Record!.Value.Should().Be(new Vector3(2, 3, 4));
}
}

[Theory]
[InlineData("http://localhost:8000")]
[InlineData("ws://localhost:8000/rpc")]
public async Task ShouldParseVector4(string url)
{
await using var surrealDbClientGenerator = new SurrealDbClientGenerator();
var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo();

string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Schemas/vector.surql");
string fileContent = File.ReadAllText(filePath, Encoding.UTF8);

string query = fileContent;

using var client = surrealDbClientGenerator.Create(url);
await client.SignIn(new RootAuth { Username = "root", Password = "root" });
await client.Use(dbInfo.Namespace, dbInfo.Database);

await client.Query(query);

{
var noneRecord = await client.Select<Vector4Record>("vector", "none");
noneRecord.Should().NotBeNull();
noneRecord!.Value.Should().BeNull();
}

{
Func<Task> act = async () => await client.Select<Vector4Record>("vector", "empty");
await act.Should().ThrowAsync<JsonException>().WithMessage("Cannot deserialize Vector4");
}

{
Func<Task> act = async () => await client.Select<Vector4Record>("vector", "vector2");
await act.Should().ThrowAsync<JsonException>().WithMessage("Cannot deserialize Vector4");
}

{
Func<Task> act = async () => await client.Select<Vector4Record>("vector", "vector3");
await act.Should().ThrowAsync<JsonException>().WithMessage("Cannot deserialize Vector4");
}

{
var vector4Record = await client.Select<Vector4Record>("vector", "vector4");
vector4Record.Should().NotBeNull();
vector4Record!.Value.Should().Be(new Vector4(2, 3, 4, 5));
}
}
}
11 changes: 11 additions & 0 deletions SurrealDb.Tests/Schemas/vector.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
DEFINE TABLE vector SCHEMAFULL;

DEFINE FIELD name ON vector TYPE string;
DEFINE FIELD value ON vector TYPE array;
DEFINE FIELD value.* ON vector TYPE number;

CREATE vector:none SET name = "none", value = NONE;
CREATE vector:empty SET name = "empty", value = [];
CREATE vector:vector2 SET name = "vector2", value = [2.5, 0.5];
CREATE vector:vector3 SET name = "vector3", value = [4, 9, 16];
CREATE vector:vector4 SET name = "vector4", value = [2, 3, 4, 5];
35 changes: 35 additions & 0 deletions SurrealDb/Internals/Helpers/VectorConverterHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Globalization;
using System.Numerics;
using System.Text.Json;

namespace SurrealDb.Internals.Helpers;

internal static class VectorConverterHelper
{
public static bool TryReadVectorFromJsonArray(ref Utf8JsonReader reader, byte length, out float[] values)
{
values = new float[length];

for (byte index = 0; index < length; index++)
{
if (!reader.Read())
return false;

values[index] = reader.TokenType switch
{
JsonTokenType.Number => reader.GetSingle(),
JsonTokenType.String => TryParseFloat(reader.GetString()!, out var value)
? value
: throw new JsonException($"Cannot deserialize {nameof(Vector)}{length}"),
_ => throw new JsonException($"Cannot deserialize {nameof(Vector)}{length}")
};
}

return true;
}

public static bool TryParseFloat(string input, out float result)
{
return float.TryParse(input, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
}
}
38 changes: 38 additions & 0 deletions SurrealDb/Internals/Json/Converters/Vector2ValueConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using SurrealDb.Internals.Helpers;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SurrealDb.Internals.Json.Converters;

internal class Vector2ValueConverter : JsonConverter<Vector2>
{
public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.None || reader.TokenType == JsonTokenType.Null)
return default;

const byte VECTOR_LENGTH = 2;

if (reader.TokenType == JsonTokenType.StartArray &&
VectorConverterHelper.TryReadVectorFromJsonArray(ref reader, VECTOR_LENGTH, out var values)
)
#if NET6_0_OR_GREATER
return new Vector2(values);
#else
return new Vector2(values[0], values[1]);
#endif

throw new JsonException($"Cannot deserialize {nameof(Vector2)}");
}

public override void Write(Utf8JsonWriter writer, Vector2 value, JsonSerializerOptions options)
{
writer.WriteStartArray();

writer.WriteNumberValue(value.X);
writer.WriteNumberValue(value.Y);

writer.WriteEndArray();
}
}
39 changes: 39 additions & 0 deletions SurrealDb/Internals/Json/Converters/Vector3ValueConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using SurrealDb.Internals.Helpers;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SurrealDb.Internals.Json.Converters;

internal class Vector3ValueConverter : JsonConverter<Vector3>
{
public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.None || reader.TokenType == JsonTokenType.Null)
return default;

const byte VECTOR_LENGTH = 3;

if (reader.TokenType == JsonTokenType.StartArray &&
VectorConverterHelper.TryReadVectorFromJsonArray(ref reader, VECTOR_LENGTH, out var values)
)
#if NET6_0_OR_GREATER
return new Vector3(values);
#else
return new Vector3(values[0], values[1], values[2]);
#endif

throw new JsonException($"Cannot deserialize {nameof(Vector3)}");
}

public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options)
{
writer.WriteStartArray();

writer.WriteNumberValue(value.X);
writer.WriteNumberValue(value.Y);
writer.WriteNumberValue(value.Z);

writer.WriteEndArray();
}
}
40 changes: 40 additions & 0 deletions SurrealDb/Internals/Json/Converters/Vector4ValueConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using SurrealDb.Internals.Helpers;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SurrealDb.Internals.Json.Converters;

internal class Vector4ValueConverter : JsonConverter<Vector4>
{
public override Vector4 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.None || reader.TokenType == JsonTokenType.Null)
return default;

const byte VECTOR_LENGTH = 4;

if (reader.TokenType == JsonTokenType.StartArray &&
VectorConverterHelper.TryReadVectorFromJsonArray(ref reader, VECTOR_LENGTH, out var values)
)
#if NET6_0_OR_GREATER
return new Vector4(values);
#else
return new Vector4(values[0], values[1], values[2], values[3]);
#endif

throw new JsonException($"Cannot deserialize {nameof(Vector4)}");
}

public override void Write(Utf8JsonWriter writer, Vector4 value, JsonSerializerOptions options)
{
writer.WriteStartArray();

writer.WriteNumberValue(value.X);
writer.WriteNumberValue(value.Y);
writer.WriteNumberValue(value.Z);
writer.WriteNumberValue(value.W);

writer.WriteEndArray();
}
}
3 changes: 3 additions & 0 deletions SurrealDb/Internals/Json/SurrealDbSerializerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public static JsonSerializerOptions CreateJsonSerializerOptions()
new DateOnlyValueConverter(),
new TimeOnlyValueConverter(),
#endif
new Vector2ValueConverter(),
new Vector3ValueConverter(),
new Vector4ValueConverter(),
new SurrealDbResultConverter(),
new SurrealDbWsResponseConverter(),
},
Expand Down

0 comments on commit 8067a6a

Please sign in to comment.