]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
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 | } |