1 module types;
2 
3 import boilerplate;
4 import std.algorithm;
5 import std.array;
6 import std.format;
7 import std.json;
8 import std.range;
9 import std.typecons;
10 import text.json.Decode;
11 import ToJson;
12 
13 abstract class Type
14 {
15     @(This.Exclude)
16     string source;
17 
18     void setSource(string source)
19     {
20         this.source = source;
21     }
22 
23     @(This.Default)
24     string description;
25 
26     // are we monadic yet
27     abstract Type transform(Type delegate(Type) dg);
28 
29     mixin(GenerateAll);
30 }
31 
32 Type decode(T)(const JSONValue value)
33 if (is(T == Type))
34 in (value.type == JSONType.array)
35 {
36     string type = value.hasKey("type") ? value.getEntry("type").str : null;
37     if (value.hasKey("allOf"))
38     {
39         const description = value.hasKey("description") ? value.getEntry("description").str : null;
40 
41         return new AllOf(value.getEntry("allOf").decodeJson!(Type[], .decode), description);
42     }
43     if (value.hasKey("oneOf"))
44     {
45         const description = value.hasKey("description") ? value.getEntry("description").str : null;
46 
47         return new OneOf(value.getEntry("oneOf").decodeJson!(Type[], .decode), description);
48     }
49     if (value.hasKey("$ref"))
50     {
51         return new Reference(value.getEntry("$ref").decodeJson!string);
52     }
53     if (type == "object" || type.empty && value.hasKey("properties"))
54     {
55         return value.toObject.decodeJson!(ObjectType, .decode);
56     }
57     if (type == "string")
58     {
59         return value.toObject.decodeJson!(StringType, .decode);
60     }
61     if (value.hasKey("enum"))
62     {
63         return new EnumType(value.getEntry("enum").decodeJson!(string[]));
64     }
65     if (type == "array")
66     {
67         return value.toObject.decodeJson!(ArrayType, .decode);
68     }
69     if (type == "bool" || type == "boolean")
70     {
71         return value.toObject.decodeJson!(BooleanType, .decode);
72     }
73     if (type == "number")
74     {
75         return value.toObject.decodeJson!(NumberType, .decode);
76     }
77     if (type == "integer")
78     {
79         return value.toObject.decodeJson!(IntegerType, .decode);
80     }
81     assert(false, format!"I don't know what this is: %s"(value));
82 }
83 
84 AdditionalProperties decode(T : AdditionalProperties)(const JSONValue value)
85 in (value.type == JSONType.array)
86 {
87     auto type = decode!Type(value);
88     auto additionalProperties = Nullable!int();
89 
90     if (value.hasKey("minProperties"))
91     {
92         additionalProperties = value.getEntry("minProperties").decodeJson!int;
93     }
94     return new AdditionalProperties(type, additionalProperties);
95 }
96 
97 private alias _ = decode!Type;
98 private alias _ = decode!AdditionalProperties;
99 
100 struct TableEntry(T)
101 {
102     string key;
103 
104     T value;
105 
106     mixin(GenerateAll);
107 }
108 
109 class ObjectType : Type
110 {
111     @(This.Default!null)
112     TableEntry!Type[] properties;
113 
114     @(This.Default!null)
115     string[] required;
116 
117     @(This.Default)
118     Nullable!AdditionalProperties additionalProperties;
119 
120     override void setSource(string source)
121     {
122         super.setSource(source);
123         foreach (entry; properties)
124         {
125             entry.value.setSource(source);
126         }
127     }
128 
129     Type findKey(string key)
130     {
131         return properties
132             .filter!(a => a.key == key)
133             .frontOrNull
134             .apply!"a.value"
135             .get(null);
136     }
137 
138     override Type transform(Type delegate(Type) dg)
139     {
140         with (ObjectType.Builder())
141         {
142             properties = this.properties
143                 .map!(a => TableEntry!Type(a.key, dg(a.value)))
144                 .array;
145             required = this.required;
146             source = this.source;
147             description = this.description;
148             additionalProperties = this.additionalProperties;
149             return value;
150         }
151     }
152 
153     mixin(GenerateAll);
154 }
155 
156 private alias frontOrNull = range => range.empty ? Nullable!(ElementType!(typeof(range)))() : range.front.nullable;
157 
158 class AdditionalProperties
159 {
160     Type type;
161 
162     @(This.Default)
163     Nullable!int minProperties;
164 
165     mixin(GenerateAll);
166 }
167 
168 class ArrayType : Type
169 {
170     Type items;
171 
172     @(This.Default)
173     Nullable!int minItems;
174 
175     override void setSource(string source)
176     {
177         super.setSource(source);
178         this.items.setSource(source);
179     }
180 
181     override Type transform(Type delegate(Type) dg)
182     {
183         auto transformed = new ArrayType(dg(this.items), this.minItems, this.description);
184         transformed.setSource(this.source);
185         return transformed;
186     }
187 
188     mixin(GenerateAll);
189 }
190 
191 class StringType : Type
192 {
193     @(This.Default)
194     string[] enum_;
195 
196     @(This.Default)
197     Nullable!string format_;
198 
199     @(This.Default)
200     Nullable!int minLength;
201 
202     override Type transform(Type delegate(Type) dg)
203     {
204         return this;
205     }
206 
207     mixin(GenerateAll);
208 }
209 
210 class EnumType : Type
211 {
212     string[] entries;
213 
214     override Type transform(Type delegate(Type) dg)
215     {
216         return this;
217     }
218 
219     mixin(GenerateAll);
220 }
221 
222 class BooleanType : Type
223 {
224     @(This.Default)
225     Nullable!bool default_;
226 
227     override Type transform(Type delegate(Type) dg)
228     {
229         return this;
230     }
231 
232     mixin(GenerateAll);
233 }
234 
235 class NumberType : Type
236 {
237     override Type transform(Type delegate(Type) dg)
238     {
239         return this;
240     }
241 
242     mixin(GenerateAll);
243 }
244 
245 class IntegerType : Type
246 {
247     @(This.Default)
248     Nullable!string format_;
249 
250     override Type transform(Type delegate(Type) dg)
251     {
252         return this;
253     }
254 
255     public string toDType()
256     {
257         switch (this.format_.get(""))
258         {
259             case "int8": return "const(ubyte)";
260             case "int16": return "short";
261             case "int32": return "int";
262             case "int64": return "long";
263             default: return "long";
264         }
265     }
266 
267     mixin(GenerateAll);
268 }
269 
270 class AllOf : Type
271 {
272     Type[] children;
273 
274     override void setSource(string source)
275     {
276         super.setSource(source);
277         foreach (child; children)
278         {
279             child.setSource(source);
280         }
281     }
282 
283     override Type transform(Type delegate(Type) dg)
284     {
285         auto transformed = new AllOf(this.children.map!(a => dg(a)).array, this.description);
286         transformed.setSource(this.source);
287         return transformed;
288     }
289 
290     mixin(GenerateAll);
291 }
292 
293 class OneOf : Type
294 {
295     Type[] children;
296 
297     override void setSource(string source)
298     {
299         super.setSource(source);
300         foreach (child; children)
301         {
302             child.setSource(source);
303         }
304     }
305 
306     override Type transform(Type delegate(Type) dg)
307     {
308         auto transformed = new OneOf(this.children.map!(a => dg(a)).array, this.description);
309         transformed.setSource(this.source);
310         return transformed;
311     }
312 
313     mixin(GenerateAll);
314 }
315 
316 class Reference : Type
317 {
318     string target;
319 
320     override Type transform(Type delegate(Type) dg)
321     {
322         return this;
323     }
324 
325     mixin(GenerateAll);
326 }