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