Skip to content

Commit 4f129ea

Browse files
Fuzzers (#69)
Adds fuzz tests to the generated output Adds fuzz tests to the generated Elm output and now also generates a slightly more elaborate directory structure, see README, allowing the user to much easier test that the generated decoders and encoders behave as expected before merging them into their existing project. Reduces the noise when referencing qualified Elm types and functions across modules in the generated files by using `import Data.Foo as Foo` statements. Refactors code according to dialyzer and credo warnings.
1 parent f2c748c commit 4f129ea

File tree

81 files changed

+2818
-1526
lines changed

Some content is hidden

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

81 files changed

+2818
-1526
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# The packaged executable
22
js2e
3+
/js2e_output
34

45
# The directory Mix will write compiled artifacts to.
56
/_build

README.md

+191-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# JSON schema to Elm
22

3-
Generates Elm types, JSON decoders and JSON encoders from JSON schema
4-
specifications.
3+
Generates Elm types, JSON decoders, JSON encoders, and Fuzz tests from JSON
4+
schema specifications.
55

66
## Installation
77

@@ -26,14 +26,14 @@ Run `./js2e` for usage instructions.
2626
> have to pass it the enclosing directory of the relevant JSON schema files,
2727
> in order for it to be able to resolve the references correctly.
2828
29-
A proper description of which properties are mandatory are how the generator
29+
A proper description of which properties are mandatory and how the generator
3030
works is still in progress, but feel free to take a look at the `examples`
3131
folder which contains an example of a pair of JSON schemas and their
3232
corresponding Elm output. Likewise, representations of each of the different
3333
JSON schema types are described in the `lib/types` folder.
3434

35-
The tool aims to produce `elm-make`-like errors if something is missing,
36-
mispelled or cannot be resolved in the supplied JSON schema file(s), e.g.
35+
The tool aims to produce `elm-make`-like errors in case something is missing,
36+
misspelled or cannot be resolved in the supplied JSON schema file(s), e.g.
3737

3838
```
3939
--- UNKNOWN NODE TYPE -------------------------------------- all_of_example.json
@@ -137,21 +137,25 @@ type alias Point =
137137

138138
colorDecoder : String -> Decoder Color
139139
colorDecoder color =
140-
case color of
141-
"red" ->
142-
succeed Red
140+
Decode.string
141+
|> andThen
142+
(\color ->
143+
case color of
144+
"red" ->
145+
succeed Red
143146

144-
"yellow" ->
145-
succeed Yellow
147+
"yellow" ->
148+
succeed Yellow
146149

147-
"green" ->
148-
succeed Green
150+
"green" ->
151+
succeed Green
149152

150-
"blue" ->
151-
succeed Blue
153+
"blue" ->
154+
succeed Blue
152155

153-
_ ->
154-
fail <| "Unknown color type: " ++ color
156+
_ ->
157+
fail <| "Unknown color type: " ++ color
158+
)
155159

156160

157161
pointDecoder : Decoder Point
@@ -253,34 +257,34 @@ import Json.Encode as Encode
253257
, object
254258
, list
255259
)
256-
import Data.Definitions
260+
import Data.Definitions as Definitions
257261

258262

259263
type alias Circle =
260-
{ center : Data.Definitions.Point
261-
, color : Maybe Data.Definitions.Color
264+
{ center : Definitions.Point
265+
, color : Maybe Definitions.Color
262266
, radius : Float
263267
}
264268

265269

266270
circleDecoder : Decoder Circle
267271
circleDecoder =
268272
decode Circle
269-
|> required "center" Data.Definitions.pointDecoder
270-
|> optional "color" (Decode.string |> andThen Data.Definitions.colorDecoder |> maybe) Nothing
273+
|> required "center" Definitions.pointDecoder
274+
|> optional "color" (nullable Definitions.colorDecoder) Nothing
271275
|> required "radius" Decode.float
272276

273277

274278
encodeCircle : Circle -> Value
275279
encodeCircle circle =
276280
let
277281
center =
278-
[ ( "center", Data.Definitions.encodePoint circle.center ) ]
282+
[ ( "center", Definitions.encodePoint circle.center ) ]
279283

280284
color =
281285
case circle.color of
282286
Just color ->
283-
[ ( "color", Data.Definitions.encodeColor color ) ]
287+
[ ( "color", Definitions.encodeColor color ) ]
284288

285289
Nothing ->
286290
[]
@@ -294,6 +298,170 @@ encodeCircle circle =
294298
++ radius
295299
```
296300

301+
Furthermore, `js2e` also generates test files for the generated decoders and
302+
encoders, which fuzzes instances of a given Elm type and tests that encoding it
303+
as JSON and decoding it back into Elm returns the original instance of that
304+
generated Elm type. In the above case, the following test files,
305+
`tests/Data/CircleTests.elm` and `tests/Data/DefinitionsTests.elm`, are
306+
generated:
307+
308+
``` elm
309+
module Data.CircleTests exposing (..)
310+
311+
-- Tests: Schema for a circle shape
312+
313+
import Expect exposing (Expectation)
314+
import Fuzz exposing (Fuzzer)
315+
import Test exposing (..)
316+
import Json.Decode as Decode
317+
import Data.Circle exposing (..)
318+
import Data.DefinitionsTests as Definitions
319+
320+
321+
circleFuzzer : Fuzzer Circle
322+
circleFuzzer =
323+
Fuzz.map3
324+
Circle
325+
Definitions.pointFuzzer
326+
(Fuzz.maybe Definitions.colorFuzzer)
327+
Fuzz.float
328+
329+
330+
encodeDecodeCircleTest : Test
331+
encodeDecodeCircleTest =
332+
fuzz circleFuzzer "can encode and decode Circle object" <|
333+
\circle ->
334+
circle
335+
|> encodeCircle
336+
|> Decode.decodeValue circleDecoder
337+
|> Expect.equal (Ok circle)
338+
```
339+
and
340+
341+
``` elm
342+
module Data.DefinitionsTests exposing (..)
343+
344+
-- Tests: Schema for common types
345+
346+
import Expect exposing (Expectation)
347+
import Fuzz exposing (Fuzzer)
348+
import Test exposing (..)
349+
import Json.Decode as Decode
350+
import Data.Definitions exposing (..)
351+
352+
353+
colorFuzzer : Fuzzer Color
354+
colorFuzzer =
355+
Fuzz.oneOf
356+
[ Fuzz.constant Red
357+
, Fuzz.constant Yellow
358+
, Fuzz.constant Green
359+
, Fuzz.constant Blue
360+
]
361+
362+
363+
encodeDecodeColorTest : Test
364+
encodeDecodeColorTest =
365+
fuzz colorFuzzer "can encode and decode Color object" <|
366+
\color ->
367+
color
368+
|> encodeColor
369+
|> Decode.decodeValue colorDecoder
370+
|> Expect.equal (Ok color)
371+
372+
373+
pointFuzzer : Fuzzer Point
374+
pointFuzzer =
375+
Fuzz.map2
376+
Point
377+
Fuzz.float
378+
Fuzz.float
379+
380+
381+
encodeDecodePointTest : Test
382+
encodeDecodePointTest =
383+
fuzz pointFuzzer "can encode and decode Point object" <|
384+
\point ->
385+
point
386+
|> encodePoint
387+
|> Decode.decodeValue pointDecoder
388+
|> Expect.equal (Ok point)
389+
390+
```
391+
392+
Finally, `js2e` also generates package config files, `package.json` and
393+
`elm-package.json` making it easy to test that the generated Elm code is
394+
behaving as expected. Thus, if we supply the following directory structure to
395+
`js2e` in the above case:
396+
397+
```
398+
.
399+
└── js2e_input/
400+
├── definitions.json
401+
└── circle.json
402+
```
403+
404+
the following new directory structure is generated:
405+
406+
```
407+
.
408+
└── js2e_output/
409+
├── package.json
410+
├── elm-package.json
411+
├── Data/
412+
│ ├── Circle.elm
413+
│ └── Definitions.elm
414+
└── tests/
415+
├── elm-package.json
416+
└── Data/
417+
├── CircleTests.elm
418+
└── DefinitionsTests.elm
419+
```
420+
421+
containing the files described above along with the needed package config files
422+
to compile and run the tests.
423+
424+
## Error reporting
425+
426+
Any errors encountered by the `js2e` tool while parsing the JSON schema files or
427+
printing the Elm code output, is reported in an Elm-like style, e.g.
428+
429+
```
430+
--- UNKNOWN NODE TYPE -------------------------------------- all_of_example.json
431+
432+
The value of "type" at '#/allOf/0/properties/description' did not match a known node type
433+
434+
"type": "strink"
435+
^^^^^^^^
436+
437+
Was expecting one of the following types
438+
439+
["null", "boolean", "object", "array", "number", "integer", "string"]
440+
441+
Hint: See the specification section 6.25. "Validation keywords - type"
442+
<http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.25>
443+
```
444+
445+
or
446+
447+
```
448+
--- UNRESOLVED REFERENCE ----------------------------------- all_of_example.json
449+
450+
451+
The following reference at `#/allOf/0/color` could not be resolved
452+
453+
"$ref": #/definitions/kolor
454+
^^^^^^^^^^^^^^^^^^^
455+
456+
457+
Hint: See the specification section 9. "Base URI and dereferencing"
458+
<http://json-schema.org/latest/json-schema-core.html#rfc.section.9>
459+
```
460+
461+
If you encounter an error while using `js2e` that does not mimic the above
462+
Elm-like style, but instead looks like an Elixir stacktrace, please report this
463+
as a bug by opening an issue and includin a JSON schema example that recreates
464+
the error.
297465

298466
## Contributing
299467

config/config.exs

+1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ use Mix.Config
33
config :elixir, ansi_enabled: true
44

55
config :js2e, templates_location: "./priv/templates/"
6+
config :js2e, output_location: "js2e_output"
67

78
import_config "#{Mix.env()}.exs"

0 commit comments

Comments
 (0)