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 }();