Skip to content

Commit 0cd9f0d

Browse files
Printers review (#66)
This PR fixes decoders/encoders for `allOf` and `anyOf` types, sanitises identifier names, various bug fixes, refactorings and dialyzer fixes. * Fixes and refactors the `allOf` and `anyOf` printers, such that they produce correct Elm code for decoding and encoding `all_of` and `any_of` JSON schema nodes. * Sanitises / Elmifies JSON schema identifiers, such that the Elm code output is valid Elm - Moves indentation- and naming-specific logic from `printer/util.ex` into a new `printer/utils` folder, - adjusts all printers to now use the `Naming.normalize_identifier()` function before sending identifier names to Elm code templates, and - adds tests for the 'sanitise identifier' feature. * Splits the `JS2E.Printer.Util` module into a whole `printer/utils` folder in order to increase cohesion, i.e. have one util module per relevant area of printing. * Fixes all dialyzer errors except for allOf/anyOf/oneOf printers * Improves documentation - Updates 'allOf' and 'anyOf' type descriptions to reflect new Elm decoders/encoders, - updates `README.md` to include a section on `js2e` error reporting, and - creates a `CONTRIBUTING.md` file, detailing what potential contributors should know before filing issues/PRs.
1 parent b14b302 commit 0cd9f0d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2141
-1440
lines changed

CONTRIBUTING.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Contributing
2+
3+
If you have found a bug, think I have misinterpreted the JSON schema spec
4+
somewhere, or have a proposal for a new feature, feel free to open an issue so
5+
we can discuss a proper solution.
6+
7+
## Reporting bugs
8+
9+
When reporting a bug, please include:
10+
11+
- A short description of the bug,
12+
- JSON schema example that triggers the bug,
13+
- expected Elm output, and the
14+
- actual Elm output.
15+
16+
## Pull requests
17+
18+
Please do not create pull requests before an issue has been created and a
19+
solution has been discussed and agreed upon.
20+
21+
When making a pull request ensure that:
22+
23+
1. It solves one specific problem, and that problem has already been documented
24+
and discussed as an issue,
25+
2. the PR solves the problem as agreed upon in the issue,
26+
3. if it is a new feature, ensure that there is proper code coverage of the new
27+
feature, and
28+
4. the PR contains no compiler warnings or dialyzer warnings (and preferably no
29+
credo warnings).
30+
31+
## Development
32+
33+
The project is written in [Elixir](http://elixir-lang.org/) - as I found it to
34+
be a more suitable tool for the job than Elm - and uses the `mix` tool for
35+
building.
36+
37+
#### Compiling
38+
39+
Install dependencies
40+
41+
mix deps.get
42+
43+
Compile project
44+
45+
mix compile
46+
47+
and you are good to go.
48+
49+
#### Tests
50+
51+
Run the standard mix task
52+
53+
mix test
54+
55+
for test coverage run
56+
57+
mix coveralls.html
58+
59+
#### Static analysis
60+
61+
Run dialyzer
62+
63+
mix dialyzer
64+
65+
Run credo
66+
67+
mix credo

README.md

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -277,35 +277,51 @@ encodeCircle circle =
277277
++ radius
278278
```
279279

280-
## Contributing
280+
## Error reporting
281281

282-
If you feel like something is missing/wrong or if I've misinterpreted the JSON
283-
schema spec, feel free to open an issue so we can discuss a solution.
282+
Any errors encountered by the `js2e` tool while parsing the JSON schema files or
283+
printing the Elm code output, is reported in an Elm-like style, e.g.
284+
285+
```
286+
--- UNKNOWN NODE TYPE -------------------------------------- all_of_example.json
287+
288+
The value of "type" at '#/allOf/0/properties/description' did not match a known node type
289+
290+
"type": "strink"
291+
^^^^^^^^
284292
285-
### Development
293+
Was expecting one of the following types
286294
287-
As noted in the installation section, the project is written
288-
in [Elixir](http://elixir-lang.org/) - as I found it to be a more suitable tool
289-
for the job than Elm, and uses the `mix` tool for building.
295+
["null", "boolean", "object", "array", "number", "integer", "string"]
290296
291-
#### Compiling
297+
Hint: See the specification section 6.25. "Validation keywords - type"
298+
<http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.25>
299+
```
292300

293-
Install dependencies
301+
or
294302

295-
mix deps.get
303+
```
304+
--- UNRESOLVED REFERENCE ----------------------------------- all_of_example.json
296305
297-
Compile project
298306
299-
mix compile
307+
The following reference at `#/allOf/0/color` could not be resolved
300308
301-
and you are good to go.
309+
"$ref": #/definitions/kolor
310+
^^^^^^^^^^^^^^^^^^^
302311
303-
#### Tests
304312
305-
Run the standard mix task
313+
Hint: See the specification section 9. "Base URI and dereferencing"
314+
<http://json-schema.org/latest/json-schema-core.html#rfc.section.9>
315+
```
306316

307-
mix test
317+
If you encounter an error while using `js2e` that does not mimic the above
318+
Elm-like style, but instead looks like an Elixir stacktrace, please report this
319+
as a bug by opening an issue and includin a JSON schema example that recreates
320+
the error.
308321

309-
for test coverage run
322+
## Contributing
323+
324+
If you feel like something is missing/wrong or if I've misinterpreted the JSON
325+
schema spec, feel free to open an issue so we can discuss a solution.
310326

311-
mix coveralls.html
327+
Please consult `CONTRIBUTING.md` first before opening an issue.

lib/js2e.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ defmodule JS2E do
110110
def generate(schema_paths, module_name) do
111111
Logger.info("Parsing JSON schema files!")
112112
parser_result = Parser.parse_schema_files(schema_paths)
113+
113114
pretty_parser_warnings(parser_result.warnings)
114115

115116
if length(parser_result.errors) > 0 do

lib/parser/all_of_parser.ex

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ defmodule JS2E.Parser.AllOfParser do
3030
"""
3131

3232
require Logger
33-
alias JS2E.Parser.{Util, ErrorUtil, ParserResult}
33+
alias JS2E.Parser.{Util, ParserResult}
3434
alias JS2E.{Types, TypePath}
3535
alias JS2E.Types.AllOfType
3636

@@ -50,7 +50,7 @@ defmodule JS2E.Parser.AllOfParser do
5050
5151
"""
5252
@impl JS2E.Parser.ParserBehaviour
53-
@spec type?(Types.node()) :: boolean
53+
@spec type?(Types.schemaNode()) :: boolean
5454
def type?(%{"allOf" => all_of})
5555
when is_list(all_of) and length(all_of) > 0,
5656
do: true
@@ -61,8 +61,13 @@ defmodule JS2E.Parser.AllOfParser do
6161
Parses a JSON schema allOf type into an `JS2E.Types.AllOfType`.
6262
"""
6363
@impl JS2E.Parser.ParserBehaviour
64-
@spec parse(Types.node(), URI.t(), URI.t() | nil, TypePath.t(), String.t()) ::
65-
ParserResult.t()
64+
@spec parse(
65+
Types.schemaNode(),
66+
URI.t(),
67+
URI.t() | nil,
68+
TypePath.t(),
69+
String.t()
70+
) :: ParserResult.t()
6671
def parse(%{"allOf" => all_of}, parent_id, id, path, name)
6772
when is_list(all_of) do
6873
child_path = TypePath.add_child(path, "allOf")

lib/parser/error_util.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,11 @@ defmodule JS2E.Parser.ErrorUtil do
128128
ParserError.new(identifier, :invalid_uri, error_msg)
129129
end
130130

131-
@spec unknown_node_type(Types.typeIdentifier(), String.t(), Types.node()) ::
132-
ParserError.t()
131+
@spec unknown_node_type(
132+
Types.typeIdentifier(),
133+
String.t(),
134+
Types.schemaNode()
135+
) :: ParserError.t()
133136
def unknown_node_type(identifier, name, schema_node) do
134137
full_identifier =
135138
identifier
@@ -178,12 +181,12 @@ defmodule JS2E.Parser.ErrorUtil do
178181
end
179182
end
180183

181-
@spec error_markings(String.t()) :: String.t()
184+
@spec error_markings(String.t()) :: [String.t()]
182185
defp error_markings(value) do
183186
red(String.duplicate("^", String.length(value)))
184187
end
185188

186-
@spec red(String.t()) :: list
189+
@spec red(String.t()) :: [String.t()]
187190
defp red(str) do
188191
IO.ANSI.format([:red, str])
189192
end

lib/parser/one_of_parser.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ defmodule JS2E.Parser.OneOfParser do
5050
5151
"""
5252
@impl JS2E.Parser.ParserBehaviour
53-
@spec type?(Types.node()) :: boolean
53+
@spec type?(Types.schemaNode()) :: boolean
5454
def type?(schema_node) do
5555
one_of = schema_node["oneOf"]
5656
is_list(one_of) && length(one_of) > 0
@@ -60,7 +60,7 @@ defmodule JS2E.Parser.OneOfParser do
6060
Parses a JSON schema oneOf type into an `JS2E.Types.OneOfType`.
6161
"""
6262
@impl JS2E.Parser.ParserBehaviour
63-
@spec parse(Types.node(), URI.t(), URI.t(), TypePath.t(), String.t()) ::
63+
@spec parse(Types.schemaNode(), URI.t(), URI.t(), TypePath.t(), String.t()) ::
6464
ParserResult.t()
6565
def parse(%{"oneOf" => one_of}, parent_id, id, path, name)
6666
when is_list(one_of) do

lib/parser/parser.ex

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ defmodule JS2E.Parser do
55
"""
66

77
require Logger
8-
alias JS2E.Types
98
alias JS2E.Parser.{RootParser, SchemaResult, ErrorUtil}
10-
alias JS2E.Printers.Util
119

1210
@spec parse_schema_files([Path.t()]) :: SchemaResult.t()
1311
def parse_schema_files(schema_paths) do

lib/parser/parser_result_types.ex

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ defmodule JS2E.Parser.ParserResult do
9595
type dictionaries to the list of errors in the merged `ParserResult`.
9696
9797
"""
98-
@spec merge(ParserResult.t(), ParserResult.t()) :: ParserResult.t()
98+
@spec merge(t, t) :: t
9999
def merge(
100100
%__MODULE__{
101101
type_dict: type_dict1,
@@ -141,8 +141,8 @@ defmodule JS2E.Parser.SchemaResult do
141141

142142
@type t :: %__MODULE__{
143143
schema_dict: Types.schemaDictionary(),
144-
warnings: [{Path.t(), ParserWarning.t()}],
145-
errors: [{Path.t(), ParserError.t()}]
144+
warnings: [{Path.t(), [ParserWarning.t()]}],
145+
errors: [{Path.t(), [ParserError.t()]}]
146146
}
147147

148148
defstruct [:schema_dict, :warnings, :errors]
@@ -158,13 +158,9 @@ defmodule JS2E.Parser.SchemaResult do
158158
dictionary corresponding to the succesfully parsed JSON schema files,
159159
and a list of warnings and errors encountered while parsing.
160160
"""
161-
@spec new(
162-
[{Path.t(), Types.schemaDictionary()}],
163-
[{Path.t(), ParserWarning.t()}],
164-
[
165-
{Path.t(), ParserError.t()}
166-
]
167-
) :: t
161+
@spec new(Types.schemaDictionary(), [{Path.t(), [ParserWarning.t()]}], [
162+
{Path.t(), [ParserError.t()]}
163+
]) :: t
168164
def new(schema_dict, warnings \\ [], errors \\ []) do
169165
%__MODULE__{schema_dict: schema_dict, warnings: warnings, errors: errors}
170166
end
@@ -174,7 +170,7 @@ defmodule JS2E.Parser.SchemaResult do
174170
schema dictionaries to the list of errors in the merged `SchemaResult`.
175171
176172
"""
177-
@spec merge(SchemaResult.t(), SchemaResult.t()) :: SchemaResult.t()
173+
@spec merge(t, t) :: t
178174
def merge(
179175
%__MODULE__{
180176
schema_dict: schema_dict1,

lib/parser/root_parser.ex

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,29 @@ defmodule JS2E.Parser.RootParser do
66
require Logger
77

88
alias JS2E.Parser.{
9+
AllOfParser,
10+
AnyOfParser,
911
ArrayParser,
1012
DefinitionsParser,
1113
ObjectParser,
14+
OneOfParser,
1215
TupleParser,
1316
TypeReferenceParser,
1417
Util,
1518
ErrorUtil,
19+
ParserError,
1620
ParserResult,
1721
SchemaResult
1822
}
1923

2024
alias JS2E.{TypePath, Types}
2125
alias JS2E.Types.SchemaDefinition
2226

23-
@spec parse_schema(Types.schemaNode(), String.t()) :: SchemaResult.t()
27+
@spec parse_schema(Types.schemaNode(), Path.t()) :: SchemaResult.t()
2428
def parse_schema(root_node, schema_file_path) do
2529
with {:ok, _schema_version} <- parse_schema_version(root_node),
2630
{:ok, schema_id} <- parse_schema_id(root_node) do
27-
title = Map.get(root_node, "title", "")
31+
title = Map.get(root_node, "title", "Root")
2832
description = Map.get(root_node, "description")
2933

3034
root_node_no_def = Map.delete(root_node, "definitions")
@@ -89,22 +93,33 @@ defmodule JS2E.Parser.RootParser do
8993
end
9094

9195
@spec parse_root_object(map, URI.t(), String.t()) :: ParserResult.t()
92-
defp parse_root_object(schema_root_node, schema_id, _title) do
96+
defp parse_root_object(schema_root_node, schema_id, name) do
9397
type_path = TypePath.from_string("#")
94-
name = "#"
9598

9699
cond do
100+
AllOfParser.type?(schema_root_node) ->
101+
schema_root_node
102+
|> AllOfParser.parse(schema_root_node, schema_id, type_path, name)
103+
104+
AnyOfParser.type?(schema_root_node) ->
105+
schema_root_node
106+
|> AnyOfParser.parse(schema_root_node, schema_id, type_path, name)
107+
97108
ArrayParser.type?(schema_root_node) ->
98109
schema_root_node
99-
|> Util.parse_type(schema_id, [], name)
110+
|> ArrayParser.parse(schema_root_node, schema_id, type_path, name)
100111

101112
ObjectParser.type?(schema_root_node) ->
102113
schema_root_node
103-
|> Util.parse_type(schema_id, [], name)
114+
|> ObjectParser.parse(schema_root_node, schema_id, type_path, name)
115+
116+
OneOfParser.type?(schema_root_node) ->
117+
schema_root_node
118+
|> OneOfParser.parse(schema_root_node, schema_id, type_path, name)
104119

105120
TupleParser.type?(schema_root_node) ->
106121
schema_root_node
107-
|> Util.parse_type(schema_id, [], name)
122+
|> TupleParser.parse(schema_root_node, schema_id, type_path, name)
108123

109124
TypeReferenceParser.type?(schema_root_node) ->
110125
schema_root_node

lib/parser/tuple_parser.ex

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ defmodule JS2E.Parser.TupleParser do
3434
3535
"""
3636
@impl JS2E.Parser.ParserBehaviour
37-
@spec type?(Types.node()) :: boolean
37+
@spec type?(Types.schemaNode()) :: boolean
3838
def type?(schema_node) do
3939
items = schema_node["items"]
4040
is_list(items)
@@ -44,8 +44,13 @@ defmodule JS2E.Parser.TupleParser do
4444
Parses a JSON schema array type into an `JS2E.Types.TupleType`.
4545
"""
4646
@impl JS2E.Parser.ParserBehaviour
47-
@spec parse(Types.node(), URI.t(), URI.t() | nil, TypePath.t(), String.t()) ::
48-
ParserResult.t()
47+
@spec parse(
48+
Types.schemaNode(),
49+
URI.t(),
50+
URI.t() | nil,
51+
TypePath.t(),
52+
String.t()
53+
) :: ParserResult.t()
4954
def parse(%{"items" => items}, parent_id, id, path, name)
5055
when is_list(items) do
5156
child_path = TypePath.add_child(path, "items")
@@ -65,10 +70,4 @@ defmodule JS2E.Parser.TupleParser do
6570
|> ParserResult.new()
6671
|> ParserResult.merge(child_types_result)
6772
end
68-
69-
def parse(%{"items" => items}, _parent_id, _id, path, _name) do
70-
items_type = ErrorUtil.get_type(items)
71-
error = ErrorUtil.invalid_type(path, "items", "list or object", items_type)
72-
ParserResult.new(%{}, [], [error])
73-
end
7473
end

0 commit comments

Comments
 (0)