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     assert([-54L, 54L].toProtobuf!(Wire.zigzag).array == [0x02, 0x6b, 0x6c]);
123 }
124 
125 auto toProtobuf(T)(T value)
126 if (is(T == class) || is(T == struct))
127 {
128     import std.meta : AliasSeq;
129     import std.traits : hasMember;
130 
131     static if (hasMember!(T, "toProtobuf"))
132     {
133         return value.toProtobuf;
134     }
135     else static if (is(Message!T.fields == AliasSeq!()))
136     {
137         return cast(ubyte[]) null;
138     }
139     else
140     {
141         import std.algorithm : joiner;
142         import std.array : array;
143 
144         enum fieldExpressions = [Message!T.fieldNames]
145             .map!(a => "value.toProtobufByField!(T." ~ a ~ ")")
146             .joiner(", ")
147             .array;
148         enum resultExpression = "chain(" ~ fieldExpressions ~ ")";
149 
150         static if (isRecursive!T)
151         {
152             static if (is(T == class))
153             {
154                 if (value is null)
155                     return cast(SizedRange!ubyte) sizedRangeObject(cast(ubyte[]) null);
156             }
157 
158             return cast(SizedRange!ubyte) mixin(resultExpression).sizedRangeObject;
159         }
160         else
161         {
162             static if (is(T == class))
163             {
164                 if (value is null)
165                     return typeof(mixin(resultExpression)).init;
166             }
167 
168             return mixin(resultExpression);
169         }
170     }
171 }
172 
173 unittest
174 {
175     import std.array : array;
176 
177     class Foo
178     {
179         @Proto(1) int bar = protoDefaultValue!int;
180         @Proto(3) bool qux = protoDefaultValue!bool;
181         @Proto(2, Wire.fixed) long baz = protoDefaultValue!long;
182         @Proto(4) string quux = protoDefaultValue!string;
183 
184         @Proto(5) Foo recursion = protoDefaultValue!Foo;
185     }
186 
187     Foo foo;
188     assert(foo.toProtobuf.empty);
189     foo = new Foo;
190     assert(foo.toProtobuf.empty);
191     foo.bar = 5;
192     foo.baz = 1;
193     foo.qux = true;
194     assert(foo.toProtobuf.array == [0x08, 0x05, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01]);
195 }
196 
197 unittest
198 {
199     import std.array : array;
200 
201     struct EmptyMessage
202     {
203     }
204 
205     EmptyMessage emptyMessage;
206     assert(emptyMessage.toProtobuf.empty);
207 }
208 
209 unittest
210 {
211     import std.array : array;
212     import std.typecons : Yes;
213 
214     struct Foo
215     {
216         @Proto(1) int[] bar = protoDefaultValue!(int[]);
217         @Proto(2, Wire.zigzag, Yes.packed) int[] baz = protoDefaultValue!(int[]);
218     }
219 
220     Foo foo;
221     assert(foo.toProtobuf.empty);
222     foo.bar = [1, 2];
223     foo.baz = [3, 4];
224     assert(foo.toProtobuf.array == [0x08, 0x01, 0x08, 0x02, 0x12, 0x02, 0x06, 0x08]);
225 }
226 
227 unittest
228 {
229     struct Foo
230     {
231         @Proto(1) int bar = protoDefaultValue!int;
232 
233         auto toProtobuf()
234         {
235             return [0x08, 0x00];
236         }
237     }
238 
239     Foo foo;
240     assert(foo.toProtobuf == [0x08, 0x00]);
241 }
242 
243 private static auto toProtobufByField(alias field, T)(T message)
244 {
245     import std.meta : Alias;
246 
247     static assert(is(Alias!(__traits(parent, field)) == T), "Field and message are different types");
248     static assert(validateProto!(protoByField!field, typeof(field)));
249     enum proto = protoByField!field;
250     enum fieldName = __traits(identifier, field);
251     enum fieldInstanceName = "message." ~ fieldName;
252 
253     static if (isOneof!field)
254     {
255         auto oneofCase = __traits(getMember, message, oneofCaseFieldName!field);
256         enum fieldCase = "T." ~ typeof(oneofCase).stringof ~ "." ~ oneofAccessorName!field;
257 
258         if (oneofCase != mixin(fieldCase))
259             return emptySizedRange!(typeof(mixin(fieldInstanceName).toProtobufByProto!proto));
260     }
261     else
262     {
263         if (mixin(fieldInstanceName) == protoDefaultValue!(typeof(field)))
264             return emptySizedRange!(typeof(mixin(fieldInstanceName).toProtobufByProto!proto));
265     }
266 
267     return mixin(fieldInstanceName).toProtobufByProto!proto;
268 }
269 
270 unittest
271 {
272     import std.array : array;
273     import std.typecons : Yes;
274 
275     struct Foo
276     {
277         @Proto(1) int f10 = 10;
278         @Proto(16) int f11 = 10;
279         @Proto(2048) bool f12 = true;
280         @Proto(262144) bool f13 = true;
281 
282         @Proto(20) bool f20 = false;
283         @Proto(21) int f21 = 0;
284         @Proto(22, Wire.fixed) int f22 = 0;
285         @Proto(23, Wire.zigzag) int f23 = 0;
286         @Proto(24) long f24 = 0L;
287         @Proto(25) double f25 = 0.0;
288         @Proto(26) string f26 = "";
289         @Proto(27) bytes f27 = [];
290 
291         @Proto(30) int[] f30 = [1, 2];
292         @Proto(31, Wire.none, Yes.packed) int[] f31 = [1, 2];
293         @Proto(32, Wire.none, Yes.packed) int[] f32 = [128, 2];
294     }
295 
296     Foo foo;
297 
298     assert(foo.toProtobufByField!(Foo.f10).array == [0x08, 0x0a]);
299     assert(foo.toProtobufByField!(Foo.f11).array == [0x80, 0x01, 0x0a]);
300     assert(foo.toProtobufByField!(Foo.f12).array == [0x80, 0x80, 0x01, 0x01]);
301     assert(foo.toProtobufByField!(Foo.f13).array == [0x80, 0x80, 0x80, 0x01, 0x01]);
302 
303     assert(foo.toProtobufByField!(Foo.f20).empty);
304     assert(foo.toProtobufByField!(Foo.f21).empty);
305     assert(foo.toProtobufByField!(Foo.f22).empty);
306     assert(foo.toProtobufByField!(Foo.f23).empty);
307     assert(foo.toProtobufByField!(Foo.f24).empty);
308     assert(foo.toProtobufByField!(Foo.f25).empty);
309     assert(foo.toProtobufByField!(Foo.f26).empty);
310     assert(foo.toProtobufByField!(Foo.f27).empty);
311 
312     assert(foo.toProtobufByField!(Foo.f30).array == [0xf0, 0x01, 0x01, 0xf0, 0x01, 0x02]);
313     assert(foo.toProtobufByField!(Foo.f31).array == [0xfa, 0x01, 0x02, 0x01, 0x02]);
314     assert(foo.toProtobufByField!(Foo.f32).array == [0x82, 0x02, 0x03, 0x80, 0x01, 0x02]);
315 }
316 
317 private auto toProtobufByProto(Proto proto, T)(T value)
318 if (isBoolean!T ||
319     isFloatingPoint!T ||
320     is(T == string) ||
321     is(T == bytes) ||
322     (isArray!T && proto.packed))
323 {
324     static assert(validateProto!(proto, T));
325 
326     static if (proto.wire == Wire.none)
327     {
328         return chain(encodeTag!(proto, T), value.toProtobuf);
329     }
330     else
331     {
332         return chain(encodeTag!(proto, T), value.toProtobuf!(proto.wire));
333     }
334 }
335 
336 private auto toProtobufByProto(Proto proto, T)(T value)
337 if (isIntegral!T)
338 {
339     static assert(validateProto!(proto, T));
340 
341     enum wire = proto.wire;
342     return chain(encodeTag!(proto, T), value.toProtobuf!wire);
343 }
344 
345 private auto toProtobufByProto(Proto proto, T)(T value)
346 if (isArray!T && !proto.packed && !is(T == string) && !is(T == bytes))
347 {
348     static assert(validateProto!(proto, T));
349 
350     enum elementProto = Proto(proto.tag, proto.wire);
351     return value
352         .map!(a => a.toProtobufByProto!elementProto)
353         .sizedJoiner;
354 }
355 
356 private auto toProtobufByProto(Proto proto, T)(T value)
357 if (isAssociativeArray!T)
358 {
359     static assert(validateProto!(proto, T));
360 
361     enum keyProto = Proto(MapFieldTag.key, keyWireToWire(proto.wire));
362     enum valueProto = Proto(MapFieldTag.value, valueWireToWire(proto.wire));
363 
364     return value
365         .byKeyValue
366         .map!(a => chain(a.key.toProtobufByProto!keyProto, a.value.toProtobufByProto!valueProto))
367         .map!(a => chain(encodeTag!(proto, T), Varint(a.length), a))
368         .sizedJoiner;
369 }
370 
371 unittest
372 {
373     import std.array : array;
374     import std.typecons : Yes;
375 
376     assert([1, 2].toProtobufByProto!(Proto(1)).array == [0x08, 0x01, 0x08, 0x02]);
377     assert((int[]).init.toProtobufByProto!(Proto(1)).empty);
378     assert([1, 2].toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x02, 0x01, 0x02]);
379     assert([128, 2].toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x03, 0x80, 0x01, 0x02]);
380     assert((int[]).init.toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x00]);
381 
382     assert((int[int]).init.toProtobufByProto!(Proto(1)).empty);
383     assert([1: 2].toProtobufByProto!(Proto(1)).array == [0x0a, 0x04, 0x08, 0x01, 0x10, 0x02]);
384     assert([1: 2].toProtobufByProto!(Proto(1, Wire.fixedValue)).array ==
385         [0x0a, 0x07, 0x08, 0x01, 0x15, 0x02, 0x00, 0x00, 0x00]);
386 }
387 
388 private auto toProtobufByProto(Proto proto, T)(T value)
389 if (is(T == class) || is(T == struct))
390 {
391     static assert(validateProto!(proto, T));
392 
393     auto encodedValue = value.toProtobuf;
394     auto result = chain(encodeTag!(proto, T), Varint(encodedValue.length), encodedValue);
395 
396     static if (isRecursive!T)
397     {
398         return cast(SizedRange!ubyte) result.sizedRangeObject;
399     }
400     else
401     {
402         return result;
403     }
404 }