Skip to content

Commit 8746ba0

Browse files
PR for "#4: Add support for 'anyOf' and 'allOf' keywords" (#31)
* #4: Adds support for the 'anyOf' keyword * #4: Adds support for the 'allOf' keyword Resolves #4
1 parent cdefc49 commit 8746ba0

14 files changed

+1509
-8
lines changed

lib/parser.ex

+15-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ defmodule JS2E.Parser do
55
"""
66

77
require Logger
8-
alias JS2E.Parsers.{ArrayParser, UnionParser, PrimitiveParser,
9-
DefinitionsParser, OneOfParser, ObjectParser,
10-
EnumParser, TypeReferenceParser}
8+
alias JS2E.Parsers.{ArrayParser, ObjectParser, EnumParser, PrimitiveParser,
9+
DefinitionsParser, AllOfParser, AnyOfParser, OneOfParser,
10+
UnionParser, TypeReferenceParser}
1111
alias JS2E.{TypePath, Types}
1212
alias JS2E.Types.SchemaDefinition
1313

@@ -167,6 +167,8 @@ defmodule JS2E.Parser do
167167
{&ref_type?/1, &TypeReferenceParser.parse/5},
168168
{&enum_type?/1, &EnumParser.parse/5},
169169
{&union_type?/1, &UnionParser.parse/5},
170+
{&all_of_type?/1, &AllOfParser.parse/5},
171+
{&any_of_type?/1, &AnyOfParser.parse/5},
170172
{&one_of_type?/1, &OneOfParser.parse/5},
171173
{&object_type?/1, &ObjectParser.parse/5},
172174
{&array_type?/1, &ArrayParser.parse/5},
@@ -215,6 +217,16 @@ defmodule JS2E.Parser do
215217
Map.has_key?(schema_node, "enum")
216218
end
217219

220+
@spec all_of_type?(map) :: boolean
221+
defp all_of_type?(schema_node) do
222+
Map.get(schema_node, "allOf")
223+
end
224+
225+
@spec any_of_type?(map) :: boolean
226+
defp any_of_type?(schema_node) do
227+
Map.get(schema_node, "anyOf")
228+
end
229+
218230
@spec one_of_type?(map) :: boolean
219231
defp one_of_type?(schema_node) do
220232
Map.get(schema_node, "oneOf")

lib/parsers/all_of_parser.ex

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
defmodule JS2E.Parsers.AllOfParser do
2+
@moduledoc ~S"""
3+
Parses a JSON schema allOf type:
4+
5+
{
6+
"allOf": [
7+
{
8+
"type": "object",
9+
"properties": {
10+
"color": {
11+
"$ref": "#/color"
12+
},
13+
"title": {
14+
"type": "string"
15+
},
16+
"radius": {
17+
"type": "number"
18+
}
19+
},
20+
"required": [ "color", "radius" ]
21+
},
22+
{
23+
"type": "string"
24+
}
25+
]
26+
}
27+
28+
Into an `JS2E.Types.AllOfType`.
29+
"""
30+
31+
require Logger
32+
alias JS2E.{Types, TypePath, Parser}
33+
alias JS2E.Parsers.Util
34+
alias JS2E.Types.AllOfType
35+
36+
@doc ~S"""
37+
Parses a JSON schema allOf type into an `JS2E.Types.AllOfType`.
38+
"""
39+
@spec parse(map, URI.t, URI.t, TypePath.t, String.t)
40+
:: Types.typeDictionary
41+
def parse(schema_node, parent_id, id, path, name) do
42+
Logger.debug "Parsing '#{inspect path}' as allOf type"
43+
44+
descendants_types_dict =
45+
schema_node
46+
|> Map.get("allOf")
47+
|> create_descendants_type_dict(parent_id, path)
48+
Logger.debug "Descendants types dict: #{inspect descendants_types_dict}"
49+
50+
all_of_types =
51+
descendants_types_dict
52+
|> create_types_list(path)
53+
Logger.debug "AllOf types: #{inspect all_of_types}"
54+
55+
all_of_type = %AllOfType{name: name,
56+
path: path,
57+
types: all_of_types}
58+
Logger.debug "Parsed allOf type: #{inspect all_of_type}"
59+
60+
all_of_type
61+
|> Util.create_type_dict(path, id)
62+
|> Map.merge(descendants_types_dict)
63+
end
64+
65+
@spec create_descendants_type_dict([map], URI.t, TypePath.t)
66+
:: Types.typeDictionary
67+
defp create_descendants_type_dict(types, parent_id, path) do
68+
types
69+
|> Enum.reduce({%{}, 0}, fn(child_node, {type_dict, idx}) ->
70+
71+
child_name = to_string idx
72+
child_types = Parser.parse_type(child_node, parent_id, path, child_name)
73+
74+
{Map.merge(type_dict, child_types), idx + 1}
75+
end)
76+
|> elem(0)
77+
end
78+
79+
@spec create_types_list(Types.typeDictionary, TypePath.t) :: [TypePath.t]
80+
defp create_types_list(type_dict, path) do
81+
type_dict
82+
|> Enum.reduce(%{}, fn({child_abs_path, child_type}, reference_dict) ->
83+
84+
child_type_path = TypePath.add_child(path, child_type.name)
85+
86+
if child_type_path == TypePath.from_string(child_abs_path) do
87+
Map.merge(reference_dict, %{child_type.name => child_type_path})
88+
else
89+
reference_dict
90+
end
91+
92+
end)
93+
|> Map.values()
94+
end
95+
96+
end

lib/parsers/any_of_parser.ex

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
defmodule JS2E.Parsers.AnyOfParser do
2+
@moduledoc ~S"""
3+
Parses a JSON schema anyOf type:
4+
5+
{
6+
"anyOf": [
7+
{
8+
"type": "object",
9+
"properties": {
10+
"color": {
11+
"$ref": "#/color"
12+
},
13+
"title": {
14+
"type": "string"
15+
},
16+
"radius": {
17+
"type": "number"
18+
}
19+
},
20+
"required": [ "color", "radius" ]
21+
},
22+
{
23+
"type": "string"
24+
}
25+
]
26+
}
27+
28+
Into an `JS2E.Types.AnyOfType`.
29+
"""
30+
31+
require Logger
32+
alias JS2E.{Types, TypePath, Parser}
33+
alias JS2E.Parsers.Util
34+
alias JS2E.Types.AnyOfType
35+
36+
@doc ~S"""
37+
Parses a JSON schema anyOf type into an `JS2E.Types.AnyOfType`.
38+
"""
39+
@spec parse(map, URI.t, URI.t, TypePath.t, String.t)
40+
:: Types.typeDictionary
41+
def parse(schema_node, parent_id, id, path, name) do
42+
Logger.debug "Parsing '#{inspect path}' as anyOf type"
43+
44+
descendants_types_dict =
45+
schema_node
46+
|> Map.get("anyOf")
47+
|> create_descendants_type_dict(parent_id, path)
48+
Logger.debug "Descendants types dict: #{inspect descendants_types_dict}"
49+
50+
any_of_types =
51+
descendants_types_dict
52+
|> create_types_list(path)
53+
Logger.debug "AnyOf types: #{inspect any_of_types}"
54+
55+
any_of_type = %AnyOfType{name: name,
56+
path: path,
57+
types: any_of_types}
58+
Logger.debug "Parsed anyOf type: #{inspect any_of_type}"
59+
60+
any_of_type
61+
|> Util.create_type_dict(path, id)
62+
|> Map.merge(descendants_types_dict)
63+
end
64+
65+
@spec create_descendants_type_dict([map], URI.t, TypePath.t)
66+
:: Types.typeDictionary
67+
defp create_descendants_type_dict(types, parent_id, path) do
68+
types
69+
|> Enum.reduce({%{}, 0}, fn(child_node, {type_dict, idx}) ->
70+
71+
child_name = to_string idx
72+
child_types = Parser.parse_type(child_node, parent_id, path, child_name)
73+
74+
{Map.merge(type_dict, child_types), idx + 1}
75+
end)
76+
|> elem(0)
77+
end
78+
79+
@spec create_types_list(Types.typeDictionary, TypePath.t) :: [TypePath.t]
80+
defp create_types_list(type_dict, path) do
81+
type_dict
82+
|> Enum.reduce(%{}, fn({child_abs_path, child_type}, reference_dict) ->
83+
84+
child_type_path = TypePath.add_child(path, child_type.name)
85+
86+
if child_type_path == TypePath.from_string(child_abs_path) do
87+
Map.merge(reference_dict, %{child_type.name => child_type_path})
88+
else
89+
reference_dict
90+
end
91+
92+
end)
93+
|> Map.values()
94+
end
95+
96+
end

lib/parsers/one_of_parser.ex

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ defmodule JS2E.Parsers.OneOfParser do
6868
types
6969
|> Enum.reduce({%{}, 0}, fn(child_node, {type_dict, idx}) ->
7070

71-
child_name = "#{idx}"
71+
child_name = to_string idx
7272
child_types = Parser.parse_type(child_node, parent_id, path, child_name)
7373

7474
{Map.merge(type_dict, child_types), idx + 1}
@@ -88,6 +88,7 @@ defmodule JS2E.Parsers.OneOfParser do
8888
else
8989
reference_dict
9090
end
91+
9192
end)
9293
|> Map.values()
9394
end

lib/printer.ex

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ defmodule JS2E.Printer do
77
require Logger
88
alias JS2E.{TypePath, Types}
99
alias JS2E.Printers.{ArrayPrinter, EnumPrinter, ObjectPrinter,
10-
OneOfPrinter, PrimitivePrinter, UnionPrinter,
11-
PreamblePrinter, TypeReferencePrinter, Util}
10+
AllOfPrinter, AnyOfPrinter, OneOfPrinter,
11+
PrimitivePrinter, UnionPrinter, PreamblePrinter,
12+
TypeReferencePrinter, Util}
1213
alias JS2E.Types.{PrimitiveType, TypeReference, SchemaDefinition}
1314

1415
@primitive_types ["boolean", "null", "string", "number", "integer"]
@@ -97,6 +98,8 @@ defmodule JS2E.Printer do
9798
"EnumType" => &EnumPrinter.print_type/3,
9899
"ObjectType" => &ObjectPrinter.print_type/3,
99100
"PrimitiveType" => &PrimitivePrinter.print_type/3,
101+
"AllOfType" => &AllOfPrinter.print_type/3,
102+
"AnyOfType" => &AnyOfPrinter.print_type/3,
100103
"OneOfType" => &OneOfPrinter.print_type/3,
101104
"UnionType" => &UnionPrinter.print_type/3,
102105
"TypeReference" => &TypeReferencePrinter.print_type/3
@@ -123,6 +126,8 @@ defmodule JS2E.Printer do
123126
"EnumType" => &EnumPrinter.print_decoder/3,
124127
"ObjectType" => &ObjectPrinter.print_decoder/3,
125128
"PrimitiveType" => &PrimitivePrinter.print_decoder/3,
129+
"AllOfType" => &AllOfPrinter.print_decoder/3,
130+
"AnyOfType" => &AnyOfPrinter.print_decoder/3,
126131
"OneOfType" => &OneOfPrinter.print_decoder/3,
127132
"UnionType" => &UnionPrinter.print_decoder/3,
128133
"TypeReference" => &TypeReferencePrinter.print_decoder/3
@@ -150,6 +155,8 @@ defmodule JS2E.Printer do
150155
"EnumType" => &EnumPrinter.print_encoder/3,
151156
"ObjectType" => &ObjectPrinter.print_encoder/3,
152157
"PrimitiveType" => &PrimitivePrinter.print_encoder/3,
158+
"AllOfType" => &AllOfPrinter.print_encoder/3,
159+
"AnyOfType" => &AnyOfPrinter.print_encoder/3,
153160
"OneOfType" => &OneOfPrinter.print_encoder/3,
154161
"UnionType" => &UnionPrinter.print_encoder/3,
155162
"TypeReference" => &TypeReferencePrinter.print_encoder/3

0 commit comments

Comments
 (0)