1 module google.protobuf.common; 2 3 alias bytes = ubyte[]; 4 5 auto protoDefaultValue(T)() 6 { 7 import std.traits : hasMember, isFloatingPoint; 8 9 static if (isFloatingPoint!T) 10 return cast(T) 0.0; 11 else static if ((is(T == class) || is(T == struct)) && hasMember!(T, "protoDefaultValue")) 12 return T.protoDefaultValue; 13 else 14 return T.init; 15 } 16 17 class ProtobufException : Exception 18 { 19 this(string message = null, string file = __FILE__, size_t line = __LINE__, 20 Throwable next = null) @safe pure nothrow 21 { 22 super(message, file, line, next); 23 } 24 } 25 26 enum Wire: ubyte 27 { 28 none, 29 fixed = 1 << 0, 30 zigzag = 1 << 1, 31 fixedKey = 1 << 2, 32 zigzagKey = 1 << 3, 33 fixedValue = fixed, 34 zigzagValue = zigzag, 35 fixedKeyFixedValue = fixedKey | fixedValue, 36 fixedKeyZigzagValue = fixedKey | zigzagValue, 37 zigzagKeyFixedValue = zigzagKey | fixedValue, 38 zigzagKeyZigzagValue = zigzagKey | zigzagValue, 39 } 40 41 package Wire keyWireToWire(Wire wire) 42 { 43 return cast(Wire)((wire >> 2) & 0x03); 44 } 45 46 unittest 47 { 48 static assert(keyWireToWire(Wire.none) == Wire.none); 49 static assert(keyWireToWire(Wire.fixedKey) == Wire.fixed); 50 static assert(keyWireToWire(Wire.zigzagKey) == Wire.zigzag); 51 static assert(keyWireToWire(Wire.fixedValue) == Wire.none); 52 static assert(keyWireToWire(Wire.zigzagValue) == Wire.none); 53 static assert(keyWireToWire(Wire.fixedKeyFixedValue) == Wire.fixed); 54 static assert(keyWireToWire(Wire.fixedKeyZigzagValue) == Wire.fixed); 55 static assert(keyWireToWire(Wire.zigzagKeyFixedValue) == Wire.zigzag); 56 static assert(keyWireToWire(Wire.zigzagKeyZigzagValue) == Wire.zigzag); 57 } 58 59 package Wire valueWireToWire(Wire wire) 60 { 61 return cast(Wire)(wire & 0x03); 62 } 63 64 unittest 65 { 66 static assert(valueWireToWire(Wire.none) == Wire.none); 67 static assert(valueWireToWire(Wire.fixedKey) == Wire.none); 68 static assert(valueWireToWire(Wire.zigzagKey) == Wire.none); 69 static assert(valueWireToWire(Wire.fixedValue) == Wire.fixed); 70 static assert(valueWireToWire(Wire.zigzagValue) == Wire.zigzag); 71 static assert(valueWireToWire(Wire.fixedKeyFixedValue) == Wire.fixed); 72 static assert(valueWireToWire(Wire.fixedKeyZigzagValue) == Wire.zigzag); 73 static assert(valueWireToWire(Wire.zigzagKeyFixedValue) == Wire.fixed); 74 static assert(valueWireToWire(Wire.zigzagKeyZigzagValue) == Wire.zigzag); 75 } 76 77 struct Proto 78 { 79 import std.typecons : Flag; 80 81 uint tag; 82 Wire wire; 83 Flag!"packed" packed; 84 } 85 86 template protoByField(alias field) 87 { 88 import std.traits : getUDAs; 89 90 enum Proto protoByField = getUDAs!(field, Proto)[0]; 91 } 92 93 enum MapFieldTag : uint 94 { 95 key = 1, 96 value = 2, 97 } 98 99 struct Oneof 100 { 101 string caseFieldName; 102 } 103 104 template isOneof(alias field) 105 { 106 import std.traits : hasUDA; 107 108 enum bool isOneof = hasUDA!(field, Oneof); 109 } 110 111 enum string oneofCaseFieldName(alias field) = { 112 import std.traits : getUDAs; 113 114 static assert(isOneof!field, "No oneof field"); 115 116 return getUDAs!(field, Oneof)[0].caseFieldName; 117 }(); 118 119 enum string oneofAccessorName(alias field) = { 120 enum fieldName = __traits(identifier, field); 121 static assert(fieldName[0] == '_', "Oneof field (union member) name should start with '_'"); 122 123 return fieldName[1 .. $]; 124 }(); 125 126 enum string oneofAccessors(alias field) = { 127 import std..string : format; 128 129 enum accessorName = oneofAccessorName!field; 130 131 return " 132 @property %1$s %2$s() { return %3$s == typeof(%3$s).%2$s ? _%2$s : protoDefaultValue!(%1$s); } 133 @property void %2$s(%1$s _) { _%2$s = _; %3$s = typeof(%3$s).%2$s; } 134 ".format(typeof(field).stringof, accessorName, oneofCaseFieldName!field); 135 }(); 136 137 template Message(T) 138 { 139 import std.meta : allSatisfy, staticMap, staticSort; 140 import std.traits : getSymbolsByUDA; 141 142 alias fields = staticSort!(Less, unsortedFields); 143 alias protos = staticMap!(protoByField, fields); 144 145 alias fieldNames = staticMap!(fieldName, fields); 146 147 static assert(allSatisfy!(validateField, fields), "'" ~ T.stringof ~ "' has invalid fields"); 148 149 private alias unsortedFields = getSymbolsByUDA!(T, Proto); 150 private static enum fieldName(alias field) = __traits(identifier, field); 151 private static enum Less(alias field1, alias field2) = protoByField!field1.tag < protoByField!field2.tag; 152 } 153 154 unittest 155 { 156 import std.meta : AliasSeq; 157 import std.typecons : No; 158 159 static class Test 160 { 161 @Proto(3) int foo; 162 @Proto(2, Wire.fixed) int bar; 163 } 164 165 static assert(Message!Test.fieldNames == AliasSeq!("bar", "foo")); 166 static assert(Message!Test.protos == AliasSeq!(Proto(2, Wire.fixed, No.packed), Proto(3, Wire.none, No.packed))); 167 168 static class EmptyMessage 169 { 170 } 171 172 static assert(is(Message!EmptyMessage.fieldNames == AliasSeq!())); 173 static assert(is(Message!EmptyMessage.protos == AliasSeq!())); 174 } 175 176 template validateField(alias field) 177 { 178 import std.traits : getUDAs; 179 180 enum validateField = validateProto!(getUDAs!(field, Proto)[0], typeof(field)); 181 } 182 183 bool validateProto(Proto proto, T)() 184 { 185 import std.range : ElementType; 186 import std.traits : isArray, isAssociativeArray, isBoolean, isFloatingPoint, isIntegral; 187 import std.traits : KeyType, ValueType; 188 189 static assert(proto.tag > 0 && proto.tag < (2 << 29), "Tag value out of range [1 536_870_912]"); 190 191 static if (isBoolean!T) 192 { 193 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 194 static assert(!proto.packed, "Singular field cannot be packed"); 195 } 196 else static if (is(T == int) || is(T == uint) || is(T == long) || is(T == ulong)) 197 { 198 import std.algorithm : canFind; 199 200 static assert([Wire.none, Wire.fixed, Wire.zigzag].canFind(proto.wire), "Invalid wire encoding"); 201 static assert(!proto.packed, "Singular field cannot be packed"); 202 } 203 else static if (is(T == enum) && is(T : int)) 204 { 205 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 206 static assert(!proto.packed, "Singular field cannot be packed"); 207 } 208 else static if (isFloatingPoint!T) 209 { 210 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 211 static assert(!proto.packed, "Singular field cannot be packed"); 212 } 213 else static if (is(T == string) || is(T == bytes)) 214 { 215 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 216 static assert(!proto.packed, "Singular field cannot be packed"); 217 } 218 else static if (isArray!T) 219 { 220 static assert(is(ElementType!T == string) || is(ElementType!T == bytes) 221 || (!isArray!(ElementType!T) && !isAssociativeArray!(ElementType!T)), 222 "Invalid array element type"); 223 enum elementProto = Proto(proto.tag, proto.wire); 224 225 static assert(validateProto!(elementProto, ElementType!T)); 226 } 227 else static if (isAssociativeArray!T) 228 { 229 static assert(isBoolean!(KeyType!T) || isIntegral!(KeyType!T) || is(KeyType!T == string), 230 "Invalid map key field type"); 231 static assert(is(ValueType!T == string) || is(ValueType!T == bytes) 232 || (!isArray!(ValueType!T) && !isAssociativeArray!(ValueType!T)), 233 "Invalid map value field type"); 234 235 enum keyProto = Proto(MapFieldTag.key, keyWireToWire(proto.wire)); 236 static assert(validateProto!(keyProto, KeyType!T)); 237 238 enum valueProto = Proto(MapFieldTag.value, valueWireToWire(proto.wire)); 239 static assert(validateProto!(valueProto, ValueType!T)); 240 241 static assert(!proto.packed, "Map field cannot be packed"); 242 } 243 else static if (is(T == class) || is(T == struct)) 244 { 245 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 246 static assert(!proto.packed, "Singular field cannot be packed"); 247 } 248 else 249 { 250 static assert(0, "Invalid Proto definition for type " ~ T.stringof); 251 } 252 253 return true; 254 } 255 256 enum protocVersion(T) = { 257 import std.traits : moduleName; 258 259 mixin("static import " ~ moduleName!T ~ ";"); 260 261 static if (__traits(compiles, moduleName!T ~ ".protocVersion")) 262 { 263 return mixin(moduleName!T ~ ".protocVersion"); 264 } 265 266 return 0; 267 }();