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, OriginalType; 185 186 return isArray!(typeof(field)) && 187 staticIndexOf!(OriginalType!(ElementType!(typeof(field))), 188 AliasSeq!(bool, int, uint, long, ulong, float, double)) >= 0; 189 }(); 190 191 unittest 192 { 193 struct Foo 194 { 195 uint f1; 196 uint[] f2; 197 string f3; 198 string[] f4; 199 } 200 201 static assert(!isFieldPackable!(Foo.f1)); 202 static assert(isFieldPackable!(Foo.f2)); 203 static assert(!isFieldPackable!(Foo.f3)); 204 static assert(!isFieldPackable!(Foo.f4)); 205 } 206 207 template validateField(alias field) 208 { 209 import std.traits : getUDAs; 210 211 enum validateField = validateProto!(getUDAs!(field, Proto)[0], typeof(field)); 212 } 213 214 bool validateProto(Proto proto, T)() 215 { 216 import std.range : ElementType; 217 import std.traits : isArray, isAssociativeArray, isBoolean, isFloatingPoint, isIntegral; 218 import std.traits : KeyType, ValueType; 219 220 static assert(proto.tag > 0 && proto.tag < (2 << 29), "Tag value out of range [1 536_870_912]"); 221 222 static if (isBoolean!T) 223 { 224 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 225 static assert(!proto.packed, "Singular field cannot be packed"); 226 } 227 else static if (is(T == int) || is(T == uint) || is(T == long) || is(T == ulong)) 228 { 229 import std.algorithm : canFind; 230 231 static assert([Wire.none, Wire.fixed, Wire.zigzag].canFind(proto.wire), "Invalid wire encoding"); 232 static assert(!proto.packed, "Singular field cannot be packed"); 233 } 234 else static if (is(T == enum) && is(T : int)) 235 { 236 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 237 static assert(!proto.packed, "Singular field cannot be packed"); 238 } 239 else static if (isFloatingPoint!T) 240 { 241 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 242 static assert(!proto.packed, "Singular field cannot be packed"); 243 } 244 else static if (is(T == string) || is(T == bytes)) 245 { 246 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 247 static assert(!proto.packed, "Singular field cannot be packed"); 248 } 249 else static if (isArray!T) 250 { 251 static assert(is(ElementType!T == string) || is(ElementType!T == bytes) 252 || (!isArray!(ElementType!T) && !isAssociativeArray!(ElementType!T)), 253 "Invalid array element type"); 254 enum elementProto = Proto(proto.tag, proto.wire); 255 256 static assert(validateProto!(elementProto, ElementType!T)); 257 } 258 else static if (isAssociativeArray!T) 259 { 260 static assert(isBoolean!(KeyType!T) || isIntegral!(KeyType!T) || is(KeyType!T == string), 261 "Invalid map key field type"); 262 static assert(is(ValueType!T == string) || is(ValueType!T == bytes) 263 || (!isArray!(ValueType!T) && !isAssociativeArray!(ValueType!T)), 264 "Invalid map value field type"); 265 266 enum keyProto = Proto(MapFieldTag.key, keyWireToWire(proto.wire)); 267 static assert(validateProto!(keyProto, KeyType!T)); 268 269 enum valueProto = Proto(MapFieldTag.value, valueWireToWire(proto.wire)); 270 static assert(validateProto!(valueProto, ValueType!T)); 271 272 static assert(!proto.packed, "Map field cannot be packed"); 273 } 274 else static if (is(T == class) || is(T == struct)) 275 { 276 static assert(proto.wire == Wire.none, "Invalid wire encoding"); 277 static assert(!proto.packed, "Singular field cannot be packed"); 278 } 279 else 280 { 281 static assert(0, "Invalid Proto definition for type " ~ T.stringof); 282 } 283 284 return true; 285 } 286 287 enum protocVersion(T) = { 288 import std.traits : moduleName; 289 290 mixin("static import " ~ moduleName!T ~ ";"); 291 292 static if (__traits(compiles, moduleName!T ~ ".protocVersion")) 293 { 294 return mixin(moduleName!T ~ ".protocVersion"); 295 } 296 297 return 0; 298 }();