1 module google.protobuf.encoding;
2 
3 import std.algorithm : map;
4 import std.range : chain, ElementType, empty;
5 import std.traits : isArray, isAssociativeArray, isBoolean, isFloatingPoint, isIntegral, ValueType;
6 import google.protobuf.common;
7 import google.protobuf.internal;
8 
9 auto toProtobuf(T)(T value)
10 if (isBoolean!T)
11 {
12     return value ? [cast(ubyte) 0x01] : [cast(ubyte) 0x00];
13 }
14 
15 unittest
16 {
17     import std.array : array;
18 
19     assert(true.toProtobuf.array == [0x01]);
20     assert(false.toProtobuf.array == [0x00]);
21 }
22 
23 auto toProtobuf(Wire wire = Wire.none, T)(T value)
24 if (isIntegral!T)
25 {
26     static if (wire == Wire.none)
27     {
28         return Varint(value);
29     }
30     else static if (wire == Wire.fixed)
31     {
32         return value.encodeFixed;
33     }
34     else static if (wire == Wire.zigzag)
35     {
36         return Varint(zigZag(value));
37     }
38     else
39     {
40         assert(0, "Invalid wire encoding");
41     }
42 }
43 
44 unittest
45 {
46     import std.array : array;
47 
48     assert(10.toProtobuf.array == [0x0a]);
49     assert((-1).toProtobuf.array == [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]);
50     assert((-1L).toProtobuf.array == [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]);
51     assert(0xffffffffffffffffUL.toProtobuf.array == [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]);
52 
53     assert(1L.toProtobuf!(Wire.fixed).array == [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
54     assert(1.toProtobuf!(Wire.fixed).array == [0x01, 0x00, 0x00, 0x00]);
55     assert((-1).toProtobuf!(Wire.fixed).array == [0xff, 0xff, 0xff, 0xff]);
56     assert(0xffffffffU.toProtobuf!(Wire.fixed).array == [0xff, 0xff, 0xff, 0xff]);
57 
58     assert(1.toProtobuf!(Wire.zigzag).array == [0x02]);
59     assert((-1).toProtobuf!(Wire.zigzag).array == [0x01]);
60     assert(1L.toProtobuf!(Wire.zigzag).array == [0x02]);
61     assert((-1L).toProtobuf!(Wire.zigzag).array == [0x01]);
62 }
63 
64 auto toProtobuf(T)(T value)
65 if (isFloatingPoint!T)
66 {
67     return value.encodeFixed;
68 }
69 
70 unittest
71 {
72     import std.array : array;
73 
74     assert((0.0).toProtobuf.array == [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
75     assert((0.0f).toProtobuf.array == [0x00, 0x00, 0x00, 0x00]);
76 }
77 
78 auto toProtobuf(T)(T value)
79 if (is(T == string) || is(T == bytes))
80 {
81     return chain(Varint(value.length), cast(ubyte[]) value);
82 }
83 
84 unittest
85 {
86     import std.array : array;
87 
88     assert("abc".toProtobuf.array == [0x03, 'a', 'b', 'c']);
89     assert("".toProtobuf.array == [0x00]);
90     assert((cast(bytes) [1, 2, 3]).toProtobuf.array == [0x03, 1, 2, 3]);
91     assert((cast(bytes) []).toProtobuf.array == [0x00]);
92 }
93 
94 auto toProtobuf(Wire wire = Wire.none, T)(T value)
95 if (isArray!T && !is(T == string) && !is(T == bytes))
96 {
97     import std.range : hasLength;
98 
99     static assert(hasLength!T, "Cannot encode array with unknown length");
100 
101     static if (wire == Wire.none)
102     {
103         auto result = value.map!(a => a.toProtobuf).sizedJoiner;
104     }
105     else
106     {
107         static assert(isIntegral!(ElementType!T), "Cannot specify wire format for non-integral arrays");
108 
109         auto result = value.map!(a => a.toProtobuf!wire).sizedJoiner;
110     }
111 
112     return chain(Varint(result.length), result);
113 }
114 
115 unittest
116 {
117     import std.array : array;
118 
119     assert([false, false, true].toProtobuf.array == [0x03, 0x00, 0x00, 0x01]);
120     assert([1, 2].toProtobuf!(Wire.fixed).array == [0x08, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]);
121     assert([1, 2].toProtobuf.array == [0x02, 0x01, 0x02]);
122 }
123 
124 auto toProtobuf(T)(T value)
125 if (is(T == class) || is(T == struct))
126 {
127     import std.meta : AliasSeq;
128     import std.traits : hasMember;
129 
130     static if (hasMember!(T, "toProtobuf"))
131     {
132         return value.toProtobuf;
133     }
134     else static if (is(Message!T.fields == AliasSeq!()))
135     {
136         return cast(ubyte[]) null;
137     }
138     else
139     {
140         import std.algorithm : joiner;
141         import std.array : array;
142 
143         enum fieldExpressions = [Message!T.fieldNames]
144             .map!(a => "value.toProtobufByField!(T." ~ a ~ ")")
145             .joiner(", ")
146             .array;
147         enum resultExpression = "chain(" ~ fieldExpressions ~ ")";
148 
149         static if (isRecursive!T)
150         {
151             static if (is(T == class))
152             {
153                 if (value is null)
154                     return cast(SizedRange!ubyte) sizedRangeObject(cast(ubyte[]) null);
155             }
156 
157             return cast(SizedRange!ubyte) mixin(resultExpression).sizedRangeObject;
158         }
159         else
160         {
161             static if (is(T == class))
162             {
163                 if (value is null)
164                     return typeof(mixin(resultExpression)).init;
165             }
166 
167             return mixin(resultExpression);
168         }
169     }
170 }
171 
172 unittest
173 {
174     import std.array : array;
175 
176     class Foo
177     {
178         @Proto(1) int bar = protoDefaultValue!int;
179         @Proto(3) bool qux = protoDefaultValue!bool;
180         @Proto(2, Wire.fixed) long baz = protoDefaultValue!long;
181         @Proto(4) string quux = protoDefaultValue!string;
182 
183         @Proto(5) Foo recursion = protoDefaultValue!Foo;
184     }
185 
186     Foo foo;
187     assert(foo.toProtobuf.empty);
188     foo = new Foo;
189     assert(foo.toProtobuf.empty);
190     foo.bar = 5;
191     foo.baz = 1;
192     foo.qux = true;
193     assert(foo.toProtobuf.array == [0x08, 0x05, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01]);
194 }
195 
196 unittest
197 {
198     import std.array : array;
199 
200     struct EmptyMessage
201     {
202     }
203 
204     EmptyMessage emptyMessage;
205     assert(emptyMessage.toProtobuf.empty);
206 }
207 
208 unittest
209 {
210     struct Foo
211     {
212         @Proto(1) int bar = protoDefaultValue!int;
213 
214         auto toProtobuf()
215         {
216             return [0x08, 0x00];
217         }
218     }
219 
220     Foo foo;
221     assert(foo.toProtobuf == [0x08, 0x00]);
222 }
223 
224 private static auto toProtobufByField(alias field, T)(T message)
225 {
226     import std.meta : Alias;
227 
228     static assert(is(Alias!(__traits(parent, field)) == T), "Field and message are different types");
229     static assert(validateProto!(protoByField!field, typeof(field)));
230     enum proto = protoByField!field;
231     enum fieldName = __traits(identifier, field);
232     enum fieldInstanceName = "message." ~ fieldName;
233 
234     static if (isOneof!field)
235     {
236         auto oneofCase = __traits(getMember, message, oneofCaseFieldName!field);
237         enum fieldCase = "T." ~ typeof(oneofCase).stringof ~ "." ~ oneofAccessorName!field;
238 
239         if (oneofCase != mixin(fieldCase))
240             return emptySizedRange!(typeof(mixin(fieldInstanceName).toProtobufByProto!proto));
241     }
242     else
243     {
244         if (mixin(fieldInstanceName) == protoDefaultValue!(typeof(field)))
245             return emptySizedRange!(typeof(mixin(fieldInstanceName).toProtobufByProto!proto));
246     }
247 
248     return mixin(fieldInstanceName).toProtobufByProto!proto;
249 }
250 
251 unittest
252 {
253     import std.array : array;
254     import std.typecons : Yes;
255 
256     struct Foo
257     {
258         @Proto(1) int f10 = 10;
259         @Proto(16) int f11 = 10;
260         @Proto(2048) bool f12 = true;
261         @Proto(262144) bool f13 = true;
262 
263         @Proto(20) bool f20 = false;
264         @Proto(21) int f21 = 0;
265         @Proto(22, Wire.fixed) int f22 = 0;
266         @Proto(23, Wire.zigzag) int f23 = 0;
267         @Proto(24) long f24 = 0L;
268         @Proto(25) double f25 = 0.0;
269         @Proto(26) string f26 = "";
270         @Proto(27) bytes f27 = [];
271 
272         @Proto(30) int[] f30 = [1, 2];
273         @Proto(31, Wire.none, Yes.packed) int[] f31 = [1, 2];
274         @Proto(32, Wire.none, Yes.packed) int[] f32 = [128, 2];
275     }
276 
277     Foo foo;
278 
279     assert(foo.toProtobufByField!(Foo.f10).array == [0x08, 0x0a]);
280     assert(foo.toProtobufByField!(Foo.f11).array == [0x80, 0x01, 0x0a]);
281     assert(foo.toProtobufByField!(Foo.f12).array == [0x80, 0x80, 0x01, 0x01]);
282     assert(foo.toProtobufByField!(Foo.f13).array == [0x80, 0x80, 0x80, 0x01, 0x01]);
283 
284     assert(foo.toProtobufByField!(Foo.f20).empty);
285     assert(foo.toProtobufByField!(Foo.f21).empty);
286     assert(foo.toProtobufByField!(Foo.f22).empty);
287     assert(foo.toProtobufByField!(Foo.f23).empty);
288     assert(foo.toProtobufByField!(Foo.f24).empty);
289     assert(foo.toProtobufByField!(Foo.f25).empty);
290     assert(foo.toProtobufByField!(Foo.f26).empty);
291     assert(foo.toProtobufByField!(Foo.f27).empty);
292 
293     assert(foo.toProtobufByField!(Foo.f30).array == [0xf0, 0x01, 0x01, 0xf0, 0x01, 0x02]);
294     assert(foo.toProtobufByField!(Foo.f31).array == [0xfa, 0x01, 0x02, 0x01, 0x02]);
295     assert(foo.toProtobufByField!(Foo.f32).array == [0x82, 0x02, 0x03, 0x80, 0x01, 0x02]);
296 }
297 
298 private auto toProtobufByProto(Proto proto, T)(T value)
299 if (isBoolean!T ||
300     isFloatingPoint!T ||
301     is(T == string) ||
302     is(T == bytes) ||
303     (isArray!T && proto.packed))
304 {
305     static assert(validateProto!(proto, T));
306 
307     return chain(encodeTag!(proto, T), value.toProtobuf);
308 }
309 
310 private auto toProtobufByProto(Proto proto, T)(T value)
311 if (isIntegral!T)
312 {
313     static assert(validateProto!(proto, T));
314 
315     enum wire = proto.wire;
316     return chain(encodeTag!(proto, T), value.toProtobuf!wire);
317 }
318 
319 private auto toProtobufByProto(Proto proto, T)(T value)
320 if (isArray!T && !proto.packed && !is(T == string) && !is(T == bytes))
321 {
322     static assert(validateProto!(proto, T));
323 
324     enum elementProto = Proto(proto.tag, proto.wire);
325     return value
326         .map!(a => a.toProtobufByProto!elementProto)
327         .sizedJoiner;
328 }
329 
330 private auto toProtobufByProto(Proto proto, T)(T value)
331 if (isAssociativeArray!T)
332 {
333     static assert(validateProto!(proto, T));
334 
335     enum keyProto = Proto(MapFieldTag.key, keyWireToWire(proto.wire));
336     enum valueProto = Proto(MapFieldTag.value, valueWireToWire(proto.wire));
337 
338     return value
339         .byKeyValue
340         .map!(a => chain(a.key.toProtobufByProto!keyProto, a.value.toProtobufByProto!valueProto))
341         .map!(a => chain(encodeTag!(proto, T), Varint(a.length), a))
342         .sizedJoiner;
343 }
344 
345 unittest
346 {
347     import std.array : array;
348     import std.typecons : Yes;
349 
350     assert([1, 2].toProtobufByProto!(Proto(1)).array == [0x08, 0x01, 0x08, 0x02]);
351     assert((int[]).init.toProtobufByProto!(Proto(1)).empty);
352     assert([1, 2].toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x02, 0x01, 0x02]);
353     assert([128, 2].toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x03, 0x80, 0x01, 0x02]);
354     assert((int[]).init.toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x00]);
355 
356     assert((int[int]).init.toProtobufByProto!(Proto(1)).empty);
357     assert([1: 2].toProtobufByProto!(Proto(1)).array == [0x0a, 0x04, 0x08, 0x01, 0x10, 0x02]);
358     assert([1: 2].toProtobufByProto!(Proto(1, Wire.fixedValue)).array ==
359         [0x0a, 0x07, 0x08, 0x01, 0x15, 0x02, 0x00, 0x00, 0x00]);
360 }
361 
362 private auto toProtobufByProto(Proto proto, T)(T value)
363 if (is(T == class) || is(T == struct))
364 {
365     static assert(validateProto!(proto, T));
366 
367     auto encodedValue = value.toProtobuf;
368     auto result = chain(encodeTag!(proto, T), Varint(encodedValue.length), encodedValue);
369 
370     static if (isRecursive!T)
371     {
372         return cast(SizedRange!ubyte) result.sizedRangeObject;
373     }
374     else
375     {
376         return result;
377     }
378 }