Skip to content

Commit f675064

Browse files
Error reporting (#48)
Adds Elm-style error reporting to js2e - Streamlines error handling across all files using `ParserResult` and `PrinterResult` types, which encapsulate the result of parsing/printing a schema and any warnings/errors encountered along the way, - adds parser and printer error util modules for pretty printing errors, - moves the logic from Predicates.ex into their respective parser files, - moves schema version logic into its own module, - splits most of parser.ex into root parser and parser util, - adds missing 'properties', 'anyOf', 'items', etc. to `TypePath`s, - updates README, and - Closes #23
1 parent ea92cf4 commit f675064

Some content is hidden

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

76 files changed

+3681
-1948
lines changed

.tool-versions

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
erlang 20.0
1+
erlang 20.1
22
elixir 1.5.1

README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ folder which contains an example of a pair of JSON schemas and their
2929
corresponding Elm output. Likewise, representations of each of the different
3030
JSON schema types are described in the `lib/types` folder.
3131

32+
The tool aims to produce `elm-make`-like errors if something is missing,
33+
mispelled or cannot be resolved in the supplied JSON schema file(s). If you
34+
experience errors that look more like stack traces, feel free to open an issue
35+
so it can be fixed.
36+
3237
## Example
3338

3439
If we supply `js2e` with the following JSON schema file, `definitions.json`:
@@ -269,7 +274,30 @@ encodeCircle circle =
269274
++ radius
270275
```
271276

272-
## Tests
277+
## Contributing
278+
279+
If you feel like something is missing/wrong or if I've misinterpreted the JSON
280+
schema spec, feel free to open an issue so we can discuss a solution.
281+
282+
### Development
283+
284+
As noted in the installation section, the project is written
285+
in [Elixir](http://elixir-lang.org/) - as I found it to be a more suitable tool
286+
for the job than Elm, and uses the `mix` tool for building.
287+
288+
#### Compiling
289+
290+
Install dependencies
291+
292+
mix deps.get
293+
294+
Compile project
295+
296+
mix compile
297+
298+
and you are good to go.
299+
300+
#### Tests
273301

274302
Run the standard mix task
275303

config/config.exs

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use Mix.Config
22

3+
config :elixir, ansi_enabled: true
4+
35
config :js2e,
46
templates_location: "./priv/templates/"
57

config/prod.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
use Mix.Config
22

3-
config :logger, level: :info
3+
config :logger, level: :warn

coveralls.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"coverage_options": {
3+
"treat_no_relevant_lines_as_covered": true,
4+
"minimum_coverage": 80
5+
}
6+
}

examples/example-output-elm-code/Domain/Circle.elm

+1-3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,4 @@ encodeCircle circle =
6565
[ ( "radius", Encode.float circle.radius ) ]
6666
in
6767
object <|
68-
center
69-
++ color
70-
++ radius
68+
center ++ color ++ radius

lib/js2e.ex

+143-15
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ defmodule JS2E do
1818
"""
1919

2020
require Logger
21-
alias JS2E.{Parser, Printer}
21+
import JS2E.Parser, only: [parse_schema_files: 1]
22+
import JS2E.Printer, only: [print_schemas: 2]
23+
alias JS2E.Parsers.{ParserWarning, ParserError}
24+
alias JS2E.Printers.PrinterError
2225

2326
@spec main([String.t]) :: :ok
2427
def main(args) do
25-
Logger.debug "Arguments: #{inspect args}"
2628

2729
{options, paths, errors} =
2830
OptionParser.parse(args, switches: [module_name: :string])
@@ -33,23 +35,23 @@ defmodule JS2E do
3335
end
3436

3537
if length(errors) > 0 do
36-
IO.puts "Error: Found one or more errors in the supplied options"
38+
print_error("Error: Found one or more errors in the supplied options")
3739
exit({:unknown_arguments, errors})
3840
end
3941

4042
files = resolve_all_paths(paths)
41-
Logger.debug "Files: #{inspect files}"
4243

4344
if length(files) == 0 do
44-
IO.puts "Error: Could not find any JSON files in path: #{inspect paths}"
45+
print_error("Error: Could not find any " <>
46+
"JSON files in path: #{inspect paths}")
4547
exit(:no_files)
4648
end
4749

4850
output_path = create_output_dir(options)
4951
JS2E.generate(files, output_path)
5052
end
5153

52-
@spec resolve_all_paths([String.t]) :: [String.t]
54+
@spec resolve_all_paths([String.t]) :: [Path.t]
5355
defp resolve_all_paths(paths) do
5456
paths
5557
|> Enum.filter(&File.exists?/1)
@@ -103,18 +105,144 @@ defmodule JS2E do
103105
end
104106

105107
@spec generate([String.t], String.t) :: :ok
106-
def generate(json_schema_paths, module_name) do
108+
def generate(schema_paths, module_name) do
107109

108-
schema_dict = Parser.parse_schema_files(json_schema_paths, module_name)
109-
printed_schemas = Printer.print_schemas(schema_dict)
110+
Logger.info "Parsing JSON schema files!"
111+
parser_result = parse_schema_files(schema_paths)
112+
pretty_parser_warnings(parser_result.warnings)
110113

111-
printed_schemas
112-
|> Enum.each(fn{file_path, file_content} ->
113-
{:ok, file} = File.open file_path, [:write]
114-
IO.binwrite file, file_content
115-
File.close file
116-
Logger.info "Created file: #{file_path}"
114+
if length(parser_result.errors) > 0 do
115+
pretty_parser_errors(parser_result.errors)
116+
117+
else
118+
Logger.info "Converting to Elm code!"
119+
printer_result = print_schemas(parser_result.schema_dict, module_name)
120+
121+
if length(printer_result.errors) > 0 do
122+
pretty_printer_errors(printer_result.errors)
123+
124+
else
125+
Logger.info "Printing Elm code to file(s)!"
126+
127+
file_dict = printer_result.file_dict
128+
Enum.each(file_dict, fn {file_path, file_content} ->
129+
{:ok, file} = File.open file_path, [:write]
130+
IO.binwrite file, file_content
131+
File.close file
132+
Logger.info "Created file '#{file_path}'"
133+
end)
134+
end
135+
end
136+
end
137+
138+
@spec pretty_parser_warnings([ParserWarning.t]) :: :ok
139+
defp pretty_parser_warnings(warnings) do
140+
warnings
141+
|> Enum.each(fn {file_path, warnings} ->
142+
if length(warnings) > 0 do
143+
144+
warning_header()
145+
146+
warnings
147+
|> Enum.group_by(fn warning -> warning.warning_type end)
148+
|> Enum.each(fn {warning_type, warnings} ->
149+
pretty_warning_type =
150+
warning_type
151+
|> to_string
152+
|> String.replace("_", " ")
153+
|> String.downcase
154+
155+
padding = String.duplicate("-",
156+
74 - String.length(pretty_warning_type) - String.length(file_path))
157+
158+
warnings
159+
|> Enum.each(fn warning ->
160+
print_header("--- #{pretty_warning_type} #{padding} #{file_path}\n")
161+
IO.puts warning.message
162+
end)
163+
end)
164+
165+
end
166+
end)
167+
:ok
168+
end
169+
170+
@spec pretty_parser_errors([ParserError.t]) :: :ok
171+
defp pretty_parser_errors(errors) do
172+
errors
173+
|> Enum.each(fn {file_path, errors} ->
174+
if length(errors) > 0 do
175+
176+
errors
177+
|> Enum.group_by(fn err -> err.error_type end)
178+
|> Enum.each(fn {error_type, errors} ->
179+
pretty_error_type =
180+
error_type
181+
|> to_string
182+
|> String.replace("_", " ")
183+
|> String.upcase
184+
185+
padding = String.duplicate("-",
186+
74 - String.length(pretty_error_type) - String.length(file_path))
187+
188+
errors
189+
|> Enum.each(fn error ->
190+
print_header("--- #{pretty_error_type} #{padding} #{file_path}\n")
191+
IO.puts error.message
192+
end)
193+
end)
194+
end
195+
end)
196+
197+
:ok
198+
end
199+
200+
@spec pretty_printer_errors([PrinterError.t]) :: :ok
201+
defp pretty_printer_errors(errors) do
202+
203+
errors
204+
|> Enum.each(fn {file_path, errors} ->
205+
if length(errors) > 0 do
206+
207+
errors
208+
|> Enum.group_by(fn err -> err.error_type end)
209+
|> Enum.each(fn {error_type, errors} ->
210+
211+
pretty_error_type =
212+
error_type
213+
|> to_string
214+
|> String.replace("_", " ")
215+
|> String.upcase
216+
217+
padding = String.duplicate("-",
218+
74 - String.length(pretty_error_type) - String.length(file_path))
219+
220+
errors
221+
|> Enum.each(fn error ->
222+
print_header("--- #{pretty_error_type} #{padding} #{file_path}\n")
223+
IO.puts error.message
224+
end)
225+
end)
226+
227+
end
117228
end)
229+
:ok
230+
end
231+
232+
defp print_error(str) do
233+
IO.puts IO.ANSI.format([:cyan, str])
234+
end
235+
236+
defp print_header(str) do
237+
IO.puts IO.ANSI.format([:cyan, str])
238+
end
239+
240+
defp warning_header do
241+
header = String.duplicate("^", 35) <>
242+
" WARNINGS " <>
243+
String.duplicate("^", 35)
244+
245+
IO.puts IO.ANSI.format([:yellow, header])
118246
end
119247

120248
end

0 commit comments

Comments
 (0)