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