/home/runner/work/klsn/klsn/_build/test/cover/aggregate/klsn_rule_generator.html

1 -module(klsn_rule_generator).
2 -include_lib("klsn/include/klsn_rule_annotation.hrl").
3
4 -export([
5 from_json_schema/1
6 , from_json_schema/2
7 ]).
8
9 -klsn_rule_alias([
10 {json_schema, {any_of, [
11 {alias, {?MODULE, json_schema_true}},
12 {alias, {?MODULE, json_schema_false}},
13 {alias, {?MODULE, json_schema_ref}},
14 {alias, {?MODULE, json_schema_any_of}},
15 {alias, {?MODULE, json_schema_all_of}},
16 {alias, {?MODULE, json_schema_one_of}},
17 {alias, {?MODULE, json_schema_const}},
18 {alias, {?MODULE, json_schema_enum}},
19 {alias, {?MODULE, json_schema_type_list}},
20 {alias, {?MODULE, json_schema_integer}},
21 {alias, {?MODULE, json_schema_string}},
22 {alias, {?MODULE, json_schema_boolean}},
23 {alias, {?MODULE, json_schema_number}},
24 {alias, {?MODULE, json_schema_float}},
25 {alias, {?MODULE, json_schema_null}},
26 {alias, {?MODULE, json_schema_array}},
27 {alias, {?MODULE, json_schema_object}},
28 {alias, {?MODULE, json_schema_annotations}}
29 ]}}
30 , {json_schema_definitions, {map, {binstr, {alias, {?MODULE, json_schema}}}}}
31 , {json_schema_ref, {struct, #{
32 '$ref' => {required, binstr},
33 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
34 default => {optional, {alias, {?MODULE, json_value}}}
35 }}}
36 , {json_schema_true, {exact, true}}
37 , {json_schema_false, {exact, false}}
38 , {json_schema_any_of, {struct, #{
39 anyOf => {required, {list, {alias, {?MODULE, json_schema}}}},
40 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
41 default => {optional, {alias, {?MODULE, json_value}}}
42 }}}
43 , {json_schema_all_of, {struct, #{
44 allOf => {required, {list, {alias, {?MODULE, json_schema}}}},
45 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
46 default => {optional, {alias, {?MODULE, json_value}}}
47 }}}
48 , {json_schema_one_of, {struct, #{
49 oneOf => {required, {list, {alias, {?MODULE, json_schema}}}},
50 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
51 default => {optional, {alias, {?MODULE, json_value}}}
52 }}}
53 , {json_schema_type_list, {struct, #{
54 type => {required, {list, {enum, [integer, string, boolean, number, float, null, array, object]}}},
55 items => {optional, {alias, {?MODULE, json_schema}}},
56 properties => {optional, {map, {binstr, {alias, {?MODULE, json_schema}}}}},
57 required => {optional, {list, binstr}},
58 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
59 default => {optional, {alias, {?MODULE, json_value}}}
60 }}}
61 , {json_value, {any_of, [
62 {exact, null},
63 boolean,
64 number,
65 binstr,
66 {list, {alias, {?MODULE, json_value}}},
67 {map, {binstr, {alias, {?MODULE, json_value}}}}
68 ]}}
69 , {json_schema_integer, {struct, #{
70 type => {required, {enum, [integer]}},
71 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
72 default => {optional, {alias, {?MODULE, json_value}}}
73 }}}
74 , {json_schema_string, {struct, #{
75 type => {required, {enum, [string]}},
76 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
77 default => {optional, {alias, {?MODULE, json_value}}}
78 }}}
79 , {json_schema_boolean, {struct, #{
80 type => {required, {enum, [boolean]}},
81 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
82 default => {optional, {alias, {?MODULE, json_value}}}
83 }}}
84 , {json_schema_number, {struct, #{
85 type => {required, {enum, [number]}},
86 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
87 default => {optional, {alias, {?MODULE, json_value}}}
88 }}}
89 , {json_schema_float, {struct, #{
90 type => {required, {enum, [float]}},
91 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
92 default => {optional, {alias, {?MODULE, json_value}}}
93 }}}
94 , {json_schema_null, {struct, #{
95 type => {required, {enum, [null]}},
96 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
97 default => {optional, {alias, {?MODULE, json_value}}}
98 }}}
99 , {json_schema_array, {struct, #{
100 type => {required, {enum, [array]}},
101 items => {optional, {alias, {?MODULE, json_schema}}},
102 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
103 default => {optional, {alias, {?MODULE, json_value}}}
104 }}}
105 , {json_schema_const, {struct, #{
106 const => {required, {any_of, [{exact, null}, boolean, number, binstr]}},
107 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
108 default => {optional, {alias, {?MODULE, json_value}}}
109 }}}
110 , {json_schema_enum, {struct, #{
111 enum => {required, {list, binstr}},
112 definitions => {optional, {alias, {?MODULE, json_schema_definitions}}},
113 default => {optional, {alias, {?MODULE, json_value}}}
114 }}}
115 , {json_schema_object, {struct, #{
116 type => {required, {enum, [object]}}
117 , properties => {optional, {map, {binstr, {alias, {?MODULE, json_schema}}}}}
118 , required => {optional, {list, binstr}}
119 , additionalProperties => {optional, {any_of, [boolean, {alias, {?MODULE, json_schema}}]}}
120 , definitions => {optional, {alias, {?MODULE, json_schema_definitions}}}
121 , default => {optional, {alias, {?MODULE, json_value}}}
122 }}}
123 , {json_schema_annotations, {struct, #{
124 description => {optional, binstr},
125 title => {optional, binstr},
126 default => {optional, {alias, {?MODULE, json_value}}},
127 examples => {optional, {list, {alias, {?MODULE, json_value}}}},
128 example => {optional, {alias, {?MODULE, json_value}}},
129 '$comment' => {optional, binstr},
130 deprecated => {optional, boolean},
131 readOnly => {optional, boolean},
132 writeOnly => {optional, boolean},
133 format => {optional, binstr}
134 }}}
135 ]).
136 -type json_schema() :: klsn_rule:alias(json_schema).
137 -type opts() :: #{}.
138
139 -klsn_rule_alias([
140 {opts_rule, {struct, #{}}}
141 , {json_schema_rules, {struct, #{
142 from_json => {required, term}
143 , to_json => {required, term}
144 }}}
145 ]).
146 -type json_schema_rules() :: #{
147 from_json => klsn_rule:rule()
148 , to_json => klsn_rule:rule()
149 }.
150
151 %% @doc
152 %% Do not accept JSON Schema from user input; this function uses binary_to_atom/2
153 %% when converting property names and enum values, which can exhaust the atom table.
154 %% Not for production use: generating rules at runtime is for development only.
155 %% Generate rules ahead of time and include them in releases.
156 -spec from_json_schema(json_schema()) -> #{from_json := klsn_rule:rule(), to_json := klsn_rule:rule()}.
157 from_json_schema(Schema) ->
158 185 from_json_schema(Schema, #{}).
159
160 -klsn_input_rule([{alias, {?MODULE, json_schema}}, {alias, {?MODULE, opts_rule}}]).
161 -klsn_output_rule({alias, {?MODULE, json_schema_rules}}).
162 -spec from_json_schema(json_schema(), opts()) ->
163 #{from_json := klsn_rule:rule(), to_json := klsn_rule:rule()}.
164 13145 from_json_schema(Schema, Opts) ->
165 13145 Rules = from_json_schema_base_(Schema, Opts),
166 13145 case Schema of
167 Map when is_map(Map) ->
168 12986 case klsn_map:lookup([definitions], Map) of
169 none ->
170 12899 Rules;
171 {value, Defs} when is_map(Defs) ->
172 87 {FromDefs, ToDefs} = lists:foldl(fun({Name, DefSchema}, {FromAcc, ToAcc}) ->
173 1241 #{from_json := FromRule, to_json := ToRule} = from_json_schema(DefSchema, Opts),
174 1241 {maps:put(Name, FromRule, FromAcc), maps:put(Name, ToRule, ToAcc)}
175 end, {#{}, #{}}, maps:to_list(Defs)),
176 87 #{from_json := FromRule, to_json := ToRule} = Rules,
177 87 #{from_json => {with_defs, {FromDefs, FromRule}},
178 to_json => {with_defs, {ToDefs, ToRule}}};
179 {value, _} ->
180
:-(
error({klsn_rule_generator, unsupported_schema, Schema})
181 end;
182 _ ->
183 159 Rules
184 end.
185
186 from_json_schema_base_(true, _Opts) ->
187 158 json_rules_from_rule_(term);
188 from_json_schema_base_(false, _Opts) ->
189 1 json_rules_from_rule_({enum, []});
190 from_json_schema_base_(#{'$ref' := Ref}=Schema, _Opts) ->
191 1793 RefName = case Ref of
192 <<"#/definitions/", Name/binary>> when Name =/= <<>> ->
193 1793 Name;
194 _ ->
195
:-(
error({klsn_rule_generator, unsupported_ref, Ref})
196 end,
197 1793 json_rules_from_rule_(with_default_(Schema, {ref, RefName}));
198 from_json_schema_base_(#{anyOf := Schemas}=Schema, Opts) ->
199 377 {FromRules, ToRules} = json_rule_list_(Schemas, Opts),
200 377 #{from_json => with_default_(Schema, {any_of, FromRules}),
201 to_json => with_default_(Schema, {any_of, ToRules})};
202 from_json_schema_base_(#{allOf := Schemas}=Schema, Opts) ->
203 256 {FromRules, ToRules} = json_rule_list_(Schemas, Opts),
204 256 #{from_json => with_default_(Schema, {all_of, FromRules}),
205 to_json => with_default_(Schema, {all_of, ToRules})};
206 from_json_schema_base_(#{oneOf := Schemas}=Schema, Opts) ->
207 330 {FromRules, ToRules} = json_rule_list_(Schemas, Opts),
208 330 #{from_json => with_default_(Schema, {any_of, FromRules}),
209 to_json => with_default_(Schema, {any_of, ToRules})};
210 from_json_schema_base_(#{const := Const}=Schema, _Opts) ->
211 1 json_rules_from_rule_(with_default_(Schema, {exact, Const}));
212 from_json_schema_base_(#{enum := Enum}=Schema, _Opts) ->
213 2083 EnumAtoms = [binary_to_atom(Value, utf8) || Value <- Enum],
214 2083 json_rules_from_rule_(with_default_(Schema, {enum, EnumAtoms}));
215 from_json_schema_base_(#{type := Types}=Schema, Opts) when is_list(Types) ->
216 1372 SchemaNoDefault = maps:remove(default, Schema),
217 1372 {FromRev, ToRev} = lists:foldl(fun(Type, {FromAcc, ToAcc}) ->
218 2744 Schema1 = maps:put(type, Type, SchemaNoDefault),
219 2744 #{from_json := FromRule, to_json := ToRule} = from_json_schema_base_(Schema1, Opts),
220 2744 {[FromRule|FromAcc], [ToRule|ToAcc]}
221 end, {[], []}, Types),
222 1372 FromRules = lists:reverse(FromRev),
223 1372 ToRules = lists:reverse(ToRev),
224 1372 #{from_json => with_default_(Schema, {any_of, FromRules}),
225 to_json => with_default_(Schema, {any_of, ToRules})};
226 from_json_schema_base_(#{type := integer}=Schema, _Opts) ->
227 498 json_rules_from_rule_(with_default_(Schema, integer));
228 from_json_schema_base_(#{type := string}=Schema, _Opts) ->
229 3654 json_rules_from_rule_(with_default_(Schema, binstr));
230 from_json_schema_base_(#{type := boolean}=Schema, _Opts) ->
231 210 json_rules_from_rule_(with_default_(Schema, boolean));
232 from_json_schema_base_(#{type := number}=Schema, _Opts) ->
233 20 json_rules_from_rule_(with_default_(Schema, number));
234 from_json_schema_base_(#{type := float}=Schema, _Opts) ->
235 1 json_rules_from_rule_(with_default_(Schema, float));
236 from_json_schema_base_(#{type := null}=Schema, _Opts) ->
237 1738 json_rules_from_rule_(with_default_(Schema, {exact, null}));
238 from_json_schema_base_(#{type := array}=Schema, Opts) ->
239 594 case klsn_map:lookup([items], Schema) of
240 {value, Items} ->
241 593 #{from_json := FromItem, to_json := ToItem} = from_json_schema(Items, Opts),
242 593 #{from_json => with_default_(Schema, {list, FromItem}),
243 to_json => with_default_(Schema, {list, ToItem})};
244 none ->
245 1 json_rules_from_rule_(with_default_(Schema, {list, term}))
246 end;
247 from_json_schema_base_(#{type := object}=Schema, Opts) ->
248 2791 Properties = maps:get(properties, Schema, #{}),
249 2791 Required = maps:get(required, Schema, []),
250 2791 case Properties =:= #{} of
251 true ->
252 127 case Required of
253 [] ->
254 127 case maps:get(additionalProperties, Schema, none) of
255 none ->
256 46 json_rules_from_rule_(with_default_(Schema, {map, {binstr, term}}));
257 true ->
258
:-(
json_rules_from_rule_(with_default_(Schema, {map, {binstr, term}}));
259 false ->
260
:-(
json_rules_from_rule_(with_default_(Schema, {struct, #{}}));
261 AddSchema when is_map(AddSchema) ->
262 81 #{from_json := FromItem, to_json := ToItem} = from_json_schema(AddSchema, Opts),
263 81 #{from_json => with_default_(Schema, {map, {binstr, FromItem}}),
264 to_json => with_default_(Schema, {map, {binstr, ToItem}})};
265 _ ->
266
:-(
error({klsn_rule_generator, unsupported_schema, Schema})
267 end;
268 _ ->
269
:-(
error({klsn_rule_generator, unsupported_schema, Schema})
270 end;
271 false ->
272 2664 PropList = maps:to_list(Properties),
273 2664 MissingRequired = lists:filter(fun(Key) ->
274 5922 not maps:is_key(Key, Properties)
275 end, Required),
276 2664 case MissingRequired of
277 [] ->
278 2664 {FromMap, ToMap} = lists:foldl(fun({PropName, PropSchema}, {FromAcc, ToAcc}) ->
279 8039 #{from_json := FromJson, to_json := ToJson} = from_json_schema(PropSchema, Opts),
280 8039 ReqOpt = case lists:member(PropName, Required) of
281 5922 true -> required;
282 2117 false -> optional
283 end,
284 8039 Field = binary_to_atom(PropName, utf8),
285 8039 {maps:put(Field, {ReqOpt, FromJson}, FromAcc),
286 maps:put(Field, {ReqOpt, ToJson}, ToAcc)}
287 end, {#{}, #{}}, PropList),
288 2664 #{from_json => with_default_(Schema, {struct, FromMap}),
289 to_json => with_default_(Schema, {struct, ToMap})};
290 _ ->
291
:-(
error({klsn_rule_generator, unsupported_schema, Schema})
292 end
293 end;
294 from_json_schema_base_(Schema, _Opts) when is_map(Schema) ->
295 12 case annotation_only_schema_(Schema) of
296 true ->
297 12 json_rules_from_rule_(with_default_(Schema, term));
298 false ->
299
:-(
error({klsn_rule_generator, unsupported_schema, Schema})
300 end;
301 from_json_schema_base_(Schema, _Opts) ->
302
:-(
error({klsn_rule_generator, unsupported_schema, Schema}).
303
304 json_rules_from_rule_(Rule) ->
305 10216 #{from_json => Rule, to_json => Rule}.
306
307 json_rule_list_(Schemas, Opts) ->
308 963 {FromRev, ToRev} = lists:foldl(fun(ItemSchema, {FromAcc, ToAcc}) ->
309 3006 #{from_json := FromRule, to_json := ToRule} = from_json_schema(ItemSchema, Opts),
310 3006 {[FromRule|FromAcc], [ToRule|ToAcc]}
311 end, {[], []}, Schemas),
312 963 {lists:reverse(FromRev), lists:reverse(ToRev)}.
313
314 with_default_(Schema, Rule) ->
315 21403 case klsn_map:lookup([default], Schema) of
316 {value, Default} ->
317 523 {default, {Default, Rule}};
318 none ->
319 20880 Rule
320 end.
321
322 annotation_only_schema_(Schema) ->
323 12 maps:fold(fun(Key, _Value, Acc) ->
324 11 Acc andalso annotation_key_(Key)
325 end, true, Schema).
326
327 annotation_key_(Key) ->
328 11 case Key of
329 11 description -> true;
330
:-(
title -> true;
331
:-(
default -> true;
332
:-(
examples -> true;
333
:-(
example -> true;
334
:-(
'$comment' -> true;
335
:-(
deprecated -> true;
336
:-(
readOnly -> true;
337
:-(
writeOnly -> true;
338
:-(
format -> true;
339
:-(
<<"description">> -> true;
340
:-(
<<"title">> -> true;
341
:-(
<<"default">> -> true;
342
:-(
<<"examples">> -> true;
343
:-(
<<"example">> -> true;
344
:-(
<<"$comment">> -> true;
345
:-(
<<"deprecated">> -> true;
346
:-(
<<"readOnly">> -> true;
347
:-(
<<"writeOnly">> -> true;
348
:-(
<<"format">> -> true;
349
:-(
_ -> false
350 end.
Line Hits Source