66-export ([to_json /1 , add_definition /1 , add_definition /2 , add_definition_array /2 , schema /1 ]).
77
88% % Utilities
9- -export ([enc_json /1 , dec_json /1 ]).
9+ -export ([enc_json /1 , dec_json /1 , normalize_json / 1 ]).
1010-export ([swagger_paths /1 , validate_metadata /1 ]).
1111-export ([filter_cowboy_swagger_handler /1 ]).
12- -export ([get_existing_definitions /2 ]).
12+ -export ([get_existing_definitions /2 ,
13+ get_global_spec /0 , get_global_spec /1 , set_global_spec /1 ]).
1314
1415% is_visible is used as a maps:filter/2 predicate, which requires a /2 arity function
1516-hank ([{unnecessary_function_arguments , [is_visible / 2 ]}]).
1920% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2021
2122-opaque parameter_obj () ::
22- #{ name => iodata ()
23- , in => iodata ()
24- , description => iodata ()
23+ #{ name => binary ()
24+ , in => binary ()
25+ , description => binary ()
2526 , required => boolean ()
26- , type => iodata ()
27- , schema => iodata ()
27+ , type => binary ()
28+ , schema => binary ()
2829 }.
2930-export_type ([parameter_obj / 0 ]).
3031
6263
6364% % Swagger map spec
6465-opaque swagger_map () ::
65- #{ description => iodata ()
66- , summary => iodata ()
66+ #{ description => binary ()
67+ , summary => binary ()
6768 , parameters => [parameter_obj ()]
68- , tags => [iodata ()]
69- , consumes => [iodata ()]
70- , produces => [iodata ()]
69+ , tags => [binary ()]
70+ , consumes => [binary ()]
71+ , produces => [binary ()]
7172 , responses => responses_definitions ()
7273 }.
7374-type metadata () :: trails :metadata (swagger_map ()).
8889-spec to_json ([trails :trail ()]) -> jsx :json_text ().
8990to_json (Trails ) ->
9091 Default = #{info => #{title => <<" API-DOCS" >>}},
91- GlobalSpec = normalize_map_values (
92- application :get_env (cowboy_swagger , global_spec , Default )),
92+ GlobalSpec = get_global_spec (Default ),
9393 SanitizeTrails = filter_cowboy_swagger_handler (Trails ),
9494 SwaggerSpec = create_swagger_spec (GlobalSpec , SanitizeTrails ),
9595 enc_json (SwaggerSpec ).
@@ -115,19 +115,20 @@ add_definition(Name, Properties) ->
115115 ) ->
116116 ok .
117117add_definition (Definition ) ->
118- CurrentSpec = application :get_env (cowboy_swagger , global_spec , #{}),
119- Type = definition_type (Definition ),
118+ CurrentSpec = get_global_spec (),
119+ NormDefinition = normalize_json (Definition ),
120+ Type = definition_type (NormDefinition ),
120121 NewDefinitions = maps :merge ( get_existing_definitions (CurrentSpec , Type )
121- , Definition
122+ , normalize_json ( NormDefinition )
122123 ),
123124 NewSpec = prepare_new_global_spec (CurrentSpec , NewDefinitions , Type ),
124- application : set_env ( cowboy_swagger , global_spec , NewSpec ).
125+ set_global_spec ( NewSpec ).
125126
126127definition_type (Definition ) ->
127128 case maps :values (Definition ) of
128- [#{in := In }] when In =:= query orelse In =:= path orelse In =:= header ->
129- parameters ;
130- _ -> schemas
129+ [#{<< " in " >> := In }] when In =:= << " query" >>; In =:= << " path" >>; In =:= << " header" >> ->
130+ << " parameters" >> ;
131+ _ -> << " schemas" >>
131132 end .
132133
133134-spec schema (DefinitionName :: parameter_definition_name ()) ->
@@ -159,6 +160,62 @@ dec_json(Data) ->
159160 throw (bad_json )
160161 end .
161162
163+ % % We assume the jsx representation of JSON as Erlang terms:
164+ % % true/false/null: 'true' | 'false' | 'null'
165+ % % number: integer() | float()
166+ % % string: binary() | atom()
167+ % % array: [ JSON ]
168+ % % object: #{ Label => JSON, ... } | [{ Label, JSON }] | [{}]
169+ % % date string: {{Year, Month, Day}, {Hour, Min, Sec}}
170+ % % where
171+ % % Label: binary() | atom() | integer()
172+ % %
173+ % % We also detect lists of printable characters (plain Erlang strings) and
174+ % % convert them into binaries. This use is deprecated and should be
175+ % % removed (for example, a json array [64] becomes <<"@">>).
176+ % %
177+ % % When normalizing, we make all strings and labels be binaries,
178+ % % and all objects be maps, not proplists.
179+
180+ % % @hidden
181+ -spec normalize_json (jsx :json_term ()) -> jsx :json_term ().
182+ normalize_json (Json ) when is_map (Json ) ->
183+ normalize_json_proplist (maps :to_list (Json ));
184+ normalize_json ([]) -> []; % empty array
185+ normalize_json ([{}]) -> #{}; % special case in jsx for empty map as list
186+ normalize_json ([{_K , _V } | _ ] = Json ) ->
187+ normalize_json_proplist (Json ); % map as proplist
188+ normalize_json (Json ) when is_list (Json ) ->
189+ case io_lib :printable_list (Json ) of
190+ true -> unicode :characters_to_binary (Json );
191+ false -> normalize_json_list (Json )
192+ end ;
193+ normalize_json (true ) -> true ;
194+ normalize_json (false ) -> false ;
195+ normalize_json (null ) -> null ;
196+ normalize_json (Json ) when is_atom (Json ) -> erlang :atom_to_binary (Json , utf8 );
197+ normalize_json (Json ) ->
198+ Json .
199+
200+ normalize_json_key (K ) when is_atom (K ) ->
201+ erlang :atom_to_binary (K , utf8 );
202+ normalize_json_key (K ) when is_integer (K ) ->
203+ erlang :integer_to_binary (K );
204+ normalize_json_key (K ) ->
205+ K .
206+
207+ normalize_json_proplist (Proplist ) ->
208+ F = fun ({K , V }, Acc ) ->
209+ maps :put (normalize_json_key (K ), normalize_json (V ), Acc )
210+ end ,
211+ lists :foldl (F , #{}, Proplist ).
212+
213+ normalize_json_list (List ) ->
214+ F = fun (V , Acc ) ->
215+ [normalize_json (V ) | Acc ]
216+ end ,
217+ lists :foldr (F , [], List ).
218+
162219% % @hidden
163220-spec swagger_paths ([trails :trail ()]) -> map ().
164221swagger_paths (Trails ) ->
@@ -178,49 +235,69 @@ validate_metadata(Metadata) ->
178235-spec filter_cowboy_swagger_handler ([trails :trail ()]) -> [trails :trail ()].
179236filter_cowboy_swagger_handler (Trails ) ->
180237 F = fun (Trail ) ->
181- MD = trails : metadata (Trail ),
238+ MD = get_metadata (Trail ),
182239 maps :size (maps :filter (fun is_visible /2 , MD )) /= 0
183240 end ,
184241 lists :filter (F , Trails ).
185242
186- -spec get_existing_definitions (CurrentSpec :: map (), Type :: scheams | parameters ) ->
243+ -spec get_existing_definitions (CurrentSpec :: jsx : json_term (), Type :: atom () | binary () ) ->
187244 Definition :: parameters_definitions ()
188245 | parameters_definition_array ().
189- get_existing_definitions (CurrentSpec , Type ) ->
246+ get_existing_definitions (CurrentSpec , Type ) when is_atom (Type ) ->
247+ get_existing_definitions (CurrentSpec , atom_to_binary (Type , utf8 ));
248+ get_existing_definitions (CurrentSpec , Type ) when is_binary (Type ) ->
190249 case swagger_version () of
191250 swagger_2_0 ->
192- maps :get (definitions , CurrentSpec , #{});
251+ maps :get (<< " definitions" >> , CurrentSpec , #{});
193252 openapi_3_0_0 ->
194253 case CurrentSpec of
195- #{components :=
254+ #{<< " components" >> :=
196255 #{Type := Def }} -> Def ;
197256 _Other -> #{}
198257 end
199258 end .
200259
260+ -spec get_global_spec () -> jsx :json_term ().
261+ get_global_spec () ->
262+ get_global_spec (#{}).
263+
264+ -spec get_global_spec (jsx :json_term ()) -> jsx :json_term ().
265+ get_global_spec (Default ) ->
266+ normalize_json (application :get_env (cowboy_swagger , global_spec , Default )).
267+
268+ -spec set_global_spec (jsx :json_term ()) -> ok .
269+ set_global_spec (NewSpec ) ->
270+ application :set_env (cowboy_swagger , global_spec , normalize_json (NewSpec )).
271+
272+
273+ -spec get_metadata (trails :trail ()) -> jsx :json_term ().
274+ get_metadata (Trail ) ->
275+ normalize_json (trails :metadata (Trail )).
276+
277+
201278% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
202279% % Private API.
203280% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
204281
205282% % @private
206283-spec swagger_version () -> swagger_version ().
207284swagger_version () ->
208- case application : get_env ( cowboy_swagger , global_spec , #{} ) of
209- #{openapi := " 3.0.0" } -> openapi_3_0_0 ;
210- #{swagger := " 2.0" } -> swagger_2_0 ;
285+ case get_global_spec ( ) of
286+ #{<< " openapi" >> := << " 3.0.0" >> } -> openapi_3_0_0 ;
287+ #{<< " swagger" >> := << " 2.0" >> } -> swagger_2_0 ;
211288 _Other -> swagger_2_0
212289 end .
213290
214291% % @private
215292is_visible (_Method , Metadata ) ->
216- not maps :get (hidden , Metadata , false ).
293+ not maps :get (<< " hidden" >> , Metadata , false ).
217294
218295% % @private
219296translate_swagger_paths ([], Acc ) ->
220297 Acc ;
221298translate_swagger_paths ([Trail | T ], Acc ) ->
222299 Path = normalize_path (trails :path_match (Trail )),
223- Metadata = normalize_map_values ( validate_metadata (trails : metadata (Trail ) )),
300+ Metadata = validate_metadata (get_metadata (Trail )),
224301 translate_swagger_paths (T , maps :put (Path , Metadata , Acc )).
225302
226303% % @private
@@ -255,117 +332,89 @@ normalize_path(Path) ->
255332 " \\ [|\\ ]|\\ :" , " " , [{return , binary }, global ]).
256333
257334% % @private
258- normalize_map_values (Map ) when is_map (Map ) ->
259- normalize_map_values (maps :to_list (Map ));
260- normalize_map_values (Proplist ) ->
261- F = fun ({K , []}, Acc ) ->
262- maps :put (K , normalize_list_values ([]), Acc );
263- ({K , V }, Acc ) when is_list (V ) ->
264- case io_lib :printable_list (V ) of
265- true -> maps :put (K , list_to_binary (V ), Acc );
266- false -> maps :put (K , normalize_list_values (V ), Acc )
267- end ;
268- ({K , V }, Acc ) when is_map (V ) ->
269- maps :put (K , normalize_map_values (V ), Acc );
270- ({K , V }, Acc ) ->
271- maps :put (K , V , Acc )
272- end ,
273- lists :foldl (F , #{}, Proplist ).
274-
275- % % @private
276- normalize_list_values (List ) ->
277- F = fun (V , Acc ) when is_list (V ) ->
278- case io_lib :printable_list (V ) of
279- true -> [list_to_binary (V ) | Acc ];
280- false -> [normalize_list_values (V ) | Acc ]
281- end ;
282- (V , Acc ) when is_map (V ) ->
283- [normalize_map_values (V ) | Acc ];
284- (V , Acc ) ->
285- [V | Acc ]
286- end ,
287- lists :foldr (F , [], List ).
288-
289- % % @private
290- create_swagger_spec (#{swagger := _Version } = GlobalSpec , SanitizeTrails ) ->
291- BasePath = maps :get (basePath , GlobalSpec , undefined ),
335+ create_swagger_spec (#{<<" swagger" >> := _Version } = GlobalSpec , SanitizeTrails ) ->
336+ BasePath = maps :get (<<" basePath" >>, GlobalSpec , undefined ),
292337 SwaggerPaths = swagger_paths (SanitizeTrails , BasePath ),
293- GlobalSpec #{paths => SwaggerPaths };
294- create_swagger_spec (#{openapi := _Version } = GlobalSpec , SanitizeTrails ) ->
338+ GlobalSpec #{<< " paths" >> => SwaggerPaths };
339+ create_swagger_spec (#{<< " openapi" >> := _Version } = GlobalSpec , SanitizeTrails ) ->
295340 BasePath = deconstruct_openapi_url (GlobalSpec ),
296341 SwaggerPaths = swagger_paths (SanitizeTrails , BasePath ),
297- GlobalSpec #{paths => SwaggerPaths };
342+ GlobalSpec #{<< " paths" >> => SwaggerPaths };
298343create_swagger_spec (GlobalSpec , SanitizeTrails ) ->
299- create_swagger_spec (GlobalSpec #{openapi => <<" 3.0.0" >>}, SanitizeTrails ).
344+ create_swagger_spec (GlobalSpec #{<< " openapi" >> => <<" 3.0.0" >>}, SanitizeTrails ).
300345
301346% % @private
302347deconstruct_openapi_url (GlobalSpec ) ->
303- [Server |_ ] = maps :get (servers , GlobalSpec , [#{}]),
304- Url = maps :get (url , Server , <<" " >>),
348+ [Server |_ ] = maps :get (<< " servers" >> , GlobalSpec , [#{}]),
349+ Url = maps :get (<< " url" >> , Server , <<" " >>),
305350 maps :get (path , uri_string :parse (Url )).
306351
307352% % @private
308353validate_swagger_map (Map ) ->
309354 F = fun (_K , V ) ->
310- Params = validate_swagger_map_params (maps :get (parameters , V , [])),
311- Responses = validate_swagger_map_responses (maps :get (responses , V , #{})),
312- V #{parameters => Params , responses => Responses }
355+ Params = validate_swagger_map_params (maps :get (<< " parameters" >> , V , [])),
356+ Responses = validate_swagger_map_responses (maps :get (<< " responses" >> , V , #{})),
357+ V #{<< " parameters" >> => Params , << " responses" >> => Responses }
313358 end ,
314359 maps :map (F , Map ).
315360
316361% % @private
317362validate_swagger_map_params (Params ) ->
318363 ValidateParams =
319364 fun (E ) ->
320- case maps :get (name , E , undefined ) of
365+ case maps :get (<< " name" >> , E , undefined ) of
321366 undefined -> maps :is_key (<<" $ref" >>, E );
322- _ -> {true , E #{in => maps :get (in , E , <<" path" >>)}}
367+ _ -> {true , E #{in => maps :get (<< " in " >> , E , <<" path" >>)}}
323368 end
324369 end ,
325370 lists :filtermap (ValidateParams , Params ).
326371
327372% % @private
328373validate_swagger_map_responses (Responses ) ->
329- F = fun (_K , V ) -> V #{description => maps :get (description , V , <<" " >>)} end ,
374+ F = fun (_K , V ) -> V #{<< " description" >> => maps :get (<< " description" >> , V , <<" " >>)} end ,
330375 maps :map (F , Responses ).
331376
332377% % @private
333378-spec build_definition ( Name :: parameter_definition_name ()
334379 , Properties :: property_obj ()
335380 ) ->
336381 parameters_definitions ().
337- build_definition (Name , Properties ) ->
338- #{Name => #{ type => <<" object" >>
339- , properties => Properties
382+ build_definition (Name , Properties ) when is_atom (Name ) ->
383+ build_definition (erlang :atom_to_binary (Name , utf8 ), Properties );
384+ build_definition (Name , Properties ) when is_binary (Name ) ->
385+ #{Name => #{ <<" type" >> => <<" object" >>
386+ , <<" properties" >> => Properties
340387 }}.
341388
342389% % @private
343390-spec build_definition_array ( Name :: parameter_definition_name ()
344391 , Properties :: property_obj ()
345392 ) ->
346393 parameters_definition_array ().
347- build_definition_array (Name , Properties ) ->
348- #{Name => #{ type => <<" array" >>
349- , items => #{ type => <<" object" >>
350- , properties => Properties
351- }
394+ build_definition_array (Name , Properties ) when is_atom (Name ) ->
395+ build_definition_array (erlang :atom_to_binary (Name , utf8 ), Properties );
396+ build_definition_array (Name , Properties ) when is_binary (Name ) ->
397+ #{Name => #{ <<" type" >> => <<" array" >>
398+ , <<" items" >> => #{ <<" type" >> => <<" object" >>
399+ , <<" properties" >> => Properties
400+ }
352401 }}.
353402
354403% % @private
355- -spec prepare_new_global_spec ( CurrentSpec :: map ()
404+ -spec prepare_new_global_spec ( CurrentSpec :: jsx : json_term ()
356405 , Definitions :: parameters_definitions ()
357406 | parameters_definition_array ()
358- , Type :: schemas | parameters
407+ , Type :: binary ()
359408 ) ->
360- NewSpec :: map ().
409+ NewSpec :: jsx : json_term ().
361410prepare_new_global_spec (CurrentSpec , Definitions , Type ) ->
362411 case swagger_version () of
363412 swagger_2_0 ->
364- CurrentSpec #{definitions => Definitions
413+ CurrentSpec #{<< " definitions" >> => Definitions
365414 };
366415 openapi_3_0_0 ->
367- Components = maps :get (components , CurrentSpec , #{}),
368- CurrentSpec #{components =>
416+ Components = maps :get (<< " components" >> , CurrentSpec , #{}),
417+ CurrentSpec #{<< " components" >> =>
369418 Components #{ Type => Definitions
370419 }
371420 }
0 commit comments