Skip to content

Commit

Permalink
WIP cont
Browse files Browse the repository at this point in the history
  • Loading branch information
pnwpedro committed Feb 22, 2024
1 parent c2a4e4e commit 086e612
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 91 deletions.
1 change: 1 addition & 0 deletions Fauna.Test/Helpers/Fixtures.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Fauna.Mapping.Attributes;
using Fauna.Types;
using static Fauna.Query;

namespace Fauna.Test;
Expand Down
39 changes: 29 additions & 10 deletions Fauna.Test/Serialization/Deserializer.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public void DeserializeRef()
}

[Test]
public void DeserializeNullRef()
public void DeserializeNullAsNullableDocument()
{
const string given = @"
{
Expand All @@ -305,12 +305,22 @@ public void DeserializeNullRef()
}
}";

var actual = Deserialize<NullDocumentRef>(given);
Assert.AreEqual("123", actual.Id);
Assert.AreEqual(new Module("MyColl"), actual.Collection);
Assert.AreEqual("not found", actual.Cause);
var actual = Deserialize<NullableDocument<Document>>(given);

switch (actual)
{
case NullDocument<Document> d:
Assert.AreEqual("123", d.Id);
Assert.AreEqual("MyColl", d.Collection.Name);
Assert.AreEqual("not found", d.Cause);
break;
default:
Assert.Fail($"result is type: {actual.GetType()}");
break;
}
}


[Test]
public void DeserializeNamedRef()
{
Expand All @@ -328,7 +338,7 @@ public void DeserializeNamedRef()
}

[Test]
public void DeserializeNullNamedRef()
public void DeserializeNullAsNullableNamedDocument()
{
const string given = @"
{
Expand All @@ -340,10 +350,19 @@ public void DeserializeNullNamedRef()
}
}";

var actual = Deserialize<NullNamedDocumentRef>(given);
Assert.AreEqual("RefName", actual.Name);
Assert.AreEqual(new Module("MyColl"), actual.Collection);
Assert.AreEqual("not found", actual.Cause);
var actual = Deserialize<NullableDocument<NamedDocument>>(given);

switch (actual)
{
case NullDocument<NamedDocument> d:
Assert.AreEqual("RefName", d.Id);
Assert.AreEqual("MyColl", d.Collection.Name);
Assert.AreEqual("not found", d.Cause);
break;
default:
Assert.Fail($"result is type: {actual.GetType()}");
break;
}
}

[Test]
Expand Down
19 changes: 19 additions & 0 deletions Fauna/Exceptions/NullDocumentException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Fauna.Types;

namespace Fauna.Exceptions;

internal class NullDocumentException : Exception
{
public string Id { get; }

public Module Collection { get; }

public string Cause { get; }

public NullDocumentException(string message, string id, Module collection, string cause) : base(message)
{
Id = id;
Collection = collection;
Cause = cause;
}
}
9 changes: 4 additions & 5 deletions Fauna/Serialization/CheckedDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ public override T Deserialize(MappingContext context, ref Utf8FaunaReader reader
var tokenType = reader.CurrentTokenType;
var obj = DynamicDeserializer.Singleton.Deserialize(context, ref reader);

if (obj is T v)
return v;
else
throw new SerializationException(
$"Unexpected token while deserializing: {tokenType}");
if (obj is T v) return v;

throw new SerializationException(
$"Expected type {typeof(T)} but received {obj?.GetType()}");
}
}
27 changes: 23 additions & 4 deletions Fauna/Serialization/Deserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public static class Deserializer
private static readonly CheckedDeserializer<Module> _module = new();
private static readonly CheckedDeserializer<Document> _doc = new();
private static readonly CheckedDeserializer<NamedDocument> _namedDoc = new();
private static readonly DocumentDeserializer<NullableDocument<Document>> _nullableDoc = new();
private static readonly DocumentDeserializer<NullableDocument<NamedDocument>> _nullableNamedDoc = new();
private static readonly CheckedDeserializer<DocumentRef> _docRef = new();
private static readonly CheckedDeserializer<NullDocumentRef> _nullDocRef = new();
private static readonly CheckedDeserializer<NamedDocumentRef> _namedDocRef = new();
private static readonly CheckedDeserializer<NullNamedDocumentRef> _nullNamedDocRef = new();

/// <summary>
/// Generates a deserializer for the specified non-nullable .NET type.
Expand Down Expand Up @@ -62,9 +62,28 @@ public static IDeserializer Generate(MappingContext context, Type targetType)
if (targetType == typeof(Document)) return _doc;
if (targetType == typeof(NamedDocument)) return _namedDoc;
if (targetType == typeof(DocumentRef)) return _docRef;
if (targetType == typeof(NullDocumentRef)) return _nullDocRef;
if (targetType == typeof(NamedDocumentRef)) return _namedDocRef;
if (targetType == typeof(NullNamedDocumentRef)) return _nullNamedDocRef;

if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(NullableDocument<>))
{
var argTypes = targetType.GetGenericArguments();
var valueType = argTypes[0];

if (valueType == typeof(Document))
{
var deserType = typeof(NullableDocumentDeserializer<>).MakeGenericType(new[] { valueType });
var deser = Activator.CreateInstance(deserType, new[] { _nullableDoc });

return (IDeserializer)deser!;
}
if (valueType == typeof(NamedDocument))
{
var deserType = typeof(NullableDocumentDeserializer<>).MakeGenericType(new[] { valueType });
var deser = Activator.CreateInstance(deserType, new[] { _nullableNamedDoc });

return (IDeserializer)deser!;
}
}

if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
Expand Down
144 changes: 144 additions & 0 deletions Fauna/Serialization/DocumentDeserializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using Fauna.Mapping;
using Fauna.Types;

namespace Fauna.Serialization;

internal class DocumentDeserializer<T> : BaseDeserializer<T>
{
public override T Deserialize(MappingContext context, ref Utf8FaunaReader reader)
{
return reader.CurrentTokenType switch
{
TokenType.StartObject => DeserializeDocument(context, ref reader),
TokenType.StartRef => DeserializeRef(context, ref reader),
_ => throw new SerializationException(
$"Unexpected token while deserializing into {typeof(NullableDocument<T>)}: {reader.CurrentTokenType}")
};
}

private T DeserializeDocument(MappingContext context, ref Utf8FaunaReader reader)
{
var data = new Dictionary<string, object?>();
string? id = null;
object? name = null;
DateTime? ts = null;
Module? coll = null;

while (reader.Read() && reader.CurrentTokenType != TokenType.EndDocument)
{
if (reader.CurrentTokenType != TokenType.FieldName)
throw new SerializationException(
$"Unexpected token while deserializing into Document: {reader.CurrentTokenType}");

var fieldName = reader.GetString()!;
reader.Read();
switch (fieldName)
{
case "id":
id = reader.GetString();
break;
case "name":
name = DynamicDeserializer.Singleton.Deserialize(context, ref reader);
break;
case "coll":
coll = reader.GetModule();

// if we encounter a mapped collection, jump to the class deserializer.
// NB this relies on the fact that docs on the wire always
// start with id and coll.
if (context.TryGetCollection(coll.Name, out var collInfo))
{
// This assumes ordering on the wire. If name is not null and we're here, then it's a named document so name is a string.
var doc = collInfo.Deserializer.DeserializeDocument(context, id, name != null ? (string)name : null, ref reader);
if (doc is T v) return v;
throw new SerializationException($"Expected type {typeof(T)} but received {doc.GetType()}");
}

break;
case "ts":
ts = reader.GetTime();
break;
default:
data[fieldName] = DynamicDeserializer.Singleton.Deserialize(context, ref reader);
break;
}
}

if (id != null && coll != null && ts != null)
{
if (name != null) data["name"] = name;
var r = new Document(id, coll, ts.GetValueOrDefault(), data);
if (r is T d) return d;
var nr = (NullableDocument<Document>)new NonNullDocument<Document>(r);
if (nr is T nnd) return nnd;
}

if (name != null && coll != null && ts != null)
{
// If we're here, name is a string.
var r = new NamedDocument((string)name, coll, ts.GetValueOrDefault(), data);
if (r is T d) return d;
var nr = (NullableDocument<NamedDocument>)new NonNullDocument<NamedDocument>(r);
if (nr is T nnd) return nnd;
}

throw new SerializationException("Unsupported document type.");
}

private T DeserializeRef(MappingContext context, ref Utf8FaunaReader reader)
{
string? id = null;
string? name = null;
Module? coll = null;
string? cause = null;
var exists = true;

while (reader.Read() && reader.CurrentTokenType != TokenType.EndRef)
{
if (reader.CurrentTokenType != TokenType.FieldName)
throw new SerializationException(
$"Unexpected token while deserializing into DocumentRef: {reader.CurrentTokenType}");

var fieldName = reader.GetString()!;
reader.Read();
switch (fieldName)
{
case "id":
id = reader.GetString();
break;
case "name":
name = reader.GetString();
break;
case "coll":
coll = reader.GetModule();
break;
case "cause":
cause = reader.GetString();
break;
case "exists":
exists = reader.GetBoolean();
break;
}
}

if ((id != null || name != null) && coll != null && exists)
{
throw new SerializationException($"Expected a document but received a ref: {id ?? name} in {coll.Name}");
}

if ((id != null || name != null) && coll != null && !exists)
{
var ty = typeof(T);
if (ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(NullableDocument<>))
{
var inner = ty.GetGenericArguments()[0];
var nty = typeof(NullDocument<>).MakeGenericType(inner);
var r = Activator.CreateInstance(nty, (id ?? name)!, coll, cause!);
return (T)r!;
}
}

throw new SerializationException("Unsupported reference type");
}

}
Loading

0 comments on commit 086e612

Please sign in to comment.