1 /**
2  * Authors: Tomoya Tanjo
3  * Copyright: © 2021 Tomoya Tanjo
4  * License: Apache-2.0
5  */
6 module salad.schema;
7 
8 import dyaml : Node, NodeType;
9 
10 import salad.exception;
11 import salad.meta;
12 import salad.canonicalizer;
13 import salad.type;
14 
15 import so = salad.schema_original;
16 
17 import std.algorithm : map;
18 import std.array : array;
19 import std.typecons : Tuple;
20 
21 private Optional!string concat(Optional!(string, string[]) ss)
22 {
23     import std.array : join;
24 
25     return ss.match!(
26         (string s) => Optional!string(s),
27         (string[] ss) => Optional!string(ss.join("\n")),
28         none => Optional!string.init,
29     );
30 }
31 
32 private auto canonicalize(Optional!(string, so.JsonldPredicate) jp)
33 {
34     return jp.match!(
35         (string id) {
36             auto pred = new JsonldPredicate;
37             if (id == "@id")
38             {
39                 pred._type_ = "@id";
40                 pred.identity_ = true;
41             }
42             else
43             {
44                 pred._id_ = id;
45             }
46             return pred;
47         },
48         (so.JsonldPredicate pred) => new JsonldPredicate(pred),
49         none => null,
50     );
51 }
52 
53 private Either!(
54             PrimitiveType,
55             RecordSchema,
56             EnumSchema,
57             ArraySchema,
58             string)[] canonicalize(Either!(
59                                     so.PrimitiveType,
60                                     so.RecordSchema,
61                                     so.EnumSchema,
62                                     so.ArraySchema,
63                                     string,
64                                     Either!(
65                                         so.PrimitiveType,
66                                         so.RecordSchema,
67                                         so.EnumSchema,
68                                         so.ArraySchema,
69                                         string)[]
70                                     ) type)
71 {
72     import std.range : ElementType;
73 
74     alias RetElemType = ElementType!(typeof(return));
75 
76     return type.match!(
77         (so.PrimitiveType pt) => [RetElemType(new PrimitiveType(pt))],
78         (so.RecordSchema rs) => [RetElemType(new RecordSchema(rs))],
79         (so.EnumSchema es) => [RetElemType(new EnumSchema(es))],
80         (so.ArraySchema as) => [RetElemType(new ArraySchema(as))],
81         (string str) => [RetElemType(str)],
82         (Either!(
83             so.PrimitiveType,
84             so.RecordSchema,
85             so.EnumSchema,
86             so.ArraySchema,
87             string)[] types) => types.map!(
88                 t => t.match!(
89                     (so.PrimitiveType pt) => RetElemType(new PrimitiveType(pt)),
90                     (so.RecordSchema rs) => RetElemType(new RecordSchema(rs)),
91                     (so.EnumSchema es) => RetElemType(new EnumSchema(es)),
92                     (so.ArraySchema as) => RetElemType(new ArraySchema(as)),
93                     str => RetElemType(str),
94                 )
95             ).array,
96     );
97 }
98 
99 private auto orDefault(T, U)(T val, U default_)
100 if (isOptional!T && is(T.Types[1] == U))
101 {
102     return val.match!((U u) => u, none => default_);
103 }
104 
105 @documentRoot class SaladRecordSchema
106 {
107     mixin genCanonicalizeBody!(
108         so.SaladRecordSchema,
109         "inVocab", (Optional!bool inVocab) => inVocab.orDefault(true),
110         "fields", (Optional!(so.SaladRecordField[]) fields) =>
111                         fields.match!((so.SaladRecordField[] fs) => fs.map!(f => new SaladRecordField(f)).array,
112                                       none => (SaladRecordField[]).init),
113         "doc", (Optional!(string, string[]) doc) => doc.concat,
114         "docChild", (Optional!(string, string[]) doc) => doc.concat,
115         "jsonldPredicate", (Optional!(string, so.JsonldPredicate) jp) => jp.canonicalize,
116         "documentRoot", (Optional!bool documentRoot) => documentRoot.orDefault(false),
117         "abstract", (Optional!bool abstract_) => abstract_.orDefault(false),
118         "extends", (Optional!(string, string[]) extends) => extends.match!((string s) => [s],
119                                                                            (string[] ss) => ss,
120                                                                            none => (string[]).init),
121         "specialize", (Optional!(so.SpecializeDef[]) specialize) => 
122                             specialize.match!((so.SpecializeDef[] sd) => sd.map!(s => new SpecializeDef(s)).array,
123                                               none => (SpecializeDef[]).init),
124     );
125 }
126 
127 class SaladRecordField
128 {
129     mixin genCanonicalizeBody!(
130         so.SaladRecordField,
131         "type", (Either!(
132                     so.PrimitiveType,
133                     so.RecordSchema,
134                     so.EnumSchema,
135                     so.ArraySchema,
136                     string,
137                     Either!(
138                         so.PrimitiveType,
139                         so.RecordSchema,
140                         so.EnumSchema,
141                         so.ArraySchema,
142                         string)[]
143                     ) type) => type.canonicalize,
144         "doc", (Optional!(string, string[]) doc) => doc.concat,
145         "jsonldPredicate", (Optional!(string, so.JsonldPredicate) jp) => jp.canonicalize,
146         "default", (Optional!(so.Any) default_) => default_.match!((so.Any any) => new Any(any), none => null),
147     );
148 }
149 
150 class PrimitiveType
151 {
152     mixin genCanonicalizeBody!(so.PrimitiveType);
153 }
154 
155 class Any
156 {
157     mixin genCanonicalizeBody!(so.Any);
158 }
159 
160 class RecordSchema
161 {
162     mixin genCanonicalizeBody!(
163         so.RecordSchema,
164         "fields", (Optional!(so.RecordField[]) fields) =>
165                     fields.match!((so.RecordField[] rf) => rf.map!(r => new RecordField(r)).array,
166                                   none => (RecordField[]).init),
167     );
168 }
169 
170 class RecordField
171 {
172     mixin genCanonicalizeBody!(
173         so.RecordField,
174         "type", (Either!(
175                     so.PrimitiveType,
176                     so.RecordSchema,
177                     so.EnumSchema,
178                     so.ArraySchema,
179                     string,
180                     Either!(
181                         so.PrimitiveType,
182                         so.RecordSchema,
183                         so.EnumSchema,
184                         so.ArraySchema,
185                         string)[]
186                     ) type) => type.canonicalize,
187         "doc", (Optional!(string, string[]) doc) => doc.concat,
188     );
189 }
190 
191 class EnumSchema
192 {
193     mixin genCanonicalizeBody!(so.EnumSchema);
194 }
195 
196 class ArraySchema
197 {
198     mixin genCanonicalizeBody!(
199         so.ArraySchema,
200         "items", (Either!(
201                     so.PrimitiveType,
202                     so.RecordSchema,
203                     so.EnumSchema,
204                     so.ArraySchema,
205                     string,
206                     Either!(
207                         so.PrimitiveType,
208                         so.RecordSchema,
209                         so.EnumSchema,
210                         so.ArraySchema,
211                         string)[]
212                     ) items) => items.canonicalize,
213     );
214 }
215 
216 class JsonldPredicate
217 {
218     mixin genCanonicalizeBody!(
219         so.JsonldPredicate,
220         "_id", (Optional!string _id) => _id.orDefault(""),
221         "_type", (Optional!string _type) => _type.orDefault(""),
222         "_container", (Optional!string _container) => _container.orDefault(""),
223         "identity", (Optional!bool identity) => identity.orDefault(false),
224         "noLinkCheck", (Optional!bool noLinkCheck) => noLinkCheck.orDefault(false),
225         "mapSubject", (Optional!string mapSubject) => mapSubject.orDefault(""),
226         "mapPreidcate", (Optional!string mapPredicate) => mapPredicate.orDefault(""),
227         "refScope", (Optional!int refScope) => refScope.orDefault(0),
228         "typeDSL", (Optional!bool typeDSL) => typeDSL.orDefault(false),
229         "secondaryFilesDSL", (Optional!bool secondaryFilesDSL) => secondaryFilesDSL.orDefault(false),
230         "subscope", (Optional!string subscope) => subscope.orDefault(""),
231     );
232 }
233 
234 class SpecializeDef
235 {
236     mixin genCanonicalizeBody!(so.SpecializeDef);
237 }
238 
239 @documentRoot class SaladEnumSchema
240 {
241     mixin genCanonicalizeBody!(
242         so.SaladEnumSchema,
243         "inVocab", (Optional!bool inVocab) => inVocab.orDefault(true),
244         "doc", (Optional!(string, string[]) doc) => doc.concat,
245         "docChild", (Optional!(string, string[]) docChild) => docChild.concat,
246         "jsonldPredicate", (Optional!(string, so.JsonldPredicate) jp) => jp.canonicalize,
247         "documentRoot", (Optional!bool documentRoot) => documentRoot.orDefault(false),
248         "extends", (Optional!(string, string[]) extends) => extends.match!((string s) => [s],
249                                                                            (string[] ss) => ss,
250                                                                            none => (string[]).init),
251     );
252 }
253 
254 @documentRoot class Documentation
255 {
256     mixin genCanonicalizeBody!(
257         so.Documentation,
258         "inVocab", (Optional!bool inVocab) => inVocab.orDefault(true),
259         "doc", (Optional!(string, string[]) doc) => doc.concat,
260         "docChild", (Optional!(string, string[]) docChild) => docChild.concat,
261     );
262 }