1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 /**
21  * Contains <b>experimental</b> functionality for generating Thrift IDL files
22  * (.thrift) from existing D data structures, i.e. the reverse of what the
23  * Thrift compiler does.
24  */
25 module thrift.codegen.idlgen;
26 
27 import std.algorithm : find;
28 import std.array : empty, front;
29 import std.conv : to;
30 import std.traits : EnumMembers, isSomeFunction, OriginalType,
31   ParameterTypeTuple, ReturnType;
32 import std.typetuple : allSatisfy, staticIndexOf, staticMap, NoDuplicates,
33   TypeTuple;
34 import thrift.base;
35 import thrift.codegen.base;
36 import thrift.internal.codegen;
37 import thrift.internal.ctfe;
38 import thrift.util.hashset;
39 
40 /**
41  * True if the passed type is a Thrift entity (struct, exception, enum,
42  * service).
43  */
44 alias Any!(isStruct, isException, isEnum, isService) isThriftEntity;
45 
46 /**
47  * Returns an IDL string describing the passed »root« entities and all types
48  * they depend on.
49  */
50 template idlString(Roots...) if (allSatisfy!(isThriftEntity, Roots)) {
51   enum idlString = idlStringImpl!Roots.result;
52 }
53 
54 private {
55   template idlStringImpl(Roots...) if (allSatisfy!(isThriftEntity, Roots)) {
56     alias ForAllWithList!(
57       ConfinedTuple!(StaticFilter!(isService, Roots)),
58       AddBaseServices
59     ) Services;
60 
61     alias TypeTuple!(
62       StaticFilter!(isEnum, Roots),
63       ForAllWithList!(
64         ConfinedTuple!(
65           StaticFilter!(Any!(isException, isStruct), Roots),
66           staticMap!(CompositeTypeDeps, staticMap!(ServiceTypeDeps, Services))
67         ),
68         AddStructWithDeps
69       )
70     ) Types;
71 
72     enum result = ctfeJoin(
73       [
74         staticMap!(
75           enumIdlString,
76           StaticFilter!(isEnum, Types)
77         ),
78         staticMap!(
79           structIdlString,
80           StaticFilter!(Any!(isStruct, isException), Types)
81         ),
82         staticMap!(
83           serviceIdlString,
84           Services
85         )
86       ],
87       "\n"
88     );
89   }
90 
91   template ServiceTypeDeps(T) if (isService!T) {
92     alias staticMap!(
93       PApply!(MethodTypeDeps, T),
94       FilterMethodNames!(T, __traits(derivedMembers, T))
95     ) ServiceTypeDeps;
96   }
97 
98   template MethodTypeDeps(T, string name) if (
99     isService!T && isSomeFunction!(MemberType!(T, name))
100   ) {
101     alias TypeTuple!(
102       ReturnType!(MemberType!(T, name)),
103       ParameterTypeTuple!(MemberType!(T, name)),
104       ExceptionTypes!(T, name)
105     ) MethodTypeDeps;
106   }
107 
108   template ExceptionTypes(T, string name) if (
109     isService!T && isSomeFunction!(MemberType!(T, name))
110   ) {
111     mixin({
112       enum meta = find!`a.name == b`(getMethodMeta!T, name);
113       if (meta.empty) return "alias TypeTuple!() ExceptionTypes;";
114 
115       string result = "alias TypeTuple!(";
116       foreach (i, e; meta.front.exceptions) {
117         if (i > 0) result ~= ", ";
118         result ~= "mixin(`T." ~ e.type ~ "`)";
119       }
120       result ~= ") ExceptionTypes;";
121       return result;
122     }());
123   }
124 
125   template AddBaseServices(T, List...) {
126     static if (staticIndexOf!(T, List) == -1) {
127       alias NoDuplicates!(BaseServices!T, List) AddBaseServices;
128     } else {
129       alias List AddStructWithDeps;
130     }
131   }
132 
133   unittest {
134     interface A {}
135     interface B : A {}
136     interface C : B {}
137     interface D : A {}
138 
139     static assert(is(AddBaseServices!(C) == TypeTuple!(A, B, C)));
140     static assert(is(ForAllWithList!(ConfinedTuple!(C, D), AddBaseServices) ==
141       TypeTuple!(A, D, B, C)));
142   }
143 
144   template BaseServices(T, Rest...) if (isService!T) {
145     static if (isDerivedService!T) {
146       alias BaseServices!(BaseService!T, T, Rest) BaseServices;
147     } else {
148       alias TypeTuple!(T, Rest) BaseServices;
149     }
150   }
151 
152   template AddStructWithDeps(T, List...) {
153     static if (staticIndexOf!(T, List) == -1) {
154       // T is not already in the List, so add T and the types it depends on in
155       // the front. Because with the Thrift compiler types can only depend on
156       // other types that have already been defined, we collect all the
157       // dependencies, prepend them to the list, and then prune the duplicates
158       // (keeping the first occurrences). If this requirement should ever be
159       // dropped from Thrift, this could be easily adapted to handle circular
160       // dependencies by passing TypeTuple!(T, List) to ForAllWithList instead
161       // of appending List afterwards, and removing the now unnecessary
162       // NoDuplicates.
163       alias NoDuplicates!(
164         ForAllWithList!(
165           ConfinedTuple!(
166             staticMap!(
167               CompositeTypeDeps,
168               staticMap!(
169                 PApply!(MemberType, T),
170                 FieldNames!T
171               )
172             )
173           ),
174           .AddStructWithDeps,
175           T
176         ),
177         List
178       ) AddStructWithDeps;
179     } else {
180       alias List AddStructWithDeps;
181     }
182   }
183 
184   version (unittest) {
185     struct A {}
186     struct B {
187       A a;
188       int b;
189       A c;
190       string d;
191     }
192     struct C {
193       B b;
194       A a;
195     }
196 
197     static assert(is(AddStructWithDeps!C == TypeTuple!(A, B, C)));
198 
199     struct D {
200       C c;
201       mixin TStructHelpers!([TFieldMeta("c", 0, TReq.IGNORE)]);
202     }
203     static assert(is(AddStructWithDeps!D == TypeTuple!(D)));
204   }
205 
206   version (unittest) {
207     // Circles in the type dependency graph are not allowed in Thrift, but make
208     // sure we fail in a sane way instead of crashing the compiler.
209 
210     struct Rec1 {
211       Rec2[] other;
212     }
213 
214     struct Rec2 {
215       Rec1[] other;
216     }
217 
218     static assert(!__traits(compiles, AddStructWithDeps!Rec1));
219   }
220 
221   /*
222    * Returns the non-primitive types T directly depends on.
223    *
224    * For example, CompositeTypeDeps!int would yield an empty type tuple,
225    * CompositeTypeDeps!SomeStruct would give SomeStruct, and
226    * CompositeTypeDeps!(A[B]) both CompositeTypeDeps!A and CompositeTypeDeps!B.
227    */
228   template CompositeTypeDeps(T) {
229     static if (is(FullyUnqual!T == bool) || is(FullyUnqual!T == byte) ||
230       is(FullyUnqual!T == short) || is(FullyUnqual!T == int) ||
231       is(FullyUnqual!T == long) || is(FullyUnqual!T : string) ||
232       is(FullyUnqual!T == double) || is(FullyUnqual!T == void)
233     ) {
234       alias TypeTuple!() CompositeTypeDeps;
235     } else static if (is(FullyUnqual!T _ : U[], U)) {
236       alias CompositeTypeDeps!U CompositeTypeDeps;
237     } else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
238       alias CompositeTypeDeps!E CompositeTypeDeps;
239     } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
240       alias TypeTuple!(CompositeTypeDeps!K, CompositeTypeDeps!V) CompositeTypeDeps;
241     } else static if (is(FullyUnqual!T == enum) || is(FullyUnqual!T == struct) ||
242       is(FullyUnqual!T : TException)
243     ) {
244       alias TypeTuple!(FullyUnqual!T) CompositeTypeDeps;
245     } else {
246       static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
247     }
248   }
249 }
250 
251 /**
252  * Returns an IDL string describing the passed service. IDL code for any type
253  * dependcies is not included.
254  */
255 template serviceIdlString(T) if (isService!T) {
256   enum serviceIdlString = {
257     string result = "service " ~ T.stringof;
258     static if (isDerivedService!T) {
259       result ~= " extends " ~ BaseService!T.stringof;
260     }
261     result ~= " {\n";
262 
263     foreach (methodName; FilterMethodNames!(T, __traits(derivedMembers, T))) {
264       result ~= "  ";
265 
266       enum meta = find!`a.name == b`(T.methodMeta, methodName);
267 
268       static if (!meta.empty && meta.front.type == TMethodType.ONEWAY) {
269         result ~= "oneway ";
270       }
271 
272       alias ReturnType!(MemberType!(T, methodName)) RT;
273       static if (is(RT == void)) {
274         // We special-case this here instead of adding void to dToIdlType to
275         // avoid accepting things like void[].
276         result ~= "void ";
277       } else {
278         result ~= dToIdlType!RT ~ " ";
279       }
280       result ~= methodName ~ "(";
281 
282       short lastId;
283       foreach (i, ParamType; ParameterTypeTuple!(MemberType!(T, methodName))) {
284         static if (!meta.empty && i < meta.front.params.length) {
285           enum havePM = true;
286         } else {
287           enum havePM = false;
288         }
289 
290         short id;
291         static if (havePM) {
292           id = meta.front.params[i].id;
293         } else {
294           id = --lastId;
295         }
296 
297         string paramName;
298         static if (havePM) {
299           paramName = meta.front.params[i].name;
300         } else {
301           paramName = "param" ~ to!string(i + 1);
302         }
303 
304         result ~= to!string(id) ~ ": " ~ dToIdlType!ParamType ~ " " ~ paramName;
305 
306         static if (havePM && !meta.front.params[i].defaultValue.empty) {
307           result ~= " = " ~ dToIdlConst(mixin(meta.front.params[i].defaultValue));
308         } else {
309           // Unfortunately, getting the default value for parameters from a
310           // function alias isn't possible – we can't transfer the default
311           // value to the IDL e.g. for interface Foo { void foo(int a = 5); }
312           // without the user explicitly declaring it in metadata.
313         }
314         result ~= ", ";
315       }
316       result ~= ")";
317 
318       static if (!meta.empty && !meta.front.exceptions.empty) {
319         result ~= " throws (";
320         foreach (e; meta.front.exceptions) {
321           result ~= to!string(e.id) ~ ": " ~ e.type ~ " " ~ e.name ~ ", ";
322         }
323         result ~= ")";
324       }
325 
326       result ~= ",\n";
327     }
328 
329     result ~= "}\n";
330     return result;
331   }();
332 }
333 
334 /**
335  * Returns an IDL string describing the passed enum. IDL code for any type
336  * dependcies is not included.
337  */
338 template enumIdlString(T) if (isEnum!T) {
339   enum enumIdlString = {
340     static assert(is(OriginalType!T : long),
341       "Can only have integer enums in Thrift (not " ~ OriginalType!T.stringof ~
342       ", for " ~ T.stringof ~ ").");
343 
344     string result = "enum " ~ T.stringof ~ " {\n";
345 
346     foreach (name; __traits(derivedMembers, T)) {
347       result ~= "  " ~ name ~ " = " ~ dToIdlConst(GetMember!(T, name)) ~ ",\n";
348     }
349 
350     result ~= "}\n";
351     return result;
352   }();
353 }
354 
355 /**
356  * Returns an IDL string describing the passed struct. IDL code for any type
357  * dependcies is not included.
358  */
359 template structIdlString(T) if (isStruct!T || isException!T) {
360   enum structIdlString = {
361     mixin({
362       string code = "";
363       foreach (field; getFieldMeta!T) {
364         code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
365       }
366       return code;
367     }());
368 
369     string result;
370     static if (isException!T) {
371       result = "exception ";
372     } else {
373       result = "struct ";
374     }
375     result ~= T.stringof ~ " {\n";
376 
377     // The last automatically assigned id – fields with no meta information
378     // are assigned (in lexical order) descending negative ids, starting with
379     // -1, just like the Thrift compiler does.
380     short lastId;
381 
382     foreach (name; FieldNames!T) {
383       enum meta = find!`a.name == b`(getFieldMeta!T, name);
384 
385       static if (meta.empty || meta.front.req != TReq.IGNORE) {
386         short id;
387         static if (meta.empty) {
388           id = --lastId;
389         } else {
390           id = meta.front.id;
391         }
392 
393         result ~= "  " ~ to!string(id) ~ ":";
394         static if (!meta.empty) {
395           result ~= dToIdlReq(meta.front.req);
396         }
397         result ~= " " ~ dToIdlType!(MemberType!(T, name)) ~ " " ~ name;
398 
399         static if (!meta.empty && !meta.front.defaultValue.empty) {
400           result ~= " = " ~ dToIdlConst(mixin(meta.front.defaultValue));
401         } else static if (__traits(compiles, fieldInitA!(T, name))) {
402           static if (is(typeof(fieldInitA!(T, name))) &&
403             !is(typeof(fieldInitA!(T, name)) == void)
404           ) {
405             result ~= " = " ~ dToIdlConst(fieldInitA!(T, name));
406           }
407         } else static if (is(typeof(fieldInitB!(T, name))) &&
408           !is(typeof(fieldInitB!(T, name)) == void)
409         ) {
410           result ~= " = " ~ dToIdlConst(fieldInitB!(T, name));
411         }
412         result ~= ",\n";
413       }
414     }
415 
416     result ~= "}\n";
417     return result;
418   }();
419 }
420 
421 private {
422   // This very convoluted way of doing things was chosen because putting the
423   // static if directly into structIdlString caused »not evaluatable at compile
424   // time« errors to slip through even though typeof() was used, resp. the
425   // condition to be true even though the value couldn't actually be read at
426   // compile time due to a @@BUG@@ in DMD 2.055.
427   // The extra »compiled« field in fieldInitA is needed because we must not try
428   // to use != if !is compiled as well (but was false), e.g. for floating point
429   // types.
430   template fieldInitA(T, string name) {
431     static if (mixin("T.init." ~ name) !is MemberType!(T, name).init) {
432       enum fieldInitA = mixin("T.init." ~ name);
433     }
434   }
435 
436   template fieldInitB(T, string name) {
437     static if (mixin("T.init." ~ name) != MemberType!(T, name).init) {
438       enum fieldInitB = mixin("T.init." ~ name);
439     }
440   }
441 
442   template dToIdlType(T) {
443     static if (is(FullyUnqual!T == bool)) {
444       enum dToIdlType = "bool";
445     } else static if (is(FullyUnqual!T == byte)) {
446       enum dToIdlType = "byte";
447     } else static if (is(FullyUnqual!T == double)) {
448       enum dToIdlType = "double";
449     } else static if (is(FullyUnqual!T == short)) {
450       enum dToIdlType = "i16";
451     } else static if (is(FullyUnqual!T == int)) {
452       enum dToIdlType = "i32";
453     } else static if (is(FullyUnqual!T == long)) {
454       enum dToIdlType = "i64";
455     } else static if (is(FullyUnqual!T : string)) {
456       enum dToIdlType = "string";
457     } else static if (is(FullyUnqual!T _ : U[], U)) {
458       enum dToIdlType = "list<" ~ dToIdlType!U ~ ">";
459     } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
460       enum dToIdlType = "map<" ~ dToIdlType!K ~ ", " ~ dToIdlType!V ~ ">";
461     } else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
462       enum dToIdlType = "set<" ~ dToIdlType!E ~ ">";
463     } else static if (is(FullyUnqual!T == struct) || is(FullyUnqual!T == enum) ||
464       is(FullyUnqual!T : TException)
465     ) {
466       enum dToIdlType = FullyUnqual!(T).stringof;
467     } else {
468       static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
469     }
470   }
471 
472   string dToIdlReq(TReq req) {
473     switch (req) {
474       case TReq.REQUIRED: return " required";
475       case TReq.OPTIONAL: return " optional";
476       default: return "";
477     }
478   }
479 
480   string dToIdlConst(T)(T value) {
481     static if (is(FullyUnqual!T == bool)) {
482       return value ? "1" : "0";
483     } else static if (is(FullyUnqual!T == byte) ||
484       is(FullyUnqual!T == short) || is(FullyUnqual!T == int) ||
485       is(FullyUnqual!T == long)
486     ) {
487       return to!string(value);
488     } else static if (is(FullyUnqual!T : string)) {
489       return `"` ~ to!string(value) ~ `"`;
490     } else static if (is(FullyUnqual!T == double)) {
491       return ctfeToString(value);
492     } else static if (is(FullyUnqual!T _ : U[], U) ||
493       is(FullyUnqual!T _ : HashSet!E, E)
494     ) {
495       string result = "[";
496       foreach (e; value) {
497         result ~= dToIdlConst(e) ~ ", ";
498       }
499       result ~= "]";
500       return result;
501     } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
502       string result = "{";
503       foreach (key, val; value) {
504         result ~= dToIdlConst(key) ~ ": " ~ dToIdlConst(val) ~ ", ";
505       }
506       result ~= "}";
507       return result;
508     } else static if (is(FullyUnqual!T == enum)) {
509       import std.conv;
510       import std.traits;
511       return to!string(cast(OriginalType!T)value);
512     } else static if (is(FullyUnqual!T == struct) ||
513       is(FullyUnqual!T : TException)
514     ) {
515       string result = "{";
516       foreach (name; __traits(derivedMembers, T)) {
517         static if (memberReq!(T, name) != TReq.IGNORE) {
518           result ~= name ~ ": " ~ dToIdlConst(mixin("value." ~ name)) ~ ", ";
519         }
520       }
521       result ~= "}";
522       return result;
523     } else {
524       static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
525     }
526   }
527 }
528 
529 version (unittest) {
530   enum Foo {
531     a = 1,
532     b = 10,
533     c = 5
534   }
535 
536   static assert(enumIdlString!Foo ==
537 `enum Foo {
538   a = 1,
539   b = 10,
540   c = 5,
541 }
542 `);
543 }
544 
545 
546 version (unittest) {
547   struct WithoutMeta {
548     string a;
549     int b;
550   }
551 
552   struct WithDefaults {
553     string a = "asdf";
554     double b = 3.1415;
555     WithoutMeta c;
556 
557     mixin TStructHelpers!([
558       TFieldMeta("c", 1, TReq.init, `WithoutMeta("foo", 3)`)
559     ]);
560   }
561 
562   // These are from DebugProtoTest.thrift.
563   struct OneOfEach {
564     bool im_true;
565     bool im_false;
566     byte a_bite;
567     short integer16;
568     int integer32;
569     long integer64;
570     double double_precision;
571     string some_characters;
572     string zomg_unicode;
573     bool what_who;
574     string base64;
575     byte[] byte_list;
576     short[] i16_list;
577     long[] i64_list;
578 
579     mixin TStructHelpers!([
580       TFieldMeta(`im_true`, 1),
581       TFieldMeta(`im_false`, 2),
582       TFieldMeta(`a_bite`, 3, TReq.OPT_IN_REQ_OUT, q{cast(byte)127}),
583       TFieldMeta(`integer16`, 4, TReq.OPT_IN_REQ_OUT, q{cast(short)32767}),
584       TFieldMeta(`integer32`, 5),
585       TFieldMeta(`integer64`, 6, TReq.OPT_IN_REQ_OUT, q{10000000000L}),
586       TFieldMeta(`double_precision`, 7),
587       TFieldMeta(`some_characters`, 8),
588       TFieldMeta(`zomg_unicode`, 9),
589       TFieldMeta(`what_who`, 10),
590       TFieldMeta(`base64`, 11),
591       TFieldMeta(`byte_list`, 12, TReq.OPT_IN_REQ_OUT, q{{
592         byte[] v;
593         v ~= cast(byte)1;
594         v ~= cast(byte)2;
595         v ~= cast(byte)3;
596         return v;
597       }()}),
598       TFieldMeta(`i16_list`, 13, TReq.OPT_IN_REQ_OUT, q{{
599         short[] v;
600         v ~= cast(short)1;
601         v ~= cast(short)2;
602         v ~= cast(short)3;
603         return v;
604       }()}),
605       TFieldMeta(`i64_list`, 14, TReq.OPT_IN_REQ_OUT, q{{
606         long[] v;
607         v ~= 1L;
608         v ~= 2L;
609         v ~= 3L;
610         return v;
611       }()})
612     ]);
613   }
614 
615   struct Bonk {
616     int type;
617     string message;
618 
619     mixin TStructHelpers!([
620       TFieldMeta(`type`, 1),
621       TFieldMeta(`message`, 2)
622     ]);
623   }
624 
625   struct HolyMoley {
626     OneOfEach[] big;
627     HashSet!(string[]) contain;
628     Bonk[][string] bonks;
629 
630     mixin TStructHelpers!([
631       TFieldMeta(`big`, 1),
632       TFieldMeta(`contain`, 2),
633       TFieldMeta(`bonks`, 3)
634     ]);
635   }
636 
637   static assert(structIdlString!WithoutMeta ==
638 `struct WithoutMeta {
639   -1: string a,
640   -2: i32 b,
641 }
642 `);
643 
644 import std.algorithm;
645   static assert(structIdlString!WithDefaults.startsWith(
646 `struct WithDefaults {
647   -1: string a = "asdf",
648   -2: double b = 3.141`));
649 
650   static assert(structIdlString!WithDefaults.endsWith(
651 `1: WithoutMeta c = {a: "foo", b: 3, },
652 }
653 `));
654 
655   static assert(structIdlString!OneOfEach ==
656 `struct OneOfEach {
657   1: bool im_true,
658   2: bool im_false,
659   3: byte a_bite = 127,
660   4: i16 integer16 = 32767,
661   5: i32 integer32,
662   6: i64 integer64 = 10000000000,
663   7: double double_precision,
664   8: string some_characters,
665   9: string zomg_unicode,
666   10: bool what_who,
667   11: string base64,
668   12: list<byte> byte_list = [1, 2, 3, ],
669   13: list<i16> i16_list = [1, 2, 3, ],
670   14: list<i64> i64_list = [1, 2, 3, ],
671 }
672 `);
673 
674   static assert(structIdlString!Bonk ==
675 `struct Bonk {
676   1: i32 type,
677   2: string message,
678 }
679 `);
680 
681   static assert(structIdlString!HolyMoley ==
682 `struct HolyMoley {
683   1: list<OneOfEach> big,
684   2: set<list<string>> contain,
685   3: map<string, list<Bonk>> bonks,
686 }
687 `);
688 }
689 
690 version (unittest) {
691   class ExceptionWithAMap : TException {
692     string blah;
693     string[string] map_field;
694 
695     mixin TStructHelpers!([
696       TFieldMeta(`blah`, 1),
697       TFieldMeta(`map_field`, 2)
698     ]);
699   }
700 
701   interface Srv {
702     void voidMethod();
703     int primitiveMethod();
704     OneOfEach structMethod();
705     void methodWithDefaultArgs(int something);
706     void onewayMethod();
707     void exceptionMethod();
708 
709     alias .ExceptionWithAMap ExceptionWithAMap;
710 
711     enum methodMeta = [
712       TMethodMeta(`methodWithDefaultArgs`,
713         [TParamMeta(`something`, 1, q{2})]
714       ),
715       TMethodMeta(`onewayMethod`,
716         [],
717         [],
718         TMethodType.ONEWAY
719       ),
720       TMethodMeta(`exceptionMethod`,
721         [],
722         [
723           TExceptionMeta("a", 1, "ExceptionWithAMap"),
724           TExceptionMeta("b", 2, "ExceptionWithAMap")
725         ]
726       )
727     ];
728   }
729 
730   interface ChildSrv : Srv {
731     int childMethod(int arg);
732   }
733 
734   static assert(idlString!ChildSrv ==
735 `exception ExceptionWithAMap {
736   1: string blah,
737   2: map<string, string> map_field,
738 }
739 
740 struct OneOfEach {
741   1: bool im_true,
742   2: bool im_false,
743   3: byte a_bite = 127,
744   4: i16 integer16 = 32767,
745   5: i32 integer32,
746   6: i64 integer64 = 10000000000,
747   7: double double_precision,
748   8: string some_characters,
749   9: string zomg_unicode,
750   10: bool what_who,
751   11: string base64,
752   12: list<byte> byte_list = [1, 2, 3, ],
753   13: list<i16> i16_list = [1, 2, 3, ],
754   14: list<i64> i64_list = [1, 2, 3, ],
755 }
756 
757 service Srv {
758   void voidMethod(),
759   i32 primitiveMethod(),
760   OneOfEach structMethod(),
761   void methodWithDefaultArgs(1: i32 something = 2, ),
762   oneway void onewayMethod(),
763   void exceptionMethod() throws (1: ExceptionWithAMap a, 2: ExceptionWithAMap b, ),
764 }
765 
766 service ChildSrv extends Srv {
767   i32 childMethod(-1: i32 param1, ),
768 }
769 `);
770 }