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

Support @serdeEnumProxy (@serdeProxyCast) UDA #22

Merged
merged 2 commits into from
Oct 2, 2022
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
33 changes: 25 additions & 8 deletions source/mir/deser/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -802,16 +802,33 @@ template deserializeValue(string[] symbolTable, TableKind tableKind, bool annota
if (auto exception = impl(data, temporal, runtimeSymbolTable, compiletimeIndex, annotations_))
return exception;

static if (__traits(compiles, ()@safe{return to!T(move(temporal));}))
value = to!T(move(temporal));
static if (hasUDA!(T, serdeProxyCast))
{
static if (__traits(compiles, ()@safe{return cast(T)temporal;}))
value = cast(T)temporal;
else
{
pragma(msg, "Mir warning: can't safely cast from "
~ (const V).stringof
~ " to "
~ (const Proxy).stringof
);
value = ()@trusted{return cast(T)temporal;}();
}
}
else
{
pragma(msg, "Mir warning: can't safely cast from "
~ (const V).stringof
~ " to "
~ (const Proxy).stringof
);
value = ()@trusted{return to!T(move(temporal));}();
static if (__traits(compiles, ()@safe{return to!T(move(temporal));}))
value = to!T(move(temporal));
else
{
pragma(msg, "Mir warning: can't safely cast from "
~ (const V).stringof
~ " to "
~ (const Proxy).stringof
);
value = ()@trusted{return to!T(move(temporal));}();
}
}
}
static if(__traits(hasMember, T, "serdeFinalize"))
Expand Down
151 changes: 151 additions & 0 deletions source/mir/ion/examples.d
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,157 @@ version(unittest) private
}
}

/// enums
version(mir_ion_test) unittest
{
import std.exception : assertThrown;
import mir.serde: serdeProxy;

enum Builtin
{
one = 1,
two = 2
}

enum BuiltinString : string
{
one = "eins",
two = "zwei"
}

@serdeProxy!int
enum ByInt
{
one = 1,
two = 2
}

@serdeProxy!string
enum StringEnum : string
{
one = "eins",
two = "zwei"
}

static struct B
{
Builtin builtin;
BuiltinString bstr;
}

static struct P
{
ByInt byInt;
StringEnum str;
}

import mir.ser.json : serializeJson;
import mir.deser.json : deserializeJson;

assert(`{"builtin":"one","bstr":"one"}`.deserializeJson!B
== B(Builtin.one, BuiltinString.one));
assert(`{"builtin":"two","bstr":"two"}`.deserializeJson!B
== B(Builtin.two, BuiltinString.two));
assertThrown(`{"builtin":"three","bstr":"three"}`.deserializeJson!B);

assert(`{"byInt":1,"str":"eins"}`.deserializeJson!P
== P(ByInt.one, StringEnum.one));
assert(`{"byInt":2,"str":"zwei"}`.deserializeJson!P
== P(ByInt.two, StringEnum.two));
assertThrown(`{"byInt":2,"str":"drei"}`.deserializeJson!P);
// asserts are a little inconsistent with integral values
assert(`{"byInt":3,"str":"zwei"}`.deserializeJson!P
== P(cast(ByInt)3, StringEnum.two));


assert(B().serializeJson
== `{"builtin":"one","bstr":"one"}`);
assert(P().serializeJson
== `{"byInt":1,"str":"eins"}`);

// serializing invalid enum values may cause AssertError or other errors,
// so avoid doing it altogether.
assertThrown!Throwable(B(cast(Builtin)2, cast(BuiltinString)"drei").serializeJson);
assertThrown!Throwable(B(cast(Builtin)3, BuiltinString.two).serializeJson);
// Fine with @serdeProxy!T with serialization, although inconsistent.
// Use @serdeEnumProxy!T or add @serdeProxyCast to your serdeProxy annotated value
// to also support serialization and deserialization through the underlying type.
assert(P(cast(ByInt)2, cast(StringEnum)"drei").serializeJson
== `{"byInt":2,"str":"StringEnum(drei)"}`);
assert(P(cast(ByInt)3, StringEnum.two).serializeJson
== `{"byInt":3,"str":"zwei"}`);
}

/// ditto
version(mir_ion_test) unittest
{
import mir.serde: serdeEnumProxy;

@serdeEnumProxy!int
enum IntE
{
one = 1,
two = 2
}

@serdeEnumProxy!string
enum StrE : string
{
one = "eins",
two = "zwei"
}

static struct S
{
IntE a;
StrE b;
}

import mir.ser.json : serializeJson;
import mir.deser.json : deserializeJson;

// same as regular serdeProxy so far
assert(`{"a":1,"b":"eins"}`.deserializeJson!S == S(IntE.one, StrE.one));
assert(`{"a":2,"b":"zwei"}`.deserializeJson!S == S(IntE.two, StrE.two));

assert(`{"a":1,"b":"eins"}` == S(IntE.one, StrE.one).serializeJson);
assert(`{"a":2,"b":"zwei"}` == S(IntE.two, StrE.two).serializeJson);

// but allows any underlying values
assert(`{"a":3,"b":"drei"}`.deserializeJson!S
== S(cast(IntE)3, cast(StrE)"drei"));
assert(`{"a":3,"b":"drei"}`
== S(cast(IntE)3, cast(StrE)"drei").serializeJson,
S(cast(IntE)3, cast(StrE)"drei").serializeJson);
}

version(mir_ion_test) unittest
{
import mir.serde: serdeEnumProxy;
import mir.algebraic: Nullable, nullable;

@serdeEnumProxy!string
enum StrE : string
{
one = "eins",
two = "zwei"
}

static struct S
{
Nullable!(StrE[]) a;
}

import mir.ser.json : serializeJson;
import mir.deser.json : deserializeJson;

auto s = S([StrE.one, StrE.two, cast(StrE)"drei"].nullable);
auto str = `{"a":["eins","zwei","drei"]}`;

assert(str.deserializeJson!S == s);
assert(s.serializeJson == str, str);
}

// check symbols support
// check typed nullable support
// check void support
Expand Down
17 changes: 14 additions & 3 deletions source/mir/ser/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ void serializeValue(S, V)(scope ref S serializer, scope const V value)
{
static if (hasUDA!(V, serdeProxy))
{
serializer.serializeWithProxy!(serdeGetProxy!V)(value);
serializeProxyCastImpl!(S, V)(serializer, value);
}
else
{
Expand Down Expand Up @@ -135,6 +135,17 @@ unittest
assert(rcstring[] == "FOO");
}

private static void serializeProxyCastImpl(S, alias U, V)(scope ref S serializer, scope const V value)
{
static if (hasUDA!(U, serdeProxyCast))
{
scope casted = cast(serdeGetProxy!U)value;
serializeValue(serializer, casted);
}
else
serializer.serializeWithProxy!(serdeGetProxy!U)(value);
}

/// String serialization
void serializeValue(S)(scope ref S serializer, scope const(char)[] value)
{
Expand Down Expand Up @@ -593,7 +604,7 @@ private void serializeValueImpl(S, V)(scope ref S serializer, scope ref const V
else
static if(hasUDA!(__traits(getMember, value, member), serdeProxy))
{
serializer.serializeWithProxy!(serdeGetProxy!(__traits(getMember, value, member)))(val);
serializeProxyCastImpl!(S, __traits(getMember, value, member))(serializer, val);
}
else
{
Expand Down Expand Up @@ -983,7 +994,7 @@ void serializeValue(S, V)(scope ref S serializer, scope ref const V value) @safe
else
static if (hasUDA!(V, serdeProxy))
{
serializer.serializeWithProxy!(serdeGetProxy!V)(value);
serializeProxyCastImpl!(S, V)(serializer, value);
return;
}
else
Expand Down