1 /******************************************************************************* 2 * User-Defined Attributes used to tag fields as dproto-serializable 3 * 4 * Authors: Matthew Soucy, msoucy@csh.rit.edu 5 * Date: May 6, 2015 6 * Version: 1.3.0 7 */ 8 module dproto.attributes; 9 10 import dproto.serialize; 11 import painlesstraits : getAnnotation, hasValueAnnotation; 12 import dproto.compat; 13 14 import std.traits : isArray, Identity; 15 import std.typecons : Nullable; 16 17 struct ProtoField 18 { 19 string wireType; 20 ubyte fieldNumber; 21 @disable this(); 22 this(string w, ubyte f) { 23 wireType = w; 24 fieldNumber = f; 25 } 26 @nogc auto header() { 27 return (wireType.msgType | (fieldNumber << 3)); 28 } 29 } 30 31 struct Required {} 32 struct Packed {} 33 34 template TagId(alias T) 35 if(hasValueAnnotation!(T, ProtoField)) 36 { 37 enum TagId = getAnnotation!(T, ProtoField).fieldNumber; 38 } 39 40 template ProtoAccessors() 41 { 42 43 static auto fromProto(R)(auto ref R data) 44 if(isProtoInputRange!R) 45 { 46 auto ret = typeof(this)(); 47 ret.deserialize(data); 48 return ret; 49 } 50 51 public this(R)(auto ref R __data) 52 if(isProtoInputRange!R) 53 { 54 deserialize(__data); 55 } 56 57 ubyte[] serialize() const 58 { 59 import std.array : appender; 60 auto __a = appender!(ubyte[]); 61 serializeTo(__a); 62 return __a.data; 63 } 64 65 void serializeTo(R)(ref R __r) const 66 if(isProtoOutputRange!R) 67 { 68 import dproto.attributes; 69 import std.traits; 70 foreach(__member; ProtoFields!this) { 71 alias __field = Identity!(__traits(getMember, this, __member)); 72 serializeField!__field(__r); 73 } 74 } 75 76 void deserialize(R)(auto ref R __r) 77 if(isProtoInputRange!R) 78 { 79 import std.traits; 80 import dproto.attributes; 81 import painlesstraits : getAnnotation; 82 83 while(!__r.empty()) { 84 auto __msgdata = __r.readVarint(); 85 bool __matched = false; 86 foreach(__member; ProtoFields!this) { 87 alias __field = Identity!(__traits(getMember, this, __member)); 88 alias __fieldData = getAnnotation!(__field, ProtoField); 89 if(__msgdata.msgNum == __fieldData.fieldNumber) { 90 __r.putProtoVal!(__field)(__msgdata); 91 __matched = true; 92 } 93 } 94 if(!__matched) { 95 defaultDecode(__msgdata, __r); 96 } 97 } 98 } 99 100 version(Have_painlessjson) { 101 auto toJson() const { 102 import painlessjson; 103 import std.conv : to; 104 return painlessjson.toJSON(this).to!string; 105 } 106 } 107 } 108 109 template ProtoFields(alias self) 110 { 111 import std.typetuple : Filter, TypeTuple, Erase; 112 113 alias Field(alias F) = Identity!(__traits(getMember, self, F)); 114 alias HasProtoField(alias F) = hasValueAnnotation!(Field!F, ProtoField); 115 alias AllMembers = TypeTuple!(__traits(allMembers, typeof(self))); 116 alias ProtoFields = Filter!(HasProtoField, Erase!("dproto", AllMembers)); 117 } 118 119 template protoDefault(T) { 120 import std.traits : isFloatingPoint; 121 static if(isFloatingPoint!T) { 122 enum protoDefault = 0.0; 123 } else static if(is(T : const string)) { 124 enum protoDefault = ""; 125 } else static if(is(T : const ubyte[])) { 126 enum protoDefault = []; 127 } else { 128 enum protoDefault = T.init; 129 } 130 } 131 132 void serializeField(alias field, R)(ref R r) const 133 if (isProtoOutputRange!R) 134 { 135 alias fieldType = typeof(field); 136 enum fieldData = getAnnotation!(field, ProtoField); 137 // Serialize if required or if the value isn't the (proto) default 138 enum isNullable = is(fieldType == Nullable!U, U); 139 bool needsToSerialize; 140 static if (isNullable) { 141 needsToSerialize = ! field.isNull; 142 } else { 143 needsToSerialize = hasValueAnnotation!(field, Required) 144 || (field != protoDefault!fieldType); 145 } 146 147 // If we still don't need to serialize, we're done here 148 if (!needsToSerialize) 149 { 150 return; 151 } 152 static if(isNullable) { 153 const rawField = field.get; 154 } else { 155 const rawField = field; 156 } 157 158 enum isPacked = hasValueAnnotation!(field, Packed); 159 enum isPackType = is(fieldType == enum) || fieldData.wireType.isBuiltinType; 160 static if (isPacked && isArray!fieldType && isPackType) 161 alias serializer = serializePackedProto; 162 else 163 alias serializer = serializeProto; 164 serializer!fieldData(rawField, r); 165 } 166 167 void putProtoVal(alias __field, R)(auto ref R r, ulong __msgdata) 168 if (isProtoInputRange!R) 169 { 170 import std.range : ElementType, takeExactly, popFrontExactly; 171 import std.array : empty; // Needed if R is an array 172 import std.traits : isSomeString, isDynamicArray; 173 174 alias T = typeof(__field); 175 enum wireType = getAnnotation!(__field, ProtoField).wireType; 176 enum isBinString(T) = Identity!(is(T : const(ubyte)[])); 177 static if (isDynamicArray!T && !(isSomeString!T || isBinString!T)) 178 { 179 ElementType!T u; 180 if (wireType.msgType != PACKED_MSG_TYPE && __msgdata.wireType == PACKED_MSG_TYPE) 181 { 182 ulong nelems = r.readVarint(); 183 auto packeddata = takeExactly(r, nelems); 184 while(!packeddata.empty) 185 { 186 u.putSingleProtoVal!wireType(packeddata); 187 __field ~= u; 188 } 189 r.popFrontExactly(nelems); 190 } 191 else 192 { 193 u.putSingleProtoVal!wireType(r); 194 __field ~= u; 195 } 196 } 197 else 198 { 199 __field.putSingleProtoVal!wireType(r); 200 } 201 } 202 203 void putSingleProtoVal(string wireType, T, R)(ref T t, auto ref R r) 204 if(isProtoInputRange!R) 205 { 206 import std.conv : to; 207 static if(is(T : Nullable!U, U)) { 208 U t_tmp; 209 t_tmp.putSingleProtoVal!wireType(r); 210 t = t_tmp; 211 } else static if(wireType.isBuiltinType) { 212 t = r.readProto!wireType().to!T(); 213 } else static if(is(T == enum)) { 214 t = r.readProto!ENUM_SERIALIZATION().to!T(); 215 } else { 216 auto myData = r.readProto!"bytes"(); 217 return t.deserialize(myData); 218 } 219 } 220 221 void serializeProto(alias fieldData, T, R)(const T data, ref R r) 222 if(isProtoOutputRange!R) 223 { 224 static import dproto.serialize; 225 static if(is(T : const string)) { 226 r.toVarint(fieldData.header); 227 r.writeProto!"string"(data); 228 } else static if(is(T : const(ubyte)[])) { 229 r.toVarint(fieldData.header); 230 r.writeProto!"bytes"(data); 231 } else static if(is(T : const(T)[], T)) { 232 foreach(val; data) { 233 serializeProto!fieldData(val, r); 234 } 235 } else static if(fieldData.wireType.isBuiltinType) { 236 r.toVarint(fieldData.header); 237 enum wt = fieldData.wireType; 238 r.writeProto!(wt)(data); 239 } else static if(is(T == enum)) { 240 r.toVarint(ENUM_SERIALIZATION.msgType | (fieldData.fieldNumber << 3)); 241 r.writeProto!ENUM_SERIALIZATION(data); 242 } else static if(__traits(compiles, data.serialize())) { 243 r.toVarint(fieldData.header); 244 dproto.serialize.CntRange cnt; 245 data.serializeTo(cnt); 246 r.toVarint(cnt.cnt); 247 data.serializeTo(r); 248 } else { 249 static assert(0, "Unknown serialization"); 250 } 251 } 252 253 void serializePackedProto(alias fieldData, T, R)(const T data, ref R r) 254 if(isProtoOutputRange!R) 255 { 256 static assert(fieldData.wireType.isBuiltinType, 257 "Cannot have packed repeated message"); 258 if(data.length) { 259 dproto.serialize.CntRange cnt; 260 static if(is(T == enum)) { 261 enum wt = ENUM_SERIALIZATION.msgType; 262 } else { 263 enum wt = fieldData.wireType; 264 } 265 foreach (ref e; data) 266 cnt.writeProto!wt(e); 267 toVarint(r, PACKED_MSG_TYPE | (fieldData.fieldNumber << 3)); 268 toVarint(r, cnt.cnt); 269 foreach (ref e; data) 270 r.writeProto!wt(e); 271 } 272 }