Skip to content

Commit

Permalink
Merge pull request #66 from msoucy/nullable
Browse files Browse the repository at this point in the history
Nullable
  • Loading branch information
msoucy committed Feb 23, 2016
2 parents 5c82034 + 63e1566 commit 4f06df0
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 9 deletions.
26 changes: 22 additions & 4 deletions import/dproto/attributes.d
Expand Up @@ -12,6 +12,7 @@ import painlesstraits : getAnnotation, hasValueAnnotation;
import dproto.compat;

import std.traits : Identity;
import std.typecons : Nullable;

struct ProtoField
{
Expand Down Expand Up @@ -132,20 +133,33 @@ void serializeField(alias field, R)(ref R r) const
alias fieldType = typeof(field);
enum fieldData = getAnnotation!(field, ProtoField);
// Serialize if required or if the value isn't the (proto) default
bool needsToSerialize = hasValueAnnotation!(field, Required) ||
(field != protoDefault!fieldType);
enum isNullable = is(fieldType == Nullable!U, U);
bool needsToSerialize;
static if (isNullable) {
needsToSerialize = ! field.isNull;
} else {
needsToSerialize = hasValueAnnotation!(field, Required)
|| (field != protoDefault!fieldType);
}

// If we still don't need to serialize, we're done here
if (!needsToSerialize)
{
return;
}
static if(isNullable) {
const rawField = field.get;
} else {
const rawField = field;
}

enum isPacked = hasValueAnnotation!(field, Packed);
enum isPackType = is(fieldType == enum) || fieldData.wireType.isBuiltinType;
static if (isPacked && isArray!fieldType && isPackType)
alias serializer = serializePackedProto;
else
alias serializer = serializeProto;
serializer!fieldData(field, r);
serializer!fieldData(rawField, r);
}

void putProtoVal(string wireType, T, R)(ref T t, auto ref R r)
Expand All @@ -164,7 +178,11 @@ void putSingleProtoVal(string wireType, T, R)(ref T t, auto ref R r)
if(isProtoInputRange!R)
{
import std.conv : to;
static if(wireType.isBuiltinType) {
static if(is(T : Nullable!U, U)) {
U t_tmp;
t_tmp.putSingleProtoVal!wireType(r);
t = t_tmp;
} else static if(wireType.isBuiltinType) {
t = r.readProto!wireType().to!T();
} else static if(is(T == enum)) {
t = r.readProto!ENUM_SERIALIZATION().to!T();
Expand Down
36 changes: 31 additions & 5 deletions import/dproto/intermediate.d
Expand Up @@ -167,6 +167,13 @@ struct Field {
uint id;
Options options;

const bool hasDefaultValue() {
return null != ("default" in options);
}
const string defaultValue() {
return options["default"];
}

const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
{
switch(fmt.spec) {
Expand Down Expand Up @@ -204,15 +211,35 @@ struct Field {
sink.formattedWrite(`("%s", %s)`, type, id);
sink(")\n");

bool wrap_with_nullable =
requirement == Requirement.OPTIONAL &&
! type.isBuiltinType();

if(wrap_with_nullable) {
sink(`dproto.serialize.PossiblyNullable!(`);
}
string typestr = type;
if(type.isBuiltinType) {
sink.formattedWrite(`BuffType!"%s"`, type);
} else {
sink(type);
typestr = format(`BuffType!"%s"`, type);
}

sink(typestr);

if(wrap_with_nullable) {
sink(`)`);
}
if(requirement == Requirement.REPEATED) {
sink("[]");
}
sink.formattedWrite(" %s;\n\n", name);
sink.formattedWrite(" %s", name);
if (requirement != Requirement.REPEATED) {
if (hasDefaultValue) {
sink.formattedWrite(`= SpecifiedDefaultValue!(%s, "%s")`, typestr, defaultValue);
} else if(type.isBuiltinType || ! wrap_with_nullable) {
sink.formattedWrite("= UnspecifiedDefaultValue!(%s)", typestr);
}
}
sink(";\n\n");
}
void getCase(scope void delegate(const(char)[]) sink) const {
sink.formattedWrite("case %s:\n", id);
Expand Down Expand Up @@ -293,5 +320,4 @@ struct Service {
foreach(m; rpc) m.toString(sink, fmt);
sink("}\n");
}

}
23 changes: 23 additions & 0 deletions import/dproto/serialize.d
Expand Up @@ -40,6 +40,29 @@ unittest {
assert(isBuiltinType("quad") == false);
}

template PossiblyNullable(T) {
static if(is(T == enum)) {
alias PossiblyNullable = T;
} else {
import std.typecons : Nullable;
alias PossiblyNullable = Nullable!T;
}
}

template UnspecifiedDefaultValue(T) {
static if(is(T == enum)) {
import std.traits : EnumMembers;
enum UnspecifiedDefaultValue = EnumMembers!(T)[0];
} else {
enum UnspecifiedDefaultValue = T.init;
}
}

template SpecifiedDefaultValue(T, string value) {
import std.conv : to;
enum SpecifiedDefaultValue = to!T(value);
}

/*******************************************************************************
* Maps the given type string to the data type it represents
*/
Expand Down
44 changes: 44 additions & 0 deletions import/dproto/unittests.d
Expand Up @@ -668,11 +668,55 @@ unittest
auto acct = Account();
auto main = Character();
main.name = "Hogan";
main.stats = Stats();
main.stats.agility = agility;
acct.main = main;
auto ser = acct.serialize();
Account acct_rx;
acct_rx.deserialize(ser);
import std.string : format;
assert(acct_rx.main.stats.agility == agility, format("Expected %d, got %d", agility, acct_rx.main.stats.agility));

}

unittest
{
enum pbstring = q{
enum Enum {
A = 0;
B = 1;
C = 2;
}

message Msg {
optional Enum unset = 1;
optional Enum isset_first = 2 [default = A];
optional Enum isset_last = 3 [default = C];
required Enum unset_required = 4;
required Enum isset_required = 5 [default = B];
optional int32 i1 = 6 [default = 42];
optional int32 i2 = 7;
required int32 i3 = 8 [default = 24];
required int32 i4 = 9;
}
};

// Force code coverage in doveralls
import std.string;
import std.format;
import dproto.parse;
auto normalizedServiceDefinition = "%3.3p".format(ParseProtoSchema("<none>", pbstring));

mixin ProtocolBufferFromString!pbstring;

Msg msg;
assert(msg.unset == Enum.A);
assert(msg.isset_first == Enum.A);
assert(msg.isset_last == Enum.C);
assert(msg.unset_required == Enum.A);
assert(msg.isset_required == Enum.B);
assert(msg.i1 == 42);
assert(msg.i2 == typeof(msg.i2).init);
assert(msg.i3 == 24);
assert(msg.i4 == typeof(msg.i4).init);
}

0 comments on commit 4f06df0

Please sign in to comment.