diff --git a/source/mir/deser/package.d b/source/mir/deser/package.d index 6b14618..bf6c375 100644 --- a/source/mir/deser/package.d +++ b/source/mir/deser/package.d @@ -775,16 +775,33 @@ template deserializeValue(string[] symbolTable, TableKind tableKind) if (auto exception = impl(data, temporal, table, tableIndex, proxyAnnotations)) 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")) diff --git a/source/mir/ion/examples.d b/source/mir/ion/examples.d index fff59b0..422705e 100644 --- a/source/mir/ion/examples.d +++ b/source/mir/ion/examples.d @@ -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 diff --git a/source/mir/ser/package.d b/source/mir/ser/package.d index 987eb27..a4e9601 100644 --- a/source/mir/ser/package.d +++ b/source/mir/ser/package.d @@ -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 { @@ -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) { @@ -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 { @@ -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