1 /******************************************************************************* 2 * User-Defined Attributes used to tag fields as dproto-serializable 3 * 4 * Authors: Matthew Soucy, dproto@msoucy.me 5 */ 6 module dproto.attributes; 7 8 import dproto.serialize; 9 import painlesstraits : getAnnotation, hasValueAnnotation; 10 import dproto.compat; 11 12 import std.traits : isArray; 13 import std.typecons : Nullable; 14 15 alias Identity(alias A) = A; 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 foreach(__member; ProtoFields!this) { 70 alias __field = Identity!(__traits(getMember, this, __member)); 71 serializeField!__field(__r); 72 } 73 } 74 75 void deserialize(R)(auto ref R __r) 76 if(isProtoInputRange!R) 77 { 78 import dproto.attributes; 79 import painlesstraits : getAnnotation; 80 81 while(!__r.empty()) { 82 auto __msgdata = __r.readVarint(); 83 bool __matched = false; 84 foreach(__member; ProtoFields!this) { 85 alias __field = Identity!(__traits(getMember, this, __member)); 86 alias __fieldData = getAnnotation!(__field, ProtoField); 87 if(__msgdata.msgNum == __fieldData.fieldNumber) { 88 __r.putProtoVal!(__field)(__msgdata); 89 __matched = true; 90 } 91 } 92 if(!__matched) { 93 defaultDecode(__msgdata, __r); 94 } 95 } 96 } 97 98 void mergeFrom(this This)(auto ref This rhs) 99 { 100 import dproto.attributes; 101 102 foreach(__member; ProtoFields!this) { 103 alias __lmember = Identity!(__traits(getMember, this, __member)); 104 alias T = typeof(__lmember); 105 106 static if(is(T : const string) || is(T : const(ubyte)[])) { 107 __lmember = __traits(getMember, rhs, __member); 108 } else static if(is(T : const(U)[], U)) { 109 __lmember ~= __traits(getMember, rhs, __member); 110 } else { 111 __lmember = __traits(getMember, rhs, __member); 112 } 113 } 114 } 115 116 version(Have_painlessjson) { 117 auto toJson() const { 118 import painlessjson; 119 import std.conv : to; 120 return painlessjson.toJSON(this).to!string; 121 } 122 } 123 } 124 125 template ProtoFields(alias self) 126 { 127 import std.typetuple : Filter, TypeTuple, Erase; 128 129 alias Field(alias F) = Identity!(__traits(getMember, self, F)); 130 alias HasProtoField(alias F) = hasValueAnnotation!(Field!F, ProtoField); 131 alias AllMembers = TypeTuple!(__traits(allMembers, typeof(self))); 132 alias ProtoFields = Filter!(HasProtoField, Erase!("dproto", AllMembers)); 133 } 134 135 template protoDefault(T) { 136 import std.traits : isFloatingPoint; 137 static if(isFloatingPoint!T) { 138 enum protoDefault = 0.0; 139 } else static if(is(T : const string)) { 140 enum protoDefault = ""; 141 } else static if(is(T : const ubyte[])) { 142 enum protoDefault = []; 143 } else { 144 enum protoDefault = T.init; 145 } 146 } 147 148 void serializeField(alias field, R)(ref R r) const 149 if (isProtoOutputRange!R) 150 { 151 alias fieldType = typeof(field); 152 enum fieldData = getAnnotation!(field, ProtoField); 153 // Serialize if required or if the value isn't the (proto) default 154 enum isNullable = is(fieldType == Nullable!U, U); 155 bool needsToSerialize; 156 static if (isNullable) { 157 needsToSerialize = ! field.isNull; 158 } else { 159 needsToSerialize = hasValueAnnotation!(field, Required) 160 || (field != protoDefault!fieldType); 161 } 162 163 // If we still don't need to serialize, we're done here 164 if (!needsToSerialize) 165 { 166 return; 167 } 168 static if(isNullable) { 169 const rawField = field.get; 170 } else { 171 const rawField = field; 172 } 173 174 enum isPacked = hasValueAnnotation!(field, Packed); 175 enum isPackType = is(fieldType == enum) || fieldData.wireType.isBuiltinType; 176 static if (isPacked && isArray!fieldType && isPackType) 177 alias serializer = serializePackedProto; 178 else 179 alias serializer = serializeProto; 180 serializer!fieldData(rawField, r); 181 } 182 183 void putProtoVal(alias __field, R)(auto ref R r, ulong __msgdata) 184 if (isProtoInputRange!R) 185 { 186 import std.range : ElementType, takeExactly, popFrontExactly; 187 import std.array : empty; // Needed if R is an array 188 import std.traits : isSomeString, isDynamicArray; 189 190 alias T = typeof(__field); 191 enum wireType = getAnnotation!(__field, ProtoField).wireType; 192 enum isBinString(T) = Identity!(is(T : const(ubyte)[])); 193 static if (isDynamicArray!T && !(isSomeString!T || isBinString!T)) 194 { 195 ElementType!T u; 196 if (wireType.msgType != PACKED_MSG_TYPE && __msgdata.wireType == PACKED_MSG_TYPE) 197 { 198 size_t nelems = cast(size_t)r.readVarint(); 199 auto packeddata = takeExactly(r, nelems); 200 while(!packeddata.empty) 201 { 202 u.putSingleProtoVal!wireType(packeddata); 203 __field ~= u; 204 } 205 r.popFrontExactly(nelems); 206 } 207 else 208 { 209 u.putSingleProtoVal!wireType(r); 210 __field ~= u; 211 } 212 } 213 else 214 { 215 __field.putSingleProtoVal!wireType(r); 216 } 217 } 218 219 void putSingleProtoVal(string wireType, T, R)(ref T t, auto ref R r) 220 if(isProtoInputRange!R) 221 { 222 import std.conv : to; 223 static if(is(T : Nullable!U, U)) { 224 U t_tmp; 225 t_tmp.putSingleProtoVal!wireType(r); 226 t = t_tmp; 227 } else static if(wireType.isBuiltinType) { 228 t = r.readProto!wireType().to!T(); 229 } else static if(is(T == enum)) { 230 t = r.readProto!ENUM_SERIALIZATION().to!T(); 231 } else { 232 auto myData = r.readProto!"bytes"(); 233 return t.deserialize(myData); 234 } 235 } 236 237 void serializeProto(alias fieldData, T, R)(const T data, ref R r) 238 if(isProtoOutputRange!R) 239 { 240 static import dproto.serialize; 241 static if(is(T : const string)) { 242 r.toVarint(fieldData.header); 243 r.writeProto!"string"(data); 244 } else static if(is(T : const(ubyte)[])) { 245 r.toVarint(fieldData.header); 246 r.writeProto!"bytes"(data); 247 } else static if(is(T : const(T)[], T)) { 248 foreach(val; data) { 249 serializeProto!fieldData(val, r); 250 } 251 } else static if(fieldData.wireType.isBuiltinType) { 252 r.toVarint(fieldData.header); 253 enum wt = fieldData.wireType; 254 r.writeProto!(wt)(data); 255 } else static if(is(T == enum)) { 256 r.toVarint(ENUM_SERIALIZATION.msgType | (fieldData.fieldNumber << 3)); 257 r.writeProto!ENUM_SERIALIZATION(data); 258 } else static if(__traits(compiles, data.serialize())) { 259 r.toVarint(fieldData.header); 260 dproto.serialize.CntRange cnt; 261 data.serializeTo(cnt); 262 r.toVarint(cnt.cnt); 263 data.serializeTo(r); 264 } else { 265 static assert(0, "Unknown serialization"); 266 } 267 } 268 269 void serializePackedProto(alias fieldData, T, R)(const T data, ref R r) 270 if(isProtoOutputRange!R) 271 { 272 static assert(fieldData.wireType.isBuiltinType, 273 "Cannot have packed repeated message"); 274 if(data.length) { 275 dproto.serialize.CntRange cnt; 276 static if(is(T == enum)) { 277 enum wt = ENUM_SERIALIZATION.msgType; 278 } else { 279 enum wt = fieldData.wireType; 280 } 281 foreach (ref e; data) 282 cnt.writeProto!wt(e); 283 toVarint(r, PACKED_MSG_TYPE | (fieldData.fieldNumber << 3)); 284 toVarint(r, cnt.cnt); 285 foreach (ref e; data) 286 r.writeProto!wt(e); 287 } 288 }