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