@@ -26,30 +26,23 @@ defmodule ExJsonSchema.Schema do
2626 alias ExJsonSchema.Validator
2727
2828 @ type ref_path :: [ :root | String . t ( ) ]
29- @ type resolved ::
30- ExJsonSchema . data ( )
31- | % { String . t ( ) => ( Root . t ( ) -> { Root . t ( ) , resolved } ) | ref_path }
32- | true
33- | false
29+ @ type resolved :: ExJsonSchema . data ( ) | % { String . t ( ) => ( Root . t ( ) -> { Root . t ( ) , resolved } ) | ref_path } | true | false
3430 @ type invalid_reference_error :: { :error , :invalid_reference }
3531
3632 @ current_draft_schema_url "http://json-schema.org/schema"
3733 @ draft4_schema_url "http://json-schema.org/draft-04/schema"
3834 @ draft6_schema_url "http://json-schema.org/draft-06/schema"
3935 @ draft7_schema_url "http://json-schema.org/draft-07/schema"
4036
37+ @ ignored_properties [ "const" , "default" , "enum" , "examples" ]
38+
4139 @ spec decode_json ( String . t ( ) ) :: { :ok , String . t ( ) } | { :error , String . t ( ) }
4240 def decode_json ( json ) do
43- decoder =
44- Application . get_env ( :ex_json_schema , :decode_json ) ||
45- fn _json -> raise MissingJsonDecoderError end
46-
41+ decoder = Application . get_env ( :ex_json_schema , :decode_json ) || fn _json -> raise MissingJsonDecoderError end
4742 decoder . ( json )
4843 end
4944
50- @ spec resolve ( boolean | Root . t ( ) | ExJsonSchema . object ( ) ,
51- custom_format_validator: { module ( ) , atom ( ) }
52- ) ::
45+ @ spec resolve ( boolean | Root . t ( ) | ExJsonSchema . object ( ) , custom_format_validator: { module ( ) , atom ( ) } ) ::
5346 Root . t ( ) | no_return
5447 def resolve ( schema , options \\ [ ] )
5548
@@ -75,25 +68,31 @@ defmodule ExJsonSchema.Schema do
7568 end
7669 end
7770
78- def get_fragment ( root = % Root { } , [ :root | path ] = ref ) do
79- do_get_fragment ( root . schema , path , ref )
71+ def get_fragment ( % Root { schema: schema , refs: refs } , [ :root | path ] = ref ) do
72+ case Map . get ( refs , ref_to_string ( ref ) ) do
73+ nil -> do_get_fragment ( schema , path , ref )
74+ schema -> { :ok , schema }
75+ end
8076 end
8177
82- def get_fragment ( root = % Root { } , [ url | path ] = ref ) when is_binary ( url ) do
83- do_get_fragment ( root . refs [ url ] , path , ref )
78+ def get_fragment ( % Root { refs: refs } , [ url | path ] = ref ) when is_binary ( url ) do
79+ case Map . get ( refs , ref_to_string ( ref ) ) do
80+ nil -> do_get_fragment ( refs [ url ] , path , ref )
81+ schema -> { :ok , schema }
82+ end
8483 end
8584
8685 @ spec get_fragment! ( Root . t ( ) , ref_path | ExJsonSchema . json_path ( ) ) :: resolved | no_return
87- def get_fragment! ( schema , ref ) do
88- case get_fragment ( schema , ref ) do
86+ def get_fragment! ( root , ref ) do
87+ case get_fragment ( root , ref ) do
8988 { :ok , schema } -> schema
9089 { :error , :invalid_reference } -> raise_invalid_reference_error ( ref )
9190 end
9291 end
9392
9493 @ spec get_ref_schema ( Root . t ( ) , [ :root | String . t ( ) ] ) :: ExJsonSchema . data ( ) | no_return
95- def get_ref_schema ( root = % Root { } , [ :root | path ] = ref ) do
96- case get_ref_schema_with_schema ( root . schema , path , ref ) do
94+ def get_ref_schema ( % Root { schema: schema } , [ :root | path ] = ref ) do
95+ case get_ref_schema_with_schema ( schema , path , ref ) do
9796 { :error , error } ->
9897 raise InvalidSchemaError , message: error
9998
@@ -102,8 +101,8 @@ defmodule ExJsonSchema.Schema do
102101 end
103102 end
104103
105- def get_ref_schema ( root = % Root { } , [ url | path ] = ref ) when is_binary ( url ) do
106- case get_ref_schema_with_schema ( root . refs [ url ] , path , ref ) do
104+ def get_ref_schema ( % Root { refs: refs } , [ url | path ] = ref ) when is_binary ( url ) do
105+ case get_ref_schema_with_schema ( refs [ url ] , path , ref ) do
107106 { :error , error } ->
108107 raise InvalidSchemaError , message: error
109108
@@ -128,9 +127,42 @@ defmodule ExJsonSchema.Schema do
128127 message: "schema did not pass validation against its meta-schema: #{ inspect ( errors ) } "
129128 end
130129
130+ root = % Root { root | version: schema_version }
131131 { root , schema } = resolve_with_root ( root , root_schema )
132132
133- % Root { root | schema: schema , version: schema_version }
133+ % Root { root | schema: schema }
134+ |> resolve_refs ( schema )
135+ end
136+
137+ defp resolve_refs ( % Root { } = root , schema ) when is_map ( schema ) do
138+ schema
139+ |> Enum . reduce ( root , fn
140+ { "$ref" , [ :root | _ ] } , root ->
141+ root
142+
143+ { "$ref" , ref } , root when is_list ( ref ) ->
144+ if local_ref? ( root , ref ) do
145+ root
146+ else
147+ resolve_and_cache_remote_schema ( root , ref )
148+ end
149+
150+ { _ , value } , root when is_map ( value ) ->
151+ resolve_refs ( root , value )
152+
153+ { _ , values } , root when is_list ( values ) ->
154+ values
155+ |> Enum . reduce ( root , fn value , root -> resolve_refs ( root , value ) end )
156+
157+ _ , root ->
158+ root
159+ end )
160+ end
161+
162+ defp resolve_refs ( % Root { } = root , _ ) , do: root
163+
164+ defp local_ref? ( % Root { refs: refs } , [ url | _ ] = ref ) do
165+ Map . has_key? ( refs , url ) || Map . has_key? ( refs , ref_to_string ( ref ) )
134166 end
135167
136168 defp schema_version! ( schema_url ) do
@@ -165,25 +197,30 @@ defmodule ExJsonSchema.Schema do
165197
166198 defp resolve_with_root ( root , schema , scope \\ "" )
167199
200+ defp resolve_with_root ( root , % { "$ref" => ref } , scope ) when is_binary ( ref ) do
201+ do_resolve ( root , % { "$ref" => ref } , scope )
202+ end
203+
168204 defp resolve_with_root ( root , schema = % { "$id" => id } , scope ) when is_binary ( id ) do
169- resolve_id ( root , schema , scope , id )
205+ resolve_with_id ( root , schema , scope , id )
170206 end
171207
172208 defp resolve_with_root ( root , schema = % { "id" => id } , scope ) when is_binary ( id ) do
173- resolve_id ( root , schema , scope , id )
209+ resolve_with_id ( root , schema , scope , id )
174210 end
175211
176212 defp resolve_with_root ( root , schema = % { } , scope ) , do: do_resolve ( root , schema , scope )
177213 defp resolve_with_root ( root , non_schema , _scope ) , do: { root , non_schema }
178214
179- defp resolve_id ( root , schema , scope , id ) do
215+ defp resolve_with_id ( root , schema , scope , id ) do
180216 scope =
181217 case URI . parse ( scope ) do
182218 % URI { host: nil } -> id
183219 uri -> uri |> URI . merge ( id ) |> to_string ( )
184220 end
185221
186- do_resolve ( root , schema , scope )
222+ { root , schema } = do_resolve ( root , schema , scope )
223+ { root_with_ref ( root , scope , schema ) , schema }
187224 end
188225
189226 defp do_resolve ( root , schema , scope ) do
@@ -196,12 +233,12 @@ defmodule ExJsonSchema.Schema do
196233 { root , schema |> sanitize_attributes ( ) }
197234 end
198235
199- defp resolve_property ( root , { key , value } , scope ) when is_map ( value ) do
236+ defp resolve_property ( root , { key , value } , scope ) when is_map ( value ) and key not in @ ignored_properties do
200237 { root , resolved } = resolve_with_root ( root , value , scope )
201238 { root , { key , resolved } }
202239 end
203240
204- defp resolve_property ( root , { key , values } , scope ) when is_list ( values ) do
241+ defp resolve_property ( root , { key , values } , scope ) when is_list ( values ) and key not in @ ignored_properties do
205242 { root , values } =
206243 Enum . reduce ( values , { root , [ ] } , fn value , { root , values } ->
207244 { root , resolved } = resolve_with_root ( root , value , scope )
@@ -211,19 +248,13 @@ defmodule ExJsonSchema.Schema do
211248 { root , { key , Enum . reverse ( values ) } }
212249 end
213250
214- defp resolve_property ( root , { "$ref" , ref } , scope ) do
251+ defp resolve_property ( root , { "$ref" , ref } , scope ) when is_binary ( ref ) do
252+ ref_uri = URI . parse ( ref )
253+
215254 scoped_ref =
216- case URI . parse ( ref ) do
217- # TODO: this special case is only needed until there is proper support for URL references
218- # that point to a local schema (via scope changes)
219- % URI { host: nil , path: nil } = uri ->
220- to_string ( uri )
221-
222- ref_uri ->
223- case URI . parse ( scope ) do
224- % URI { host: nil } -> ref
225- scope_uri -> URI . merge ( scope_uri , ref_uri ) |> to_string ( )
226- end
255+ case URI . parse ( scope ) do
256+ % URI { host: nil } -> ref
257+ scope_uri -> URI . merge ( scope_uri , ref_uri ) |> to_string ( )
227258 end
228259
229260 { root , path } = resolve_ref! ( root , scoped_ref )
@@ -232,19 +263,20 @@ defmodule ExJsonSchema.Schema do
232263
233264 defp resolve_property ( root , tuple , _ ) when is_tuple ( tuple ) , do: { root , tuple }
234265
235- defp resolve_ref ( root , "#" ) do
236- { :ok , { root , [ root . location ] } }
266+ defp resolve_ref ( % Root { location: location } = root , "#" ) do
267+ { :ok , { root , [ location ] } }
237268 end
238269
239270 defp resolve_ref ( root , ref ) do
240271 [ url | anchor ] = String . split ( ref , "#" )
241272 ref_path = validate_ref_path ( anchor , ref )
242273 { root , path } = root_and_path_for_url ( root , ref_path , url )
243274
244- case get_fragment ( root , path ) do
245- { :ok , _schema } -> { :ok , { root , path } }
246- error -> error
247- end
275+ { :ok , { root , path } }
276+ # case get_fragment(root, path) do
277+ # {:ok, _schema} -> {:ok, {root, path}}
278+ # error -> error
279+ # end
248280 end
249281
250282 defp resolve_ref! ( root , ref ) do
@@ -256,23 +288,23 @@ defmodule ExJsonSchema.Schema do
256288
257289 defp validate_ref_path ( [ ] , _ ) , do: nil
258290 defp validate_ref_path ( [ "" ] , _ ) , do: nil
259- defp validate_ref_path ( [ fragment = "/" <> _ ] , _ ) , do: fragment
291+ defp validate_ref_path ( [ fragment ] , _ ) when is_binary ( fragment ) , do: fragment
260292 defp validate_ref_path ( _ , ref ) , do: raise_invalid_reference_error ( ref )
261293
262- defp root_and_path_for_url ( root , fragment , "" ) do
263- { root , [ root . location | relative_path ( fragment ) ] }
294+ defp root_and_path_for_url ( % Root { location: location } = root , fragment , "" ) do
295+ { root , [ location | relative_path ( fragment ) ] }
264296 end
265297
266298 defp root_and_path_for_url ( root , fragment , url ) do
267- root = resolve_and_cache_remote_schema ( root , url )
299+ # root = resolve_and_cache_remote_schema(root, url)
268300 { root , [ url | relative_path ( fragment ) ] }
269301 end
270302
271303 defp relative_path ( nil ) , do: [ ]
272304 defp relative_path ( fragment ) , do: relative_ref_path ( fragment )
273305
274306 defp relative_ref_path ( ref ) do
275- [ "" | keys ] = unescaped_ref_segments ( ref )
307+ keys = unescaped_ref_segments ( ref )
276308
277309 Enum . map ( keys , fn key ->
278310 case key =~ ~r/ ^\d +$/ do
@@ -285,13 +317,9 @@ defmodule ExJsonSchema.Schema do
285317 end )
286318 end
287319
288- defp resolve_and_cache_remote_schema ( root , url ) do
289- if root . refs [ url ] do
290- root
291- else
292- remote_schema = remote_schema ( url )
293- resolve_remote_schema ( root , url , remote_schema )
294- end
320+ defp resolve_and_cache_remote_schema ( root , [ url | _ ] ) do
321+ remote_schema = remote_schema ( url )
322+ resolve_remote_schema ( root , url , remote_schema )
295323 end
296324
297325 @ spec remote_schema ( String . t ( ) ) :: ExJsonSchema . object ( )
@@ -303,13 +331,14 @@ defmodule ExJsonSchema.Schema do
303331
304332 defp resolve_remote_schema ( root , url , remote_schema ) do
305333 root = root_with_ref ( root , url , remote_schema )
306- resolved_root = resolve_root ( % { root | schema: remote_schema , location: url } )
307- root = % { root | refs: resolved_root . refs }
308- root_with_ref ( root , url , resolved_root . schema )
334+ % Root { schema: schema , refs: refs } = resolve_root ( % Root { root | schema: remote_schema , location: url } )
335+
336+ % Root { root | refs: refs }
337+ |> root_with_ref ( url , schema )
309338 end
310339
311- defp root_with_ref ( root , url , ref ) do
312- % { root | refs: Map . put ( root . refs , url , ref ) }
340+ defp root_with_ref ( % Root { refs: refs } = root , url , ref ) do
341+ % { root | refs: Map . put ( refs , url , ref ) }
313342 end
314343
315344 defp fetch_remote_schema ( url ) do
@@ -362,6 +391,7 @@ defmodule ExJsonSchema.Schema do
362391 defp unescaped_ref_segments ( ref ) do
363392 ref
364393 |> String . split ( "/" )
394+ |> Enum . reject ( & ( & 1 == "" ) )
365395 |> Enum . map ( fn segment ->
366396 segment
367397 |> String . replace ( "~0" , "~" )
@@ -377,8 +407,9 @@ defmodule ExJsonSchema.Schema do
377407 defp do_get_fragment ( nil , _ , _ref ) , do: { :error , :invalid_reference }
378408 defp do_get_fragment ( schema , [ ] , _ ) , do: { :ok , schema }
379409
380- defp do_get_fragment ( schema , [ key | path ] , ref ) when is_binary ( key ) ,
381- do: do_get_fragment ( Map . get ( schema , key ) , path , ref )
410+ defp do_get_fragment ( schema , [ key | path ] , ref ) when is_binary ( key ) do
411+ do_get_fragment ( Map . get ( schema , key ) , path , ref )
412+ end
382413
383414 defp do_get_fragment ( schema , [ idx | path ] , ref ) when is_integer ( idx ) do
384415 try do
@@ -408,8 +439,8 @@ defmodule ExJsonSchema.Schema do
408439 |> get_ref_schema_with_schema ( path , ref )
409440 end
410441
411- defp ref_to_string ( [ :root | path ] ) , do: [ "#" | path ] |> Enum . join ( "/" )
412- defp ref_to_string ( [ url | path ] ) , do: [ url <> "#" | path ] | > Enum . join ( "/" )
442+ defp ref_to_string ( [ :root | path ] ) , do: "# #{ path } "
443+ defp ref_to_string ( [ url | path ] ) , do: url <> "#" < > Enum . join ( path , "/" )
413444
414445 @ spec raise_invalid_reference_error ( any ) :: no_return
415446 def raise_invalid_reference_error ( ref ) when is_binary ( ref ) ,
0 commit comments