├── .gitignore
├── .gitmodules
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── elm.json
├── generate-tests.js
├── index.html
├── package.json
├── src
├── Json
│ ├── Schema.elm
│ ├── Schema
│ │ ├── Builder.elm
│ │ ├── Definitions.elm
│ │ ├── Examples.elm
│ │ ├── Helpers.elm
│ │ ├── Random.elm
│ │ └── Validation.elm
│ └── Schemata.elm
├── Ref.elm
└── Util.elm
├── tests
├── .gitignore
├── Decoding.elm
├── Draft4.elm
├── Draft6.elm
├── Generator.elm
├── Type.elm.rm
├── Validations.elm
└── elm-package.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | repl-temp-*
3 | elm-stuff
4 | dist
5 | .DS_Store
6 | npm-debug.log
7 | deps
8 | documentation.json
9 | elm.js
10 | docs.json
11 | JSON-Schema-Test-Suite/
12 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/json-tools/json-schema/6dd54e2d077400642950ce06c637c7485b0490f3/.gitmodules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 | node_js: node
5 |
6 | cache:
7 | directories:
8 | - elm-stuff/build-artifacts
9 | - elm-stuff/packages
10 | - sysconfcpus
11 | os:
12 | - linux
13 |
14 | env: ELM_VERSION=0.18.0
15 |
16 | before_install:
17 | - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
18 |
19 | install:
20 | - node --version
21 | - npm --version
22 | - npm install -g elm@$ELM_VERSION elm-test
23 | - git clone https://github.com/NoRedInk/elm-ops-tooling
24 | - elm-ops-tooling/with_retry.rb elm package install --yes
25 | # Faster compile on Travis.
26 | - |
27 | if [ ! -d sysconfcpus/bin ];
28 | then
29 | git clone https://github.com/obmarg/libsysconfcpus.git;
30 | cd libsysconfcpus;
31 | ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus;
32 | make && make install;
33 | cd ..;
34 | fi
35 |
36 | # before_script:
37 | # - cd tests && $TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make --yes Tests.elm && cd ..
38 |
39 | script:
40 | - $TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-test
41 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 4.1.0
2 |
3 | Added support of custom keywords:
4 | - Json.Schema.Builder.withCustomKeyword
5 | - Json.Schema.Definitions.getCustomKeywordValue
6 |
7 | ## 4.0.0
8 |
9 | - support full spec of draft-04 and draft-06
10 | - functionality validated by [official test suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
11 | - dozen of fixes in validation
12 | - new type `ExclusiveBoundary(BoolBoundary, NumberBoundary)` (compatibility layer between drafts 4 and 6)
13 |
14 | ## 3.0.0
15 |
16 | - changed validation errors (added more info to facilitate error messages building)
17 |
18 | ## 2.0.0
19 |
20 | Multiple errors with details returned by validation:
21 | - added `Json.Schema.Validation`
22 | - changed validation output format from `Result String Bool` to `Result (List Json.Schema.Validation.Error) Value`
23 |
24 | ## 1.1.0
25 |
26 | Added `Json.Schema.Random` API to generate random values
27 |
28 |
29 | ## 1.0.0
30 |
31 | Initial capabilities: decode/encode, validate with a single string error
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, Anatolii Chakkaev
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSON Schema decoder and validator for elm
2 |
3 | [](https://travis-ci.org/json-tools/json-schema)
4 |
5 | > JSON Schema is a vocabulary that allows you to annotate and validate JSON documents. (http://json-schema.org/)
6 |
7 | This code is experimental, it doesn't cover json schema spec in full (yet), just allows to parse minimal subset of it in order to implement very basic proof of concept of "type as value" in elm.
8 |
9 | The end goal of this project is to cover json schema draft 6 spec in full, if you're interested - feel free to pick up some of the open issues and submit PR.
10 |
11 | ## When to use this library?
12 |
13 | ### ✍ form generation
14 |
15 | Sometimes it is not possible by design to come up with some static type definition, for example if you are building REST API test console, where each endpoint requires its own data. The simplest solution would be to allow user to enter data as json string, decode it into value to ensure that json is valid and send to a server as value, without looking inside this value to make sure it makes sense. But what if we want to enter data using form, perform some basic validation before sending it to a server? Then we have a valid use case for this library.
16 |
17 | ### ☝ documentation generation
18 |
19 | JSON Schema allows you to specify some meta data like title, description, examples, definitions and also some validation keywords like type, format, enum, and all sub-schemas (e.g. properties, items) which is a useful source of information for content generation if you want to document data structures.
20 |
21 | ### ✌ validation
22 |
23 | Instead of writing validation code as part of your frontend app you could describe it in a declarative way as JSON schema, so that you can focus on what your are validating rather than how. Combined with form generation this is a great time-saver while building interfaces.
24 |
25 | ## Current status of this project
26 |
27 | - [x] decode draft 6 of json-schema
28 | - [x] validate all the things
29 | - [x] schema builder api
30 | - [x] documentation
31 | - [x] random value generator
32 | - [x] demo: json editor
33 | - [x] multiple errors
34 | - [x] support draft-04 and draft-06 of JSON Schema
35 | - [ ] full `$ref` support
36 | - [ ] i18n
37 | - [ ] demo: docs generator
38 | - [ ] demo: schema builder
39 |
--------------------------------------------------------------------------------
/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "package",
3 | "name": "json-tools/json-schema",
4 | "summary": "JSON Schema for elm",
5 | "license": "BSD-3-Clause",
6 | "version": "1.0.2",
7 | "exposed-modules": [
8 | "Json.Schema",
9 | "Json.Schema.Random",
10 | "Json.Schema.Builder",
11 | "Json.Schema.Validation",
12 | "Json.Schema.Definitions"
13 | ],
14 | "elm-version": "0.19.0 <= v < 0.20.0",
15 | "dependencies": {
16 | "NoRedInk/elm-json-decode-pipeline": "1.0.0 <= v < 2.0.0",
17 | "elm/core": "1.0.0 <= v < 2.0.0",
18 | "elm/json": "1.0.0 <= v < 2.0.0",
19 | "elm/random": "1.0.0 <= v < 2.0.0",
20 | "elm/regex": "1.0.0 <= v < 2.0.0",
21 | "zwilias/elm-utf-tools": "2.0.1 <= v < 3.0.0"
22 | },
23 | "test-dependencies": {}
24 | }
--------------------------------------------------------------------------------
/generate-tests.js:
--------------------------------------------------------------------------------
1 | const { readdirSync, readFileSync } = require('fs');
2 | const { join } = require('path');
3 |
4 | const namespace = process.argv[2];
5 |
6 | if (namespace !== 'draft-4' && namespace !== 'draft-6') {
7 | console.error('Pleace specify namespace as a param. Avalable options: draft-4, draft-6.');
8 | console.error('Usage example: node generate-tests.js draft-6');
9 | process.exit(1);
10 | }
11 |
12 | const dirname = namespace.replace('-', '');
13 | const moduleName = dirname.substr(0, 1).toUpperCase() + dirname.substr(1);
14 |
15 | const tests = load('./JSON-Schema-Test-Suite/tests/' + dirname);
16 | console.log(header(moduleName) + body(namespace, tests) + footer());
17 |
18 | function load(path) {
19 | return readdirSync(path)
20 | .filter(x => x.endsWith('.json'))
21 | .filter(x => x !== 'refRemote.json')
22 | .map(filename => {
23 | return { filename, suite: JSON.parse(readFileSync(join(path, filename))) };
24 | });
25 | }
26 |
27 | function header(moduleName) {
28 | return `module ${moduleName} exposing (all)
29 |
30 | import Json.Encode as Encode exposing (Value)
31 | import Json.Decode as Decode exposing (decodeString, value)
32 | import Json.Schema.Definitions exposing (blankSchema, decoder)
33 | import Json.Schema exposing (validateValue)
34 | import Test exposing (Test, describe, test, only)
35 | import Expect
36 |
37 |
38 | all : Test
39 | all =
40 | `;
41 |
42 | }
43 |
44 | function footer() {
45 | return `
46 |
47 |
48 | examine : String -> String -> Bool -> Expect.Expectation
49 | examine schemaSource dataSource outcome =
50 | let
51 | schema =
52 | schemaSource
53 | |> decodeString decoder
54 | |> Result.withDefault blankSchema
55 |
56 | data =
57 | dataSource
58 | |> decodeString value
59 | |> Result.withDefault Encode.null
60 |
61 | result =
62 | validateValue data schema
63 | |> Result.mapError toString
64 | |> Result.map (\\_ -> True)
65 | in
66 | if outcome then
67 | result
68 | |> Expect.equal (Ok True)
69 | else
70 | case result of
71 | Ok x ->
72 | Expect.fail "Unexpected success"
73 |
74 | Err _ ->
75 | Expect.pass`;
76 | }
77 |
78 | function body(name, tests) {
79 | return ` describe "${name}"
80 | [ ` +
81 | tests.map(({filename, suite}) => {
82 | return `describe "${filename}"
83 | [ ${printSuite(suite).join('\n , ')}
84 | ]`
85 | }).join('\n , ')
86 | + '\n ]';
87 | }
88 |
89 | function printSuite(cases) {
90 | return cases.map(({description, schema, tests}) => {
91 | return `describe "suite: ${description}"
92 | [ ${printCases(schema, tests).join('\n , ')}
93 | ]`
94 | ;
95 | });
96 | }
97 |
98 |
99 | function printCases(schema, collection) {
100 | return collection.map(({description, data, valid}) => {
101 | return `test "${description}" <|
102 | \\() ->
103 | examine
104 | """
105 | ${JSON.stringify(schema, null, ' ').replace(/\n/g, '\n ')}
106 | """
107 | """
108 | ${JSON.stringify(data, null, ' ').replace(/\n/g, '\n ')}
109 | """
110 | ${valid ? 'True' : 'False'}`
111 | });
112 | }
113 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | JSON Schema Builder
5 |
6 |
7 |
8 |
9 |
10 |
19 |
20 |
21 |
22 |
23 |
161 |
162 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "json-schema",
3 | "private": false,
4 | "version": "3.0.0",
5 | "description": "",
6 | "author": "",
7 | "license": "ISC",
8 | "scripts": {
9 | "start": "elm-live src/Main.elm --output=elm.js --open",
10 | "test": "elm-test --watch",
11 | "deploy": "gh-pages -d dist -a",
12 | "generate-tests": "node ./generate-tests.js draft-6 > tests/Draft6.elm && node ./generate-tests.js draft-4 > tests/Draft4.elm"
13 | },
14 | "devDependencies": {
15 | "elm": "^0.18.0",
16 | "elm-live": "^2.7.4",
17 | "elm-test": "^0.18.7"
18 | },
19 | "dependencies": {
20 | "gh-pages": "^1.0.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Json/Schema.elm:
--------------------------------------------------------------------------------
1 | module Json.Schema exposing
2 | ( fromValue, fromString
3 | , validateValue, validateAt
4 | )
5 |
6 | {-| This library provides bunch of utility methods to work with JSON values using
7 | schemas defined in [JSON Schema](http://json-schema.org/) format.
8 |
9 | Currently it allows to construct schemata ([draft-6](https://github.com/json-schema-org/json-schema-spec/blob/draft-06/schema.json)), validate values and generate random
10 | values based on schema (very experimental feature).
11 | It supports local references, but doesn't support remote references.
12 |
13 |
14 | # Decode schema
15 |
16 | Use `fromValue` or `fromString` methods if you receive schema from external source. If you want to construct schema from elm code you might want to use `Json.Schema.Builder`, or low-level API using definitions from `Json.Schema.Definitions`
17 |
18 | @docs fromValue, fromString
19 |
20 |
21 | # Validation
22 |
23 | @docs validateValue, validateAt
24 |
25 | -}
26 |
27 | import Json.Decode exposing (Value, decodeString, decodeValue)
28 | import Json.Schema.Definitions exposing (Schema, decoder)
29 | import Json.Schema.Helpers exposing (collectIds)
30 | import Json.Schema.Validation exposing (Error, JsonPointer, ValidationError(..), ValidationOptions, validate)
31 | import Json.Schemata
32 | import Ref exposing (SchemataPool, defaultPool)
33 |
34 |
35 | {-| Validate value against JSON Schema. Returns Result with updated value in case if validationOptions require so.
36 |
37 | schema
38 | |> Json.Schema.validateValue { applyDefaults = True } value
39 |
40 | -}
41 | validateValue : ValidationOptions -> Value -> Schema -> Result (List Error) Value
42 | validateValue validationOptions value schema =
43 | let
44 | ( pool, _ ) =
45 | collectIds schema defaultPool
46 | in
47 | validate validationOptions pool value schema schema
48 |
49 |
50 | {-| Validate value using subschema identified by URI.
51 | -}
52 | validateAt : ValidationOptions -> Value -> Schema -> String -> Result (List Error) Value
53 | validateAt validationOptions value schema uri =
54 | let
55 | ( pool, _ ) =
56 | collectIds schema defaultPool
57 | in
58 | case Ref.resolveReference "" pool schema uri of
59 | Just ( ns, resolvedSchema ) ->
60 | validate validationOptions pool value schema resolvedSchema
61 |
62 | Nothing ->
63 | Err [ Error (JsonPointer "" []) <| UnresolvableReference uri ]
64 |
65 |
66 | {-| Construct JSON Schema from JSON value
67 | -}
68 | fromValue : Value -> Result String Schema
69 | fromValue =
70 | decodeValue decoder
71 | >> Result.mapError Json.Decode.errorToString
72 |
73 |
74 | {-| Construct JSON Schema from string
75 | -}
76 | fromString : String -> Result String Schema
77 | fromString =
78 | decodeString decoder
79 | >> Result.mapError Json.Decode.errorToString
80 |
--------------------------------------------------------------------------------
/src/Json/Schema/Builder.elm:
--------------------------------------------------------------------------------
1 | module Json.Schema.Builder exposing
2 | ( SchemaBuilder(..)
3 | , buildSchema, boolSchema, toSchema, encode
4 | , withType, withNullableType, withUnionType
5 | , withTitle, withDescription, withDefault, withExamples, withDefinitions
6 | , withId, withRef, withCustomKeyword
7 | , withMultipleOf, withMaximum, withMinimum, withExclusiveMaximum, withExclusiveMinimum
8 | , withMaxLength, withMinLength, withPattern, withFormat
9 | , withItems, withItem, withAdditionalItems, withMaxItems, withMinItems, withUniqueItems, withContains
10 | , withMaxProperties, withMinProperties, withRequired, withProperties, withPatternProperties, withAdditionalProperties, withSchemaDependency, withPropNamesDependency, withPropertyNames
11 | , withEnum, withConst, withAllOf, withAnyOf, withOneOf, withNot
12 | , validate
13 | -- type
14 | -- object
15 | -- encode
16 | -- numeric
17 | -- string
18 | -- array
19 | -- custom keyword
20 | -- schema
21 | -- generic
22 | -- meta
23 | )
24 |
25 | {-| Convenience API to build a valid JSON schema
26 |
27 |
28 | # Definition
29 |
30 | @docs SchemaBuilder
31 |
32 |
33 | # Schema builder creation
34 |
35 | @docs buildSchema, boolSchema, toSchema, encode
36 |
37 |
38 | # Building up schema
39 |
40 |
41 | ## Type
42 |
43 | JSON Schema spec allows type to be string or array of strings. There are three
44 | groups of types produced: single types (e.g. `"string"`), nullable types (e.g. `["string", "null"]`)
45 | and union types (e.g. `["string", "object"]`)
46 |
47 | @docs withType, withNullableType, withUnionType
48 |
49 |
50 | ## Meta
51 |
52 | @docs withTitle, withDescription, withDefault, withExamples, withDefinitions
53 |
54 |
55 | ## JSON-Schema
56 |
57 | @docs withId, withRef, withCustomKeyword
58 |
59 |
60 | ## Numeric validations
61 |
62 | The following validations are only applicable to numeric values and
63 | will always succeed for any type other than `number` and `integer`
64 |
65 | @docs withMultipleOf, withMaximum, withMinimum, withExclusiveMaximum, withExclusiveMinimum
66 |
67 |
68 | ## String validations
69 |
70 | @docs withMaxLength, withMinLength, withPattern, withFormat
71 |
72 |
73 | ## Array validations
74 |
75 | @docs withItems, withItem, withAdditionalItems, withMaxItems, withMinItems, withUniqueItems, withContains
76 |
77 |
78 | ## Object validations
79 |
80 | @docs withMaxProperties, withMinProperties, withRequired, withProperties, withPatternProperties, withAdditionalProperties, withSchemaDependency, withPropNamesDependency, withPropertyNames
81 |
82 |
83 | ## Generic validations
84 |
85 | @docs withEnum, withConst, withAllOf, withAnyOf, withOneOf, withNot
86 |
87 |
88 | # Validation
89 |
90 | @docs validate
91 |
92 | -}
93 |
94 | import Json.Decode as Decode exposing (Value)
95 | import Json.Encode as Encode
96 | import Json.Schema.Definitions
97 | exposing
98 | ( Dependency(..)
99 | , ExclusiveBoundary(..)
100 | , Items(..)
101 | , Schema(..)
102 | , Schemata(..)
103 | , SingleType(..)
104 | , SubSchema
105 | , Type(..)
106 | , blankSubSchema
107 | , stringToType
108 | )
109 | import Json.Schema.Validation as Validation exposing (Error)
110 | import Ref
111 | import Util exposing (foldResults)
112 |
113 |
114 | {-| Builder for JSON schema providing an API like this:
115 |
116 | buildSchema
117 | |> withTitle "My object"
118 | |> withProperties
119 | [ ( "foo"
120 | , buildSchema
121 | |> withType "string"
122 | )
123 | , ( "bar"
124 | , buildSchema
125 | |> withType "integer"
126 | |> withMaximum 10
127 | )
128 | ]
129 |
130 | -}
131 | type SchemaBuilder
132 | = SchemaBuilder { errors : List String, schema : Maybe SubSchema, bool : Maybe Bool }
133 |
134 |
135 |
136 | -- BASIC API
137 |
138 |
139 | {-| Create schema builder with blank schema
140 | -}
141 | buildSchema : SchemaBuilder
142 | buildSchema =
143 | SchemaBuilder { errors = [], schema = Just blankSubSchema, bool = Nothing }
144 |
145 |
146 | {-| Create boolean schema
147 | -}
148 | boolSchema : Bool -> SchemaBuilder
149 | boolSchema b =
150 | SchemaBuilder { errors = [], schema = Nothing, bool = Just b }
151 |
152 |
153 | {-| Extract JSON Schema from the builder
154 | -}
155 | toSchema : SchemaBuilder -> Result String Schema
156 | toSchema (SchemaBuilder sb) =
157 | if List.isEmpty sb.errors then
158 | case sb.bool of
159 | Just x ->
160 | Ok <| BooleanSchema x
161 |
162 | Nothing ->
163 | case sb.schema of
164 | Just ss ->
165 | Ok <| ObjectSchema { ss | source = Json.Schema.Definitions.encode (ObjectSchema ss) }
166 |
167 | Nothing ->
168 | Ok <| ObjectSchema blankSubSchema
169 |
170 | else
171 | Err <| String.join ", " sb.errors
172 |
173 |
174 | {-| Validate value using schema controlled by builder.
175 | -}
176 | validate : Validation.ValidationOptions -> Value -> SchemaBuilder -> Result (List Error) Value
177 | validate validationOptions val sb =
178 | case toSchema sb of
179 | Ok schema ->
180 | Validation.validate validationOptions Ref.defaultPool val schema schema
181 |
182 | Err s ->
183 | Ok val
184 |
185 |
186 |
187 | --Err <| "Schema is invalid: " ++ s
188 | -- TYPE
189 |
190 |
191 | {-| Set the `type` property of JSON schema to a specific type, accepts strings
192 |
193 | buildSchema
194 | |> withType "boolean"
195 |
196 | -}
197 | withType : String -> SchemaBuilder -> SchemaBuilder
198 | withType t sb =
199 | t
200 | |> stringToType
201 | |> Result.map (\x -> updateSchema (\s -> { s | type_ = SingleType x }) sb)
202 | |> (\r ->
203 | case r of
204 | Ok x ->
205 | x
206 |
207 | Err s ->
208 | appendError s sb
209 | )
210 |
211 |
212 | {-| Set the `type` property of JSON schema to a nullable type.
213 |
214 | buildSchema
215 | |> withNullableType "string"
216 |
217 | -}
218 | withNullableType : String -> SchemaBuilder -> SchemaBuilder
219 | withNullableType t =
220 | case stringToType t of
221 | Ok NullType ->
222 | appendError "Nullable null is not allowed"
223 |
224 | Ok r ->
225 | updateSchema (\s -> { s | type_ = NullableType r })
226 |
227 | Err s ->
228 | appendError s
229 |
230 |
231 | {-| Set the `type` property of JSON schema to an union type.
232 |
233 | buildSchema
234 | |> withUnionType [ "string", "object" ]
235 |
236 | -}
237 | withUnionType : List String -> SchemaBuilder -> SchemaBuilder
238 | withUnionType listTypes sb =
239 | listTypes
240 | |> List.sort
241 | |> List.map stringToType
242 | |> foldResults
243 | |> Result.map (\s -> updateSchema (\x -> { x | type_ = UnionType s }) sb)
244 | |> (\x ->
245 | case x of
246 | Err s ->
247 | appendError s sb
248 |
249 | Ok xLocal ->
250 | xLocal
251 | )
252 |
253 |
254 | {-| Set the `contains` property of JSON schema to a sub-schema.
255 |
256 | buildSchema
257 | |> withContains
258 | (buildSchema
259 | |> withType "string"
260 | )
261 |
262 | -}
263 | withContains : SchemaBuilder -> SchemaBuilder -> SchemaBuilder
264 | withContains =
265 | updateWithSubSchema (\sub s -> { s | contains = sub })
266 |
267 |
268 | {-| -}
269 | withNot : SchemaBuilder -> SchemaBuilder -> SchemaBuilder
270 | withNot =
271 | updateWithSubSchema (\sub s -> { s | not = sub })
272 |
273 |
274 | {-| -}
275 | withDefinitions : List ( String, SchemaBuilder ) -> SchemaBuilder -> SchemaBuilder
276 | withDefinitions =
277 | updateWithSchemata (\definitions s -> { s | definitions = definitions })
278 |
279 |
280 | {-| -}
281 | withItems : List SchemaBuilder -> SchemaBuilder -> SchemaBuilder
282 | withItems listSchemas =
283 | case listSchemas |> toListOfSchemas of
284 | Ok items ->
285 | updateSchema (\s -> { s | items = ArrayOfItems items })
286 |
287 | Err s ->
288 | appendError s
289 |
290 |
291 | {-| -}
292 | withItem : SchemaBuilder -> SchemaBuilder -> SchemaBuilder
293 | withItem item =
294 | case item |> toSchema of
295 | Ok itemSchema ->
296 | updateSchema (\s -> { s | items = ItemDefinition itemSchema })
297 |
298 | Err s ->
299 | appendError s
300 |
301 |
302 | {-| -}
303 | withAdditionalItems : SchemaBuilder -> SchemaBuilder -> SchemaBuilder
304 | withAdditionalItems =
305 | updateWithSubSchema (\sub s -> { s | additionalItems = sub })
306 |
307 |
308 | {-| -}
309 | withProperties : List ( String, SchemaBuilder ) -> SchemaBuilder -> SchemaBuilder
310 | withProperties =
311 | updateWithSchemata (\properties s -> { s | properties = properties })
312 |
313 |
314 | {-| -}
315 | withPatternProperties : List ( String, SchemaBuilder ) -> SchemaBuilder -> SchemaBuilder
316 | withPatternProperties =
317 | updateWithSchemata (\patternProperties s -> { s | patternProperties = patternProperties })
318 |
319 |
320 | {-| -}
321 | withAdditionalProperties : SchemaBuilder -> SchemaBuilder -> SchemaBuilder
322 | withAdditionalProperties =
323 | updateWithSubSchema (\sub s -> { s | additionalProperties = sub })
324 |
325 |
326 | {-| -}
327 | withSchemaDependency : String -> SchemaBuilder -> SchemaBuilder -> SchemaBuilder
328 | withSchemaDependency name sd =
329 | case sd |> toSchema of
330 | Ok depSchema ->
331 | updateSchema (\s -> { s | dependencies = s.dependencies ++ [ ( name, PropSchema depSchema ) ] })
332 |
333 | Err s ->
334 | appendError s
335 |
336 |
337 | {-| -}
338 | withPropNamesDependency : String -> List String -> SchemaBuilder -> SchemaBuilder
339 | withPropNamesDependency name pn =
340 | updateSchema (\schema -> { schema | dependencies = ( name, ArrayPropNames pn ) :: schema.dependencies })
341 |
342 |
343 | {-| -}
344 | withPropertyNames : SchemaBuilder -> SchemaBuilder -> SchemaBuilder
345 | withPropertyNames =
346 | updateWithSubSchema (\propertyNames s -> { s | propertyNames = propertyNames })
347 |
348 |
349 | {-| -}
350 | withAllOf : List SchemaBuilder -> SchemaBuilder -> SchemaBuilder
351 | withAllOf =
352 | updateWithListOfSchemas (\allOf s -> { s | allOf = allOf })
353 |
354 |
355 | {-| -}
356 | withAnyOf : List SchemaBuilder -> SchemaBuilder -> SchemaBuilder
357 | withAnyOf =
358 | updateWithListOfSchemas (\anyOf s -> { s | anyOf = anyOf })
359 |
360 |
361 | {-| -}
362 | withOneOf : List SchemaBuilder -> SchemaBuilder -> SchemaBuilder
363 | withOneOf =
364 | updateWithListOfSchemas (\oneOf s -> { s | oneOf = oneOf })
365 |
366 |
367 | {-| -}
368 | withTitle : String -> SchemaBuilder -> SchemaBuilder
369 | withTitle x =
370 | updateSchema (\s -> { s | title = Just x })
371 |
372 |
373 | {-| -}
374 | withDescription : String -> SchemaBuilder -> SchemaBuilder
375 | withDescription x =
376 | updateSchema (\s -> { s | description = Just x })
377 |
378 |
379 | {-| -}
380 | withMultipleOf : Float -> SchemaBuilder -> SchemaBuilder
381 | withMultipleOf x =
382 | updateSchema (\s -> { s | multipleOf = Just x })
383 |
384 |
385 | {-| -}
386 | withMaximum : Float -> SchemaBuilder -> SchemaBuilder
387 | withMaximum x =
388 | updateSchema (\s -> { s | maximum = Just x })
389 |
390 |
391 | {-| -}
392 | withMinimum : Float -> SchemaBuilder -> SchemaBuilder
393 | withMinimum x =
394 | updateSchema (\s -> { s | minimum = Just x })
395 |
396 |
397 | {-| -}
398 | withExclusiveMaximum : Float -> SchemaBuilder -> SchemaBuilder
399 | withExclusiveMaximum x =
400 | updateSchema (\s -> { s | exclusiveMaximum = Just (NumberBoundary x) })
401 |
402 |
403 | {-| -}
404 | withExclusiveMinimum : Float -> SchemaBuilder -> SchemaBuilder
405 | withExclusiveMinimum x =
406 | updateSchema (\s -> { s | exclusiveMinimum = Just (NumberBoundary x) })
407 |
408 |
409 | {-| -}
410 | withMaxLength : Int -> SchemaBuilder -> SchemaBuilder
411 | withMaxLength x =
412 | updateSchema (\s -> { s | maxLength = Just x })
413 |
414 |
415 | {-| -}
416 | withMinLength : Int -> SchemaBuilder -> SchemaBuilder
417 | withMinLength x =
418 | updateSchema (\s -> { s | minLength = Just x })
419 |
420 |
421 | {-| -}
422 | withMaxProperties : Int -> SchemaBuilder -> SchemaBuilder
423 | withMaxProperties n =
424 | updateSchema (\s -> { s | maxProperties = Just n })
425 |
426 |
427 | {-| -}
428 | withMinProperties : Int -> SchemaBuilder -> SchemaBuilder
429 | withMinProperties n =
430 | updateSchema (\s -> { s | minProperties = Just n })
431 |
432 |
433 | {-| -}
434 | withMaxItems : Int -> SchemaBuilder -> SchemaBuilder
435 | withMaxItems n =
436 | updateSchema (\s -> { s | maxItems = Just n })
437 |
438 |
439 | {-| -}
440 | withMinItems : Int -> SchemaBuilder -> SchemaBuilder
441 | withMinItems n =
442 | updateSchema (\s -> { s | minItems = Just n })
443 |
444 |
445 | {-| -}
446 | withUniqueItems : Bool -> SchemaBuilder -> SchemaBuilder
447 | withUniqueItems b =
448 | updateSchema (\s -> { s | uniqueItems = Just b })
449 |
450 |
451 | {-| -}
452 | withPattern : String -> SchemaBuilder -> SchemaBuilder
453 | withPattern x =
454 | updateSchema (\s -> { s | pattern = Just x })
455 |
456 |
457 | {-| -}
458 | withFormat : String -> SchemaBuilder -> SchemaBuilder
459 | withFormat x =
460 | updateSchema (\s -> { s | format = Just x })
461 |
462 |
463 | {-| -}
464 | withEnum : List Value -> SchemaBuilder -> SchemaBuilder
465 | withEnum x =
466 | updateSchema (\s -> { s | enum = Just x })
467 |
468 |
469 | {-| -}
470 | withRequired : List String -> SchemaBuilder -> SchemaBuilder
471 | withRequired x =
472 | updateSchema (\s -> { s | required = Just x })
473 |
474 |
475 | {-| -}
476 | withConst : Value -> SchemaBuilder -> SchemaBuilder
477 | withConst v =
478 | updateSchema (\s -> { s | const = Just v })
479 |
480 |
481 | {-| -}
482 | withRef : String -> SchemaBuilder -> SchemaBuilder
483 | withRef x =
484 | updateSchema (\s -> { s | ref = Just x })
485 |
486 |
487 | {-| -}
488 | withExamples : List Value -> SchemaBuilder -> SchemaBuilder
489 | withExamples x =
490 | updateSchema (\s -> { s | examples = Just x })
491 |
492 |
493 | {-| -}
494 | withDefault : Value -> SchemaBuilder -> SchemaBuilder
495 | withDefault x =
496 | updateSchema (\s -> { s | default = Just x })
497 |
498 |
499 | {-| -}
500 | withId : String -> SchemaBuilder -> SchemaBuilder
501 | withId x =
502 | updateSchema (\s -> { s | id = Just x })
503 |
504 |
505 | {-| -}
506 | withCustomKeyword : String -> Value -> SchemaBuilder -> SchemaBuilder
507 | withCustomKeyword key val =
508 | updateSchema
509 | (\s ->
510 | { s
511 | | source =
512 | s.source
513 | |> Decode.decodeValue (Decode.keyValuePairs Decode.value)
514 | |> Result.withDefault []
515 | |> (::) ( key, val )
516 | |> Encode.object
517 | }
518 | )
519 |
520 |
521 |
522 | -- HELPERS
523 |
524 |
525 | updateSchema : (SubSchema -> SubSchema) -> SchemaBuilder -> SchemaBuilder
526 | updateSchema fn (SchemaBuilder sb) =
527 | case sb.schema of
528 | Just ss ->
529 | SchemaBuilder { sb | schema = Just <| fn ss }
530 |
531 | Nothing ->
532 | SchemaBuilder sb
533 |
534 |
535 | appendError : String -> SchemaBuilder -> SchemaBuilder
536 | appendError e (SchemaBuilder { errors, schema, bool }) =
537 | SchemaBuilder { errors = e :: errors, schema = schema, bool = bool }
538 |
539 |
540 | type alias SchemataBuilder =
541 | List ( String, SchemaBuilder )
542 |
543 |
544 | toSchemata : SchemataBuilder -> Result String (List ( String, Schema ))
545 | toSchemata =
546 | List.foldl
547 | (\( key, builder ) ->
548 | Result.andThen
549 | (\schemas ->
550 | builder
551 | |> toSchema
552 | |> Result.map (\schema -> schemas ++ [ ( key, schema ) ])
553 | )
554 | )
555 | (Ok [])
556 |
557 |
558 | toListOfSchemas : List SchemaBuilder -> Result String (List Schema)
559 | toListOfSchemas =
560 | List.foldl
561 | (\builder ->
562 | Result.andThen
563 | (\schemas ->
564 | builder
565 | |> toSchema
566 | |> Result.map (\schema -> schemas ++ [ schema ])
567 | )
568 | )
569 | (Ok [])
570 |
571 |
572 |
573 | -- updateWithSubSchema (\sub s -> { s | contains = sub })
574 |
575 |
576 | updateWithSubSchema : (Maybe Schema -> (SubSchema -> SubSchema)) -> SchemaBuilder -> SchemaBuilder -> SchemaBuilder
577 | updateWithSubSchema fn subSchemaBuilder =
578 | case subSchemaBuilder |> toSchema of
579 | Ok s ->
580 | fn (Just s)
581 | |> updateSchema
582 |
583 | Err err ->
584 | appendError err
585 |
586 |
587 | updateWithSchemata : (Maybe Schemata -> (SubSchema -> SubSchema)) -> SchemataBuilder -> SchemaBuilder -> SchemaBuilder
588 | updateWithSchemata fn schemataBuilder =
589 | case schemataBuilder |> toSchemata of
590 | Ok schemata ->
591 | updateSchema (fn (Just <| Schemata schemata))
592 |
593 | Err s ->
594 | appendError s
595 |
596 |
597 | updateWithListOfSchemas : (Maybe (List Schema) -> (SubSchema -> SubSchema)) -> List SchemaBuilder -> SchemaBuilder -> SchemaBuilder
598 | updateWithListOfSchemas fn schemasBuilder =
599 | case schemasBuilder |> toListOfSchemas of
600 | Ok ls ->
601 | updateSchema (fn (Just ls))
602 |
603 | Err s ->
604 | appendError s
605 |
606 |
607 | toString : String -> String
608 | toString =
609 | Encode.string >> Encode.encode 0
610 |
611 |
612 | exclusiveBoundaryToString : ExclusiveBoundary -> String
613 | exclusiveBoundaryToString eb =
614 | case eb of
615 | BoolBoundary b ->
616 | boolToString b
617 |
618 | NumberBoundary f ->
619 | String.fromFloat f
620 |
621 |
622 | boolToString : Bool -> String
623 | boolToString b =
624 | case b of
625 | True ->
626 | "true"
627 |
628 | False ->
629 | "false"
630 |
631 |
632 | {-| Encode schema into a builder code (elm)
633 | -}
634 | encode : Int -> Schema -> String
635 | encode level s =
636 | let
637 | indent : String
638 | indent =
639 | "\n" ++ String.repeat level " "
640 |
641 | pipe : String
642 | pipe =
643 | indent ++ "|> "
644 |
645 | comma : String
646 | comma =
647 | indent ++ ", "
648 |
649 | comma2 : String
650 | comma2 =
651 | indent ++ " , "
652 |
653 | comma4 : String
654 | comma4 =
655 | indent ++ " , "
656 |
657 | optionally : (a -> String) -> Maybe a -> String -> String -> String
658 | optionally fn val key res =
659 | case val of
660 | Just sLocal ->
661 | res ++ pipe ++ key ++ " " ++ fn sLocal
662 |
663 | Nothing ->
664 | res
665 |
666 | encodeItems : Items -> String -> String
667 | encodeItems items res =
668 | case items of
669 | ItemDefinition id ->
670 | res ++ pipe ++ "withItem " ++ (encode (level + 1) >> addParens) id
671 |
672 | ArrayOfItems aoi ->
673 | res ++ pipe ++ "withItem " ++ (aoi |> List.map (encode (level + 1)) |> String.join comma)
674 |
675 | NoItems ->
676 | res
677 |
678 | encodeDependency : String -> Dependency -> String
679 | encodeDependency key dep =
680 | case dep of
681 | PropSchema ps ->
682 | pipe ++ "withSchemaDependency \"" ++ key ++ "\" " ++ encode (level + 1) ps
683 |
684 | ArrayPropNames apn ->
685 | pipe
686 | ++ "withPropNamesDependency \""
687 | ++ key
688 | ++ "\" [ "
689 | ++ (apn
690 | |> List.map (\sLocal -> "\"" ++ sLocal ++ "\"")
691 | |> String.join ", "
692 | )
693 | ++ " ]"
694 |
695 | encodeDependencies : List ( String, Dependency ) -> String -> String
696 | encodeDependencies deps res =
697 | if List.isEmpty deps then
698 | res
699 |
700 | else
701 | res
702 | ++ pipe
703 | ++ "withDependencies"
704 | ++ (deps
705 | |> List.map (\( key, dep ) -> encodeDependency key dep)
706 | |> String.join pipe
707 | )
708 |
709 | singleTypeToString : SingleType -> String
710 | singleTypeToString st =
711 | case st of
712 | StringType ->
713 | "string"
714 |
715 | IntegerType ->
716 | "integer"
717 |
718 | NumberType ->
719 | "number"
720 |
721 | BooleanType ->
722 | "boolean"
723 |
724 | ObjectType ->
725 | "object"
726 |
727 | ArrayType ->
728 | "array"
729 |
730 | NullType ->
731 | "null"
732 |
733 | encodeType : Type -> String -> String
734 | encodeType t res =
735 | case t of
736 | SingleType st ->
737 | res ++ pipe ++ "withType \"" ++ singleTypeToString st ++ "\""
738 |
739 | NullableType st ->
740 | res ++ pipe ++ "withNullableType \"" ++ singleTypeToString st ++ "\""
741 |
742 | UnionType ut ->
743 | res ++ pipe ++ "withUnionType [" ++ (ut |> List.map (singleTypeToString >> toString) |> String.join ", ") ++ "]"
744 |
745 | AnyType ->
746 | res
747 |
748 | encodeListSchemas : List Schema -> String
749 | encodeListSchemas l =
750 | l
751 | |> List.map (encode (level + 1))
752 | |> String.join comma2
753 | |> (\sLocal -> indent ++ " [ " ++ sLocal ++ indent ++ " ]")
754 |
755 | encodeSchemata : Schemata -> String
756 | encodeSchemata (Schemata l) =
757 | l
758 | |> List.map (\( sLocal, x ) -> "( \"" ++ sLocal ++ "\"" ++ comma4 ++ encode (level + 2) x ++ indent ++ " )")
759 | |> String.join comma2
760 | |> (\sLocal -> indent ++ " [ " ++ sLocal ++ indent ++ " ]")
761 |
762 | addParens sLocal =
763 | "( "
764 | ++ sLocal
765 | ++ " )"
766 | in
767 | case s of
768 | BooleanSchema bs ->
769 | if bs then
770 | "boolSchema True"
771 |
772 | else
773 | "boolSchema False"
774 |
775 | ObjectSchema os ->
776 | [ encodeType os.type_
777 | , optionally toString os.id "withId"
778 | , optionally toString os.ref "withRef"
779 | , optionally toString os.title "withTitle"
780 | , optionally toString os.description "withDescription"
781 | , optionally (\x -> x |> Encode.encode 0 |> toString |> (\xLocal -> "(" ++ xLocal ++ " |> Decode.decodeString Decode.value |> Result.withDefault Encode.null)")) os.default "withDefault"
782 | , optionally (\examples -> examples |> Encode.list identity |> Encode.encode 0) os.examples "withExamples"
783 | , optionally encodeSchemata os.definitions "withDefinitions"
784 | , optionally String.fromFloat os.multipleOf "withMultipleOf"
785 | , optionally String.fromFloat os.maximum "withMaximum"
786 | , optionally exclusiveBoundaryToString os.exclusiveMaximum "withExclusiveMaximum"
787 | , optionally String.fromFloat os.minimum "withMinimum"
788 | , optionally exclusiveBoundaryToString os.exclusiveMinimum "withExclusiveMinimum"
789 | , optionally String.fromInt os.maxLength "withMaxLength"
790 | , optionally String.fromInt os.minLength "withMinLength"
791 | , optionally toString os.pattern "withPattern"
792 | , optionally toString os.format "withFormat"
793 | , encodeItems os.items
794 | , optionally (encode (level + 1) >> addParens) os.additionalItems "withAdditionalItems"
795 | , optionally String.fromInt os.maxItems "withMaxItems"
796 | , optionally String.fromInt os.minItems "withMinItems"
797 | , optionally boolToString os.uniqueItems "withUniqueItems"
798 | , optionally (encode (level + 1) >> addParens) os.contains "withContains"
799 | , optionally String.fromInt os.maxProperties "withMaxProperties"
800 | , optionally String.fromInt os.minProperties "withMinProperties"
801 | , optionally (\sLocal -> sLocal |> List.map Encode.string |> Encode.list identity |> Encode.encode 0) os.required "withRequired"
802 | , optionally encodeSchemata os.properties "withProperties"
803 | , optionally encodeSchemata os.patternProperties "withPatternProperties"
804 | , optionally (encode (level + 1) >> addParens) os.additionalProperties "withAdditionalProperties"
805 | , encodeDependencies os.dependencies
806 | , optionally (encode (level + 1) >> addParens) os.propertyNames "withPropertyNames"
807 | , optionally (\examples -> examples |> Encode.list identity |> Encode.encode 0 |> (\x -> "( " ++ x ++ " |> List.map Encode.string )")) os.enum "withEnum"
808 | , optionally (Encode.encode 0 >> addParens) os.const "withConst"
809 | , optionally encodeListSchemas os.allOf "withAllOf"
810 | , optionally encodeListSchemas os.anyOf "withAnyOf"
811 | , optionally encodeListSchemas os.oneOf "withOneOf"
812 | , optionally (encode (level + 1) >> addParens) os.not "withNot"
813 | ]
814 | |> List.foldl identity "buildSchema"
815 |
--------------------------------------------------------------------------------
/src/Json/Schema/Definitions.elm:
--------------------------------------------------------------------------------
1 | module Json.Schema.Definitions exposing
2 | ( Schema(..), SubSchema, Schemata(..), Items(..), Dependency(..), Type(..), SingleType(..), blankSchema, blankSubSchema, ExclusiveBoundary(..)
3 | , decoder, encode
4 | , stringToType, getCustomKeywordValue
5 | )
6 |
7 | {-| This module contains low-level structures JSON Schema build from.
8 | Normally you wouldn't need to use any of those definitions.
9 |
10 | If you really need this low-level API you might need [JSON Schema spec](http://json-schema.org/documentation.html) as guidance.
11 |
12 | Feel free to open [issue](https://github.com/1602/json-schema) to describe your use-case, it will affect development roadmap of this library.
13 |
14 |
15 | # Definitions
16 |
17 | @docs Schema, SubSchema, Schemata, Items, Dependency, Type, SingleType, blankSchema, blankSubSchema, ExclusiveBoundary
18 |
19 |
20 | # Decoding / encoding
21 |
22 | @docs decoder, encode
23 |
24 |
25 | # Misc
26 |
27 | @docs stringToType, getCustomKeywordValue
28 |
29 | -}
30 |
31 | import Json.Decode as Decode exposing (Decoder, Value, andThen, bool, fail, field, float, int, lazy, list, nullable, string, succeed, value)
32 | import Json.Decode.Pipeline as DecodePipeline exposing (optional, optionalAt, required, requiredAt)
33 | import Json.Encode as Encode
34 | import Util exposing (foldResults, isInt, resultToDecoder)
35 |
36 |
37 | {-| Schema can be either boolean or actual object containing validation and meta properties
38 | -}
39 | type Schema
40 | = BooleanSchema Bool
41 | | ObjectSchema SubSchema
42 |
43 |
44 | {-| This object holds all draft-6 schema properties
45 | -}
46 | type alias SubSchema =
47 | { type_ : Type
48 | , id : Maybe String
49 | , ref :
50 | Maybe String
51 |
52 | -- meta
53 | , title : Maybe String
54 | , description : Maybe String
55 | , default : Maybe Value
56 | , examples : Maybe (List Value)
57 | , definitions :
58 | Maybe Schemata
59 |
60 | -- numeric validations
61 | , multipleOf : Maybe Float
62 | , maximum : Maybe Float
63 | , exclusiveMaximum : Maybe ExclusiveBoundary
64 | , minimum : Maybe Float
65 | , exclusiveMinimum :
66 | Maybe ExclusiveBoundary
67 |
68 | -- string validations
69 | , maxLength : Maybe Int
70 | , minLength : Maybe Int
71 | , pattern : Maybe String
72 | , format :
73 | Maybe String
74 |
75 | -- array validations
76 | , items : Items
77 | , additionalItems : Maybe Schema
78 | , maxItems : Maybe Int
79 | , minItems : Maybe Int
80 | , uniqueItems : Maybe Bool
81 | , contains :
82 | Maybe Schema
83 |
84 | -- object validations
85 | , maxProperties : Maybe Int
86 | , minProperties : Maybe Int
87 | , required : Maybe (List String)
88 | , properties : Maybe Schemata
89 | , patternProperties : Maybe Schemata
90 | , additionalProperties : Maybe Schema
91 | , dependencies : List ( String, Dependency )
92 | , propertyNames :
93 | Maybe Schema
94 |
95 | -- misc validations
96 | , enum : Maybe (List Value)
97 | , const : Maybe Value
98 | , allOf : Maybe (List Schema)
99 | , anyOf : Maybe (List Schema)
100 | , oneOf : Maybe (List Schema)
101 | , not : Maybe Schema
102 | , source : Value
103 | }
104 |
105 |
106 | {-| List of schema-properties used in properties, definitions and patternProperties
107 | -}
108 | type Schemata
109 | = Schemata (List ( String, Schema ))
110 |
111 |
112 | {-| Items definition.
113 | -}
114 | type Items
115 | = NoItems
116 | | ItemDefinition Schema
117 | | ArrayOfItems (List Schema)
118 |
119 |
120 | {-| Dependency definition.
121 | -}
122 | type Dependency
123 | = ArrayPropNames (List String)
124 | | PropSchema Schema
125 |
126 |
127 | {-| Exclusive boundaries. Compatibility layer between draft-04 and draft-06 (keywords `exclusiveMinimum` and `exclusiveMaximum` has been changed from a boolean to a number to be consistent with the principle of keyword independence). Since we currently keep both draft-4 and draft-6 as same type definition, we have a union of `Bool` and `Float` here. It might be not a bad idea to separate type definitions for different drafts of JSON Schema, current API decision will be reconsidered when future versions of JSON Schema will arrive.
128 | -}
129 | type ExclusiveBoundary
130 | = BoolBoundary Bool
131 | | NumberBoundary Float
132 |
133 |
134 | {-| Create blank JSON Schema `{}`.
135 | -}
136 | blankSchema : Schema
137 | blankSchema =
138 | ObjectSchema blankSubSchema
139 |
140 |
141 | {-| -}
142 | blankSubSchema : SubSchema
143 | blankSubSchema =
144 | { type_ = AnyType
145 | , id = Nothing
146 | , ref = Nothing
147 | , title = Nothing
148 | , description = Nothing
149 | , default = Nothing
150 | , examples = Nothing
151 | , definitions = Nothing
152 | , multipleOf = Nothing
153 | , maximum = Nothing
154 | , exclusiveMaximum = Nothing
155 | , minimum = Nothing
156 | , exclusiveMinimum = Nothing
157 | , maxLength = Nothing
158 | , minLength = Nothing
159 | , pattern = Nothing
160 | , format = Nothing
161 | , items = NoItems
162 | , additionalItems = Nothing
163 | , maxItems = Nothing
164 | , minItems = Nothing
165 | , uniqueItems = Nothing
166 | , contains = Nothing
167 | , maxProperties = Nothing
168 | , minProperties = Nothing
169 | , required = Nothing
170 | , properties = Nothing
171 | , patternProperties = Nothing
172 | , additionalProperties = Nothing
173 | , dependencies = []
174 | , propertyNames = Nothing
175 | , enum = Nothing
176 | , const = Nothing
177 | , allOf = Nothing
178 | , anyOf = Nothing
179 | , oneOf = Nothing
180 | , not = Nothing
181 | , source = Encode.object []
182 | }
183 |
184 |
185 | type RowEncoder a
186 | = RowEncoder (Maybe a) String (a -> Value)
187 |
188 |
189 | {-| -}
190 | encode : Schema -> Value
191 | encode s =
192 | let
193 | optionally : (a -> Value) -> Maybe a -> String -> List ( String, Value ) -> List ( String, Value )
194 | optionally fn val key res =
195 | let
196 | result =
197 | res
198 | |> List.filter (\( k, _ ) -> k /= key)
199 | in
200 | case val of
201 | Just schema ->
202 | ( key, fn schema ) :: result
203 |
204 | Nothing ->
205 | result
206 |
207 | encodeItems : Items -> List ( String, Value ) -> List ( String, Value )
208 | encodeItems items res =
209 | case items of
210 | ItemDefinition id ->
211 | ( "items", encode id ) :: res
212 |
213 | ArrayOfItems aoi ->
214 | ( "items", aoi |> Encode.list encode ) :: res
215 |
216 | NoItems ->
217 | res
218 |
219 | encodeDependency : Dependency -> Value
220 | encodeDependency dep =
221 | case dep of
222 | PropSchema ps ->
223 | encode ps
224 |
225 | ArrayPropNames apn ->
226 | apn |> Encode.list Encode.string
227 |
228 | encodeDependencies : List ( String, Dependency ) -> List ( String, Value ) -> List ( String, Value )
229 | encodeDependencies deps res =
230 | if List.isEmpty deps then
231 | res
232 |
233 | else
234 | ( "dependencies", deps |> List.map (\( key, dep ) -> ( key, encodeDependency dep )) |> Encode.object ) :: res
235 |
236 | singleTypeToString : SingleType -> String
237 | singleTypeToString st =
238 | case st of
239 | StringType ->
240 | "string"
241 |
242 | IntegerType ->
243 | "integer"
244 |
245 | NumberType ->
246 | "number"
247 |
248 | BooleanType ->
249 | "boolean"
250 |
251 | ObjectType ->
252 | "object"
253 |
254 | ArrayType ->
255 | "array"
256 |
257 | NullType ->
258 | "null"
259 |
260 | encodeType : Type -> List ( String, Value ) -> List ( String, Value )
261 | encodeType t res =
262 | case t of
263 | SingleType st ->
264 | ( "type", st |> singleTypeToString |> Encode.string ) :: res
265 |
266 | NullableType st ->
267 | ( "type", [ "null" |> Encode.string, st |> singleTypeToString |> Encode.string ] |> Encode.list identity ) :: res
268 |
269 | UnionType ut ->
270 | ( "type", ut |> Encode.list (singleTypeToString >> Encode.string) ) :: res
271 |
272 | AnyType ->
273 | res
274 |
275 | encodeListSchemas : List Schema -> Value
276 | encodeListSchemas l =
277 | l
278 | |> Encode.list encode
279 |
280 | encodeSchemata : Schemata -> Value
281 | encodeSchemata (Schemata listSchemas) =
282 | listSchemas
283 | |> List.map (\( key, schema ) -> ( key, encode schema ))
284 | |> Encode.object
285 |
286 | encodeExclusiveBoundary : ExclusiveBoundary -> Value
287 | encodeExclusiveBoundary eb =
288 | case eb of
289 | BoolBoundary b ->
290 | Encode.bool b
291 |
292 | NumberBoundary f ->
293 | Encode.float f
294 |
295 | source : SubSchema -> List ( String, Value )
296 | source os =
297 | os.source
298 | |> Decode.decodeValue (Decode.keyValuePairs Decode.value)
299 | |> Result.withDefault []
300 | in
301 | case s of
302 | BooleanSchema bs ->
303 | Encode.bool bs
304 |
305 | ObjectSchema os ->
306 | [ encodeType os.type_
307 | , optionally Encode.string os.id "$id"
308 | , optionally Encode.string os.ref "$ref"
309 | , optionally Encode.string os.title "title"
310 | , optionally Encode.string os.description "description"
311 | , optionally identity os.default "default"
312 | , optionally (Encode.list identity) os.examples "examples"
313 | , optionally encodeSchemata os.definitions "definitions"
314 | , optionally Encode.float os.multipleOf "multipleOf"
315 | , optionally Encode.float os.maximum "maximum"
316 | , optionally encodeExclusiveBoundary os.exclusiveMaximum "exclusiveMaximum"
317 | , optionally Encode.float os.minimum "minimum"
318 | , optionally encodeExclusiveBoundary os.exclusiveMinimum "exclusiveMinimum"
319 | , optionally Encode.int os.maxLength "maxLength"
320 | , optionally Encode.int os.minLength "minLength"
321 | , optionally Encode.string os.pattern "pattern"
322 | , optionally Encode.string os.format "format"
323 | , encodeItems os.items
324 | , optionally encode os.additionalItems "additionalItems"
325 | , optionally Encode.int os.maxItems "maxItems"
326 | , optionally Encode.int os.minItems "minItems"
327 | , optionally Encode.bool os.uniqueItems "uniqueItems"
328 | , optionally encode os.contains "contains"
329 | , optionally Encode.int os.maxProperties "maxProperties"
330 | , optionally Encode.int os.minProperties "minProperties"
331 | , optionally (\list -> list |> Encode.list Encode.string) os.required "required"
332 | , optionally encodeSchemata os.properties "properties"
333 | , optionally encodeSchemata os.patternProperties "patternProperties"
334 | , optionally encode os.additionalProperties "additionalProperties"
335 | , encodeDependencies os.dependencies
336 | , optionally encode os.propertyNames "propertyNames"
337 | , optionally (Encode.list identity) os.enum "enum"
338 | , optionally identity os.const "const"
339 | , optionally encodeListSchemas os.allOf "allOf"
340 | , optionally encodeListSchemas os.anyOf "anyOf"
341 | , optionally encodeListSchemas os.oneOf "oneOf"
342 | , optionally encode os.not "not"
343 | ]
344 | |> List.foldl identity (source os)
345 | |> List.reverse
346 | |> Encode.object
347 |
348 |
349 | {-| -}
350 | decoder : Decoder Schema
351 | decoder =
352 | let
353 | singleType =
354 | string
355 | |> andThen singleTypeDecoder
356 |
357 | multipleTypes =
358 | string
359 | |> list
360 | |> andThen multipleTypesDecoder
361 |
362 | booleanSchemaDecoder =
363 | Decode.bool
364 | |> Decode.andThen
365 | (\b ->
366 | if b then
367 | succeed (BooleanSchema True)
368 |
369 | else
370 | succeed (BooleanSchema False)
371 | )
372 |
373 | exclusiveBoundaryDecoder =
374 | Decode.oneOf [ Decode.bool |> Decode.map BoolBoundary, Decode.float |> Decode.map NumberBoundary ]
375 |
376 | objectSchemaDecoder =
377 | Decode.succeed SubSchema
378 | |> optional "type"
379 | (Decode.oneOf [ multipleTypes, Decode.map SingleType singleType ])
380 | AnyType
381 | |> DecodePipeline.custom
382 | (Decode.map2
383 | (\a b ->
384 | if a == Nothing then
385 | b
386 |
387 | else
388 | a
389 | )
390 | (field "$id" string |> Decode.maybe)
391 | (field "id" string |> Decode.maybe)
392 | )
393 | |> optional "$ref" (nullable string) Nothing
394 | -- meta
395 | |> optional "title" (nullable string) Nothing
396 | |> optional "description" (nullable string) Nothing
397 | |> optional "default" (value |> Decode.map Just) Nothing
398 | |> optional "examples" (nullable <| list value) Nothing
399 | |> optional "definitions" (nullable <| lazy <| \_ -> schemataDecoder) Nothing
400 | -- number
401 | |> optional "multipleOf" (nullable float) Nothing
402 | |> optional "maximum" (nullable float) Nothing
403 | |> optional "exclusiveMaximum" (nullable exclusiveBoundaryDecoder) Nothing
404 | |> optional "minimum" (nullable float) Nothing
405 | |> optional "exclusiveMinimum" (nullable exclusiveBoundaryDecoder) Nothing
406 | -- string
407 | |> optional "maxLength" (nullable nonNegativeInt) Nothing
408 | |> optional "minLength" (nullable nonNegativeInt) Nothing
409 | |> optional "pattern" (nullable string) Nothing
410 | |> optional "format" (nullable string) Nothing
411 | -- array
412 | |> optional "items" (lazy (\_ -> itemsDecoder)) NoItems
413 | |> optional "additionalItems" (nullable <| lazy (\_ -> decoder)) Nothing
414 | |> optional "maxItems" (nullable nonNegativeInt) Nothing
415 | |> optional "minItems" (nullable nonNegativeInt) Nothing
416 | |> optional "uniqueItems" (nullable bool) Nothing
417 | |> optional "contains" (nullable <| lazy (\_ -> decoder)) Nothing
418 | |> optional "maxProperties" (nullable nonNegativeInt) Nothing
419 | |> optional "minProperties" (nullable nonNegativeInt) Nothing
420 | |> optional "required" (nullable (list string)) Nothing
421 | |> optional "properties" (nullable (lazy (\_ -> schemataDecoder))) Nothing
422 | |> optional "patternProperties" (nullable (lazy (\_ -> schemataDecoder))) Nothing
423 | |> optional "additionalProperties" (nullable <| lazy (\_ -> decoder)) Nothing
424 | |> optional "dependencies" (lazy (\_ -> dependenciesDecoder)) []
425 | |> optional "propertyNames" (nullable <| lazy (\_ -> decoder)) Nothing
426 | |> optional "enum" (nullable nonEmptyUniqueArrayOfValuesDecoder) Nothing
427 | |> optional "const" (value |> Decode.map Just) Nothing
428 | |> optional "allOf" (nullable (lazy (\_ -> nonEmptyListOfSchemas))) Nothing
429 | |> optional "anyOf" (nullable (lazy (\_ -> nonEmptyListOfSchemas))) Nothing
430 | |> optional "oneOf" (nullable (lazy (\_ -> nonEmptyListOfSchemas))) Nothing
431 | |> optional "not" (nullable <| lazy (\_ -> decoder)) Nothing
432 | |> requiredAt [] Decode.value
433 | in
434 | Decode.oneOf
435 | [ booleanSchemaDecoder
436 | , objectSchemaDecoder
437 | |> Decode.andThen
438 | (\b ->
439 | succeed (ObjectSchema b)
440 | )
441 | ]
442 |
443 |
444 | nonEmptyListOfSchemas : Decoder (List Schema)
445 | nonEmptyListOfSchemas =
446 | list (lazy (\_ -> decoder))
447 | |> andThen failIfEmpty
448 |
449 |
450 | nonEmptyUniqueArrayOfValuesDecoder : Decoder (List Value)
451 | nonEmptyUniqueArrayOfValuesDecoder =
452 | list value
453 | |> andThen failIfValuesAreNotUnique
454 | |> andThen failIfEmpty
455 |
456 |
457 | failIfValuesAreNotUnique : List Value -> Decoder (List Value)
458 | failIfValuesAreNotUnique l =
459 | succeed l
460 |
461 |
462 | failIfEmpty : List a -> Decoder (List a)
463 | failIfEmpty l =
464 | if List.isEmpty l then
465 | fail "List is empty"
466 |
467 | else
468 | succeed l
469 |
470 |
471 | itemsDecoder : Decoder Items
472 | itemsDecoder =
473 | Decode.oneOf
474 | [ Decode.map ArrayOfItems <| list decoder
475 | , Decode.map ItemDefinition decoder
476 | ]
477 |
478 |
479 | dependenciesDecoder : Decoder (List ( String, Dependency ))
480 | dependenciesDecoder =
481 | Decode.oneOf
482 | [ Decode.map ArrayPropNames (list string)
483 | , Decode.map PropSchema decoder
484 | ]
485 | |> Decode.keyValuePairs
486 |
487 |
488 | nonNegativeInt : Decoder Int
489 | nonNegativeInt =
490 | int
491 | |> andThen
492 | (\x ->
493 | if x >= 0 then
494 | succeed x
495 |
496 | else
497 | fail "Expected non-negative int"
498 | )
499 |
500 |
501 | {-| Type property in json schema can be a single type or array of them, this type definition wraps up this complexity, also it introduces concept of nullable type, which is array of "null" type and a single type speaking JSON schema language, but also a useful concept to treat it separately from list of types.
502 | -}
503 | type Type
504 | = AnyType
505 | | SingleType SingleType
506 | | NullableType SingleType
507 | | UnionType (List SingleType)
508 |
509 |
510 | {-| -}
511 | type SingleType
512 | = IntegerType
513 | | NumberType
514 | | StringType
515 | | BooleanType
516 | | ArrayType
517 | | ObjectType
518 | | NullType
519 |
520 |
521 | multipleTypesDecoder : List String -> Decoder Type
522 | multipleTypesDecoder lst =
523 | case lst of
524 | [ x, "null" ] ->
525 | Decode.map NullableType <| singleTypeDecoder x
526 |
527 | [ "null", x ] ->
528 | Decode.map NullableType <| singleTypeDecoder x
529 |
530 | [ x ] ->
531 | Decode.map SingleType <| singleTypeDecoder x
532 |
533 | otherList ->
534 | otherList
535 | |> List.sort
536 | |> List.map stringToType
537 | |> foldResults
538 | |> Result.andThen (Ok << UnionType)
539 | |> resultToDecoder
540 |
541 |
542 | {-| Attempt to parse string into a single type, it recognises the following list of types:
543 |
544 | - integer
545 | - number
546 | - string
547 | - boolean
548 | - array
549 | - object
550 | - null
551 |
552 | -}
553 | stringToType : String -> Result String SingleType
554 | stringToType s =
555 | case s of
556 | "integer" ->
557 | Ok IntegerType
558 |
559 | "number" ->
560 | Ok NumberType
561 |
562 | "string" ->
563 | Ok StringType
564 |
565 | "boolean" ->
566 | Ok BooleanType
567 |
568 | "array" ->
569 | Ok ArrayType
570 |
571 | "object" ->
572 | Ok ObjectType
573 |
574 | "null" ->
575 | Ok NullType
576 |
577 | _ ->
578 | Err ("Unknown type: " ++ s)
579 |
580 |
581 | singleTypeDecoder : String -> Decoder SingleType
582 | singleTypeDecoder s =
583 | case stringToType s of
584 | Ok st ->
585 | succeed st
586 |
587 | Err msg ->
588 | fail msg
589 |
590 |
591 | schemataDecoder : Decoder Schemata
592 | schemataDecoder =
593 | Decode.keyValuePairs (lazy (\_ -> decoder))
594 | |> Decode.map Schemata
595 |
596 |
597 | {-| Return custom keyword value by its name, useful when dealing with additional meta information added along with standard JSON Schema keywords.
598 | -}
599 | getCustomKeywordValue : String -> Schema -> Maybe Value
600 | getCustomKeywordValue key schema =
601 | case schema of
602 | ObjectSchema os ->
603 | os.source
604 | |> Decode.decodeValue (Decode.keyValuePairs Decode.value)
605 | |> Result.withDefault []
606 | |> List.filterMap
607 | (\( k, v ) ->
608 | if k == key then
609 | Just v
610 |
611 | else
612 | Nothing
613 | )
614 | |> List.head
615 |
616 | _ ->
617 | Nothing
618 |
--------------------------------------------------------------------------------
/src/Json/Schema/Examples.elm:
--------------------------------------------------------------------------------
1 | module Json.Schema.Examples exposing (bookingSchema, coreSchemaDraft6)
2 |
3 | import Json.Decode
4 | import Json.Encode as Encode
5 | import Json.Schema.Builder exposing (..)
6 | import Json.Schema.Definitions exposing (Schema, blankSchema)
7 |
8 |
9 | coreSchemaDraft6 : Schema
10 | coreSchemaDraft6 =
11 | buildSchema
12 | |> withUnionType [ "boolean", "object" ]
13 | |> withTitle "Core schema meta-schema"
14 | |> withDefault (Encode.object [])
15 | |> withDefinitions
16 | [ ( "schemaArray"
17 | , buildSchema
18 | |> withType "array"
19 | |> withItem (buildSchema |> withRef "#")
20 | |> withMinItems 1
21 | )
22 | , ( "nonNegativeInteger"
23 | , buildSchema
24 | |> withType "integer"
25 | |> withMinimum 0
26 | )
27 | , ( "nonNegativeIntegerDefault0"
28 | , buildSchema
29 | |> withAllOf
30 | [ buildSchema
31 | |> withRef "#/definitions/nonNegativeInteger"
32 | , buildSchema
33 | |> withDefault (Encode.int 0)
34 | ]
35 | )
36 | , ( "simpleTypes"
37 | , buildSchema
38 | |> withEnum
39 | ([ "array"
40 | , "boolean"
41 | , "integer"
42 | , "null"
43 | , "number"
44 | , "object"
45 | , "string"
46 | ]
47 | |> List.map Encode.string
48 | )
49 | )
50 | , ( "stringArray"
51 | , buildSchema
52 | |> withType "array"
53 | |> withDefault ([] |> Encode.list)
54 | |> withItem (buildSchema |> withType "string")
55 | )
56 | ]
57 | |> withProperties
58 | [ ( "$id"
59 | , buildSchema
60 | |> withType "string"
61 | |> withFormat "uri-reference"
62 | )
63 | , ( "$schema"
64 | , buildSchema
65 | |> withType "string"
66 | |> withFormat "uri"
67 | )
68 | , ( "$ref"
69 | , buildSchema
70 | |> withType "string"
71 | |> withFormat "uri-reference"
72 | )
73 | , ( "title"
74 | , buildSchema |> withType "string"
75 | )
76 | , ( "description"
77 | , buildSchema |> withType "string"
78 | )
79 | , ( "default"
80 | , buildSchema
81 | )
82 | , ( "multipleOf"
83 | , buildSchema
84 | |> withType "number"
85 | |> withExclusiveMinimum 0
86 | )
87 | , ( "maximum"
88 | , buildSchema |> withType "number"
89 | )
90 | , ( "exclusiveMaximum"
91 | , buildSchema |> withType "number"
92 | )
93 | , ( "minimum"
94 | , buildSchema |> withType "number"
95 | )
96 | , ( "exclusiveMinimum"
97 | , buildSchema |> withType "number"
98 | )
99 | , ( "maxLength"
100 | , buildSchema |> withRef "#/definitions/nonNegativeInteger"
101 | )
102 | , ( "minLength"
103 | , buildSchema |> withRef "#/definitions/nonNegativeIntegerDefault0"
104 | )
105 | , ( "pattern"
106 | , buildSchema
107 | |> withType "string"
108 | |> withFormat "regex"
109 | )
110 | , ( "additionalItems"
111 | , buildSchema |> withRef "#"
112 | )
113 | , ( "items"
114 | , buildSchema
115 | |> withDefault (Encode.object [])
116 | |> withAnyOf
117 | [ buildSchema |> withRef "#"
118 | , buildSchema |> withRef "#/definitions/schemaArray"
119 | ]
120 | )
121 | , ( "maxItems"
122 | , buildSchema |> withRef "#/definitions/nonNegativeInteger"
123 | )
124 | , ( "minItems"
125 | , buildSchema |> withRef "#/definitions/nonNegativeIntegerDefault0"
126 | )
127 | , ( "uniqueItems"
128 | , buildSchema
129 | |> withType "boolean"
130 | |> withDefault (Encode.bool False)
131 | )
132 | , ( "contains"
133 | , buildSchema |> withRef "#"
134 | )
135 | , ( "maxProperties"
136 | , buildSchema |> withRef "#/definitions/nonNegativeInteger"
137 | )
138 | , ( "minProperties"
139 | , buildSchema |> withRef "#/definitions/nonNegativeIntegerDefault0"
140 | )
141 | , ( "required"
142 | , buildSchema |> withRef "#/definitions/stringArray"
143 | )
144 | , ( "additionalProperties"
145 | , buildSchema |> withRef "#"
146 | )
147 | , ( "definitions"
148 | , buildSchema
149 | |> withType "object"
150 | |> withDefault (Encode.object [])
151 | |> withAdditionalProperties (buildSchema |> withRef "#")
152 | )
153 | , ( "properties"
154 | , buildSchema
155 | |> withType "object"
156 | |> withDefault (Encode.object [])
157 | |> withAdditionalProperties (buildSchema |> withRef "#")
158 | )
159 | , ( "patternProperties"
160 | , buildSchema
161 | |> withType "object"
162 | |> withDefault (Encode.object [])
163 | |> withAdditionalProperties (buildSchema |> withRef "#")
164 | -- |> withPropertyNames (buildSchema |> withFormat "regex")
165 | )
166 | , ( "dependencies"
167 | , buildSchema
168 | |> withType "object"
169 | |> withAdditionalProperties
170 | (buildSchema
171 | |> withAnyOf
172 | [ buildSchema |> withRef "#"
173 | , buildSchema |> withRef "#/definitions/stringArray"
174 | ]
175 | )
176 | )
177 | , ( "propertyNames"
178 | , buildSchema |> withRef "#"
179 | )
180 | , ( "const"
181 | , buildSchema
182 | )
183 | , ( "enum"
184 | , buildSchema
185 | |> withType "array"
186 | |> withMinItems 1
187 | |> withUniqueItems True
188 | )
189 | , ( "type"
190 | , buildSchema
191 | |> withAnyOf
192 | [ buildSchema
193 | |> withRef "#/definitions/simpleTypes"
194 | , buildSchema
195 | |> withType "array"
196 | |> withItem (buildSchema |> withRef "#/definitions/simpleTypes")
197 | |> withMinItems 1
198 | |> withUniqueItems True
199 | ]
200 | )
201 | , ( "format"
202 | , buildSchema |> withType "string"
203 | )
204 | , ( "allOf"
205 | , buildSchema |> withRef "#/definitions/schemaArray"
206 | )
207 | , ( "anyOf"
208 | , buildSchema |> withRef "#/definitions/schemaArray"
209 | )
210 | , ( "oneOf"
211 | , buildSchema |> withRef "#/definitions/schemaArray"
212 | )
213 | , ( "not"
214 | , buildSchema |> withRef "#"
215 | )
216 | ]
217 | |> toSchema
218 | |> Result.withDefault blankSchema
219 |
220 |
221 | bookingSchema : Schema
222 | bookingSchema =
223 | buildSchema
224 | |> withType "object"
225 | |> withDefinitions
226 | [ ( "basicPerson"
227 | , buildSchema
228 | |> withType "object"
229 | |> withTitle "Basic Person"
230 | |> withDescription "Minimal information representing a person"
231 | |> withRequired [ "title", "firstName", "lastName" ]
232 | |> withProperties
233 | [ ( "title"
234 | , buildSchema
235 | |> withEnum ([ "mr", "miss", "ms", "mrs" ] |> List.map Encode.string)
236 | )
237 | , ( "firstName"
238 | , buildSchema
239 | |> withType "string"
240 | )
241 | , ( "lastName"
242 | , buildSchema
243 | |> withType "string"
244 | )
245 | ]
246 | )
247 | , ( "personAddress"
248 | , buildSchema
249 | |> withTitle "Person+Address"
250 | |> withDescription "Weird combination of a person with an address, early days [Selective breeding](https://en.wikipedia.org/wiki/Selective_breeding) experiment."
251 | |> withAllOf
252 | [ buildSchema
253 | |> withRef "#/definitions/basicPerson"
254 | , buildSchema
255 | |> withRef "#/definitions/address"
256 | ]
257 | )
258 | , ( "phone"
259 | , buildSchema
260 | |> withType "object"
261 | |> withTitle "Phone number"
262 | |> withRequired [ "countryCode", "number" ]
263 | |> withProperties
264 | [ ( "countryCode"
265 | , buildSchema
266 | |> withRef "#/definitions/countryCode"
267 | )
268 | , ( "number"
269 | , buildSchema
270 | |> withType "string"
271 | |> withDescription "Mobile phone number (numbers only, excluding country code)"
272 | |> withMinLength 10
273 | )
274 | ]
275 | )
276 | , ( "price"
277 | , buildSchema
278 | |> withType "object"
279 | |> withRequired [ "currencyCode", "value" ]
280 | |> withProperties
281 | [ ( "currencyCode"
282 | , buildSchema
283 | |> withRef "#/definitions/currencyCode"
284 | )
285 | , ( "value"
286 | , buildSchema
287 | |> withType "integer"
288 | |> withDescription "A positive integer in the smallest currency unit (that is, 100 pence for £1.00)"
289 | |> withMinimum 0
290 | )
291 | ]
292 | |> withAdditionalProperties (boolSchema False)
293 | )
294 | , ( "paymentCard"
295 | , buildSchema
296 | |> withType "object"
297 | |> withTitle "Payment Card"
298 | |> withDescription "Note that instead of card number `panToken` must be supplied because of PCI DSS Compliance limitations"
299 | |> withRequired [ "type", "brand", "expirationDate", "name", "cvv" ]
300 | |> withProperties
301 | [ ( "type"
302 | , buildSchema
303 | |> withType "string"
304 | |> withEnum ([ "debit", "credit" ] |> List.map Encode.string)
305 | )
306 | , ( "brand"
307 | , buildSchema
308 | |> withType "string"
309 | |> withEnum ([ "visa", "mastercard", "amex" ] |> List.map Encode.string)
310 | )
311 | , ( "panToken"
312 | , buildSchema
313 | |> withType "string"
314 | |> withMinLength 20
315 | )
316 | , ( "expirationDate"
317 | , buildSchema
318 | |> withType "string"
319 | |> withMaxLength 7
320 | |> withMinLength 7
321 | |> withPattern "^20[0-9]{2}-(?:0[1-9]|1[0-2])$"
322 | )
323 | , ( "name"
324 | , buildSchema
325 | |> withType "string"
326 | )
327 | , ( "cvv"
328 | , buildSchema
329 | |> withType "string"
330 | |> withMaxLength 4
331 | |> withMinLength 3
332 | )
333 | ]
334 | )
335 | , ( "address"
336 | , buildSchema
337 | |> withType "object"
338 | |> withRequired [ "line1", "city", "postcode", "countryCode" ]
339 | |> withProperties
340 | [ ( "line1"
341 | , buildSchema
342 | |> withType "string"
343 | |> withTitle "Address line 1"
344 | |> withDescription "Street name with house number"
345 | )
346 | , ( "line2"
347 | , buildSchema
348 | |> withType "string"
349 | |> withTitle "Address line 2"
350 | |> withDescription "Additional address info"
351 | )
352 | , ( "city"
353 | , buildSchema
354 | |> withType "string"
355 | )
356 | , ( "postcode"
357 | , buildSchema
358 | |> withType "string"
359 | )
360 | , ( "countryCode"
361 | , buildSchema
362 | |> withRef "#/definitions/countryCode"
363 | )
364 | ]
365 | )
366 | , ( "datePlace"
367 | , buildSchema
368 | |> withType "object"
369 | |> withRequired [ "dateTime", "airportCode" ]
370 | |> withProperties
371 | [ ( "countryCode"
372 | , buildSchema
373 | |> withRef "#/definitions/countryCode"
374 | )
375 | , ( "dateTime"
376 | , buildSchema
377 | |> withType "string"
378 | |> withTitle "Date-Time"
379 | |> withDescription "Date and time of flight (airport local time)"
380 | |> withPattern "^20[0-9]{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-3][0-9]) [012][0-9]:[0-5][0-9]$"
381 | )
382 | , ( "airportCode"
383 | , buildSchema
384 | |> withType "string"
385 | |> withTitle "Airport Code"
386 | |> withDescription "International Air Transport Association airport code"
387 | |> withMaxLength 3
388 | |> withMinLength 3
389 | |> withPattern "^[A-Z]{3}$"
390 | )
391 | ]
392 | |> withPropertyNames buildSchema
393 | |> withEnum ([ "dateTime", "airportCode", "countryCode" ] |> List.map Encode.string)
394 | )
395 | , ( "currencyCode"
396 | , buildSchema
397 | |> withType "string"
398 | |> withDescription "3-letter ISO code representing the currency. __Lowercase__."
399 | |> withMaxLength 3
400 | |> withMinLength 3
401 | |> withEnum ([ "all", "afn", "ars", "awg", "aud", "azn", "bsd", "bbd", "byn", "bzd", "bmd", "bob", "bam", "bwp", "bgn", "brl", "bnd", "khr", "cad", "kyd", "clp", "cny", "cop", "crc", "hrk", "cup", "czk", "dkk", "dop", "xcd", "egp", "svc", "eur", "fkp", "fjd", "ghs", "gip", "gtq", "ggp", "gyd", "hnl", "hkd", "huf", "isk", "inr", "idr", "irr", "imp", "ils", "jmd", "jpy", "jep", "kzt", "kpw", "krw", "kgs", "lak", "lbp", "lrd", "mkd", "myr", "mur", "mxn", "mnt", "mzn", "nad", "npr", "ang", "nzd", "nio", "ngn", "nok", "omr", "pkr", "pab", "pyg", "pen", "php", "pln", "qar", "ron", "rub", "shp", "sar", "rsd", "scr", "sgd", "sbd", "sos", "zar", "lkr", "sek", "chf", "srd", "syp", "twd", "thb", "ttd", "try", "tvd", "uah", "gbp", "usd", "uyu", "uzs", "vef", "vnd", "yer", "zwd" ] |> List.map Encode.string)
402 | )
403 | , ( "countryCode"
404 | , buildSchema
405 | |> withType "string"
406 | |> withTitle "ISO code representing the country"
407 | |> withDescription "2-letter ISO code representing the country. United Kingdom is officially assigned the alpha-2 code `gb` rather than `uk`. __Lowercase__."
408 | |> withMaxLength 2
409 | |> withMinLength 2
410 | |> withEnum ([ "af", "ax", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "bq", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cw", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw", "ps", "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "bl", "sh", "kn", "lc", "mf", "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sx", "sk", "si", "sb", "so", "za", "gs", "ss", "es", "lk", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "gb", "us", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", "zm", "zw" ] |> List.map Encode.string)
411 | )
412 | ]
413 | |> withRequired [ "url", "account", "passengers", "payment", "flight" ]
414 | |> withProperties
415 | [ ( "url"
416 | , buildSchema
417 | |> withType "string"
418 | |> withFormat "uri"
419 | )
420 | , ( "account"
421 | , buildSchema
422 | |> withType "object"
423 | |> withRequired [ "email", "phone", "isExisting" ]
424 | |> withProperties
425 | [ ( "email"
426 | , buildSchema
427 | |> withType "string"
428 | |> withFormat "email"
429 | )
430 | , ( "password"
431 | , buildSchema
432 | |> withType "string"
433 | |> withFormat "string"
434 | )
435 | , ( "phone"
436 | , buildSchema
437 | |> withRef "#/definitions/phone"
438 | )
439 | , ( "isExisting"
440 | , buildSchema
441 | |> withType "boolean"
442 | |> withEnum ([ True, False ] |> List.map Encode.bool)
443 | )
444 | ]
445 | )
446 | , ( "flight"
447 | , buildSchema
448 | |> withType "object"
449 | |> withRequired [ "cabinClass", "from", "to", "price" ]
450 | |> withProperties
451 | [ ( "from"
452 | , buildSchema
453 | |> withRef "#/definitions/datePlace"
454 | )
455 | , ( "to"
456 | , buildSchema
457 | |> withRef "#/definitions/datePlace"
458 | )
459 | , ( "return"
460 | , buildSchema
461 | |> withType "object"
462 | |> withRequired [ "from", "to" ]
463 | |> withProperties
464 | [ ( "from"
465 | , buildSchema
466 | |> withRef "#/definitions/datePlace"
467 | )
468 | , ( "to"
469 | , buildSchema
470 | |> withRef "#/definitions/datePlace"
471 | )
472 | ]
473 | )
474 | , ( "price"
475 | , buildSchema
476 | |> withRef "#/definitions/price"
477 | )
478 | , ( "cabinClass"
479 | , buildSchema
480 | |> withType "string"
481 | |> withEnum ([ "economy", "economy premium", "business", "first" ] |> List.map Encode.string)
482 | )
483 | ]
484 | |> withAdditionalProperties (boolSchema True)
485 | )
486 | , ( "passengers"
487 | , buildSchema
488 | |> withType "array"
489 | |> withItem
490 | (buildSchema
491 | |> withType "object"
492 | |> withRequired [ "title", "firstName", "lastName", "dateOfBirth", "hasHoldLuggage" ]
493 | |> withProperties
494 | [ ( "title"
495 | , buildSchema
496 | |> withEnum ([ "mr", "miss", "ms", "mrs" ] |> List.map Encode.string)
497 | )
498 | , ( "firstName"
499 | , buildSchema
500 | |> withType "string"
501 | )
502 | , ( "lastName"
503 | , buildSchema
504 | |> withType "string"
505 | )
506 | , ( "dateOfBirth"
507 | , buildSchema
508 | |> withType "string"
509 | |> withFormat "date"
510 | )
511 | , ( "hasHoldLuggage"
512 | , buildSchema
513 | |> withType "boolean"
514 | |> withEnum ([ True, False ] |> List.map Encode.bool)
515 | )
516 | , ( "id"
517 | , buildSchema
518 | |> withType "object"
519 | |> withProperties
520 | [ ( "type"
521 | , buildSchema
522 | |> withType "string"
523 | )
524 | , ( "number"
525 | , buildSchema
526 | |> withType "string"
527 | )
528 | , ( "expDate"
529 | , buildSchema
530 | |> withType "string"
531 | |> withFormat "date"
532 | )
533 | , ( "countryCode"
534 | , buildSchema
535 | |> withRef "#/definitions/countryCode"
536 | )
537 | ]
538 | )
539 | ]
540 | )
541 | |> withMaxItems 1
542 | |> withMinItems 1
543 | )
544 | , ( "payment"
545 | , buildSchema
546 | |> withType "object"
547 | |> withRequired [ "card", "address" ]
548 | |> withProperties
549 | [ ( "card"
550 | , buildSchema
551 | |> withRef "#/definitions/paymentCard"
552 | )
553 | , ( "address"
554 | , buildSchema
555 | |> withRef "#/definitions/personAddress"
556 | )
557 | ]
558 | )
559 | ]
560 | |> withAdditionalProperties (boolSchema False)
561 | |> toSchema
562 | |> Result.withDefault blankSchema
563 |
--------------------------------------------------------------------------------
/src/Json/Schema/Helpers.elm:
--------------------------------------------------------------------------------
1 | module Json.Schema.Helpers exposing
2 | ( ImpliedType
3 | , collectIds
4 | , typeToList
5 | --, implyType
6 | --, for
7 |
8 | , typeToString
9 | , whenObjectSchema
10 | --, makeJsonPointer
11 | -- , resolve
12 | --, resolveReference
13 | --, calcSubSchemaType
14 |
15 | )
16 |
17 | import Dict exposing (Dict)
18 | import Json.Decode as Decode exposing (Value, decodeString, decodeValue)
19 | import Json.Encode as Encode
20 | import Json.Schema.Definitions as Schema
21 | exposing
22 | ( Items(..)
23 | , Schema(..)
24 | , Schemata(..)
25 | , SingleType(..)
26 | , SubSchema
27 | , Type(..)
28 | , blankSchema
29 | , blankSubSchema
30 | )
31 | import Ref exposing (SchemataPool, parseJsonPointer, resolveReference)
32 |
33 |
34 | type alias ImpliedType =
35 | { type_ : Type
36 | , schema : SubSchema
37 | , error : Maybe String
38 | }
39 |
40 |
41 | singleTypeToString : SingleType -> String
42 | singleTypeToString st =
43 | case st of
44 | StringType ->
45 | "string"
46 |
47 | IntegerType ->
48 | "integer"
49 |
50 | NumberType ->
51 | "number"
52 |
53 | BooleanType ->
54 | "boolean"
55 |
56 | ObjectType ->
57 | "object"
58 |
59 | ArrayType ->
60 | "array"
61 |
62 | NullType ->
63 | "null"
64 |
65 |
66 | typeToString : Type -> String
67 | typeToString t =
68 | case t of
69 | NullableType NullType ->
70 | "null"
71 |
72 | NullableType st ->
73 | "nullable " ++ singleTypeToString st
74 |
75 | SingleType s ->
76 | singleTypeToString s
77 |
78 | UnionType l ->
79 | l
80 | |> List.map singleTypeToString
81 | |> String.join ", "
82 |
83 | AnyType ->
84 | "any"
85 |
86 |
87 | typeToList : Type -> List String
88 | typeToList t =
89 | case t of
90 | NullableType NullType ->
91 | [ "null" ]
92 |
93 | NullableType st ->
94 | [ "nullable " ++ singleTypeToString st ]
95 |
96 | SingleType s ->
97 | [ singleTypeToString s ]
98 |
99 | UnionType l ->
100 | l
101 | |> List.map singleTypeToString
102 |
103 | AnyType ->
104 | []
105 |
106 |
107 | whenObjectSchema : Schema -> Maybe SubSchema
108 | whenObjectSchema schema =
109 | case schema of
110 | ObjectSchema os ->
111 | Just os
112 |
113 | BooleanSchema _ ->
114 | Nothing
115 |
116 |
117 | makeJsonPointer : ( Bool, String, List String ) -> String
118 | makeJsonPointer ( isPointer, ns, path ) =
119 | if isPointer then
120 | ("#" :: path)
121 | |> String.join "/"
122 | |> (++) ns
123 |
124 | else if List.isEmpty path then
125 | ns
126 |
127 | else
128 | path
129 | |> String.join "/"
130 | |> (++) (ns ++ "#")
131 |
132 |
133 |
134 | {-
135 | for : String -> Schema -> Maybe Schema
136 | for jsonPointer schema =
137 | jsonPointer
138 | |> parseJsonPointer
139 | |> List.foldl (weNeedToGoDeeper schema) (Just schema)
140 |
141 |
142 | implyType : Value -> Schema -> String -> ImpliedType
143 | implyType val schema subpath =
144 | let
145 | path =
146 | parseJsonPointer subpath
147 |
148 | actualValue =
149 | val
150 | |> Decode.decodeValue (Decode.at path Decode.value)
151 | |> Result.toMaybe
152 | in
153 | path
154 | |> List.foldl (weNeedToGoDeeper schema) (Just schema)
155 | |> Maybe.andThen whenObjectSchema
156 | |> Maybe.andThen (calcSubSchemaType actualValue schema)
157 | |> \x ->
158 | case x of
159 | Nothing ->
160 | { type_ = AnyType
161 | , schema = blankSubSchema
162 | , error = Just <| "Can't imply type: " ++ subpath
163 | }
164 |
165 | Just ( t, os ) ->
166 | { type_ = t
167 | , schema = os
168 | , error = Nothing
169 | }
170 |
171 | -}
172 |
173 |
174 | getListItem : Int -> List a -> Maybe a
175 | getListItem index list =
176 | let
177 | ( _, result ) =
178 | List.foldl
179 | (\item ( i, resultLocal ) ->
180 | if index == i then
181 | ( i + 1, Just item )
182 |
183 | else
184 | ( i + 1, resultLocal )
185 | )
186 | ( 0, Nothing )
187 | list
188 | in
189 | result
190 |
191 |
192 | setListItem : Int -> a -> List a -> List a
193 | setListItem index a list =
194 | List.indexedMap
195 | (\i item ->
196 | if index == i then
197 | a
198 |
199 | else
200 | item
201 | )
202 | list
203 |
204 |
205 |
206 | {-
207 | calcSubSchemaType : Maybe Value -> Schema -> SubSchema -> Maybe ( Type, SubSchema )
208 | calcSubSchemaType actualValue schema os =
209 | (case os.ref of
210 | Just ref ->
211 | ref
212 | |> resolveReference schema
213 | |> Maybe.andThen whenObjectSchema
214 |
215 | Nothing ->
216 | Just os
217 | )
218 | |> Maybe.andThen
219 | (\os ->
220 | case os.type_ of
221 | AnyType ->
222 | [ os.anyOf
223 | , os.allOf
224 | , os.oneOf
225 | ]
226 | |> List.map (Maybe.withDefault [])
227 | |> List.concat
228 | |> tryAllSchemas actualValue schema
229 | |> \res ->
230 | if res == Nothing then
231 | if os.properties /= Nothing || os.additionalProperties /= Nothing then
232 | Just ( SingleType ObjectType, os )
233 | else if os.enum /= Nothing then
234 | os.enum
235 | |> deriveTypeFromEnum
236 | |> \t -> Just ( t, os )
237 | else if os == blankSubSchema then
238 | Just ( AnyType, os )
239 | else
240 | Nothing
241 | else
242 | res
243 |
244 | UnionType ut ->
245 | if ut == [ BooleanType, ObjectType ] || ut == [ ObjectType, BooleanType ] then
246 | Just ( SingleType ObjectType, os )
247 | else
248 | Just ( os.type_, os )
249 |
250 | x ->
251 | Just ( x, os )
252 | )
253 |
254 |
255 | deriveTypeFromValue : Value -> Maybe Type
256 | deriveTypeFromValue val =
257 | case Decode.decodeValue Decode.string val of
258 | Ok _ ->
259 | Just <| SingleType StringType
260 |
261 | Err _ ->
262 | Nothing
263 |
264 |
265 | deriveTypeFromEnum : Maybe (List Value) -> Type
266 | deriveTypeFromEnum enum =
267 | enum
268 | |> Maybe.andThen List.head
269 | |> Maybe.andThen deriveTypeFromValue
270 | |> Maybe.withDefault AnyType
271 | -}
272 | {-
273 | resolve : Schema -> Schema -> Schema
274 | resolve rootSchema schema =
275 | schema
276 | |> whenObjectSchema
277 | |> Maybe.andThen
278 | (\os ->
279 | os.ref
280 | |> Maybe.andThen (resolveReference "" rootSchema)
281 | )
282 | |> Maybe.withDefault schema
283 | -}
284 | {-
285 | weNeedToGoDeeper : Schema -> String -> Maybe Schema -> Maybe Schema
286 | weNeedToGoDeeper rootSchema key schema =
287 | schema
288 | |> Maybe.andThen whenObjectSchema
289 | |> Maybe.andThen
290 | (\os ->
291 | case os.ref of
292 | Just r ->
293 | resolveReference rootSchema r
294 |
295 | Nothing ->
296 | schema
297 | )
298 | |> Maybe.andThen (findProperty key rootSchema)
299 | |> Maybe.map (resolve rootSchema)
300 |
301 |
302 |
303 | findProperty : String -> Schema -> Schema -> Maybe Schema
304 | findProperty name rootSchema schema =
305 | let
306 | os =
307 | whenObjectSchema schema
308 | in
309 | os
310 | |> Maybe.andThen .properties
311 | |> Maybe.andThen
312 | (\(Schemata pp) ->
313 | pp
314 | |> List.foldl
315 | (\( key, s ) res ->
316 | if res /= Nothing || key /= name then
317 | res
318 | else
319 | Just s
320 | )
321 | Nothing
322 | )
323 | |> (\r ->
324 | if r == Nothing then
325 | os
326 | |> Maybe.andThen .additionalProperties
327 | else
328 | r
329 | )
330 | |> (\r ->
331 | if r == Nothing then
332 | os
333 | |> Maybe.andThen .anyOf
334 | |> Maybe.andThen
335 | (\anyOf ->
336 | anyOf
337 | |> List.foldl
338 | (\s r ->
339 | if r == Nothing then
340 | s
341 | |> resolve rootSchema
342 | |> findProperty name rootSchema
343 | else
344 | r
345 | )
346 | Nothing
347 | )
348 | else
349 | r
350 | )
351 | |> \r ->
352 | if r == Nothing then
353 | Just blankSchema
354 | else
355 | r
356 |
357 |
358 | findDefinition : String -> Schemata -> Maybe SubSchema
359 | findDefinition ref (Schemata defs) =
360 | defs
361 | |> List.foldl
362 | (\( key, def ) res ->
363 | if res == Nothing && ("#/definitions/" ++ key) == ref then
364 | whenObjectSchema def
365 | else
366 | res
367 | )
368 | Nothing
369 |
370 |
371 | tryAllSchemas : Maybe Value -> Schema -> List Schema -> Maybe ( Type, SubSchema )
372 | tryAllSchemas actualValue rootSchema listSchemas =
373 | listSchemas
374 | |> List.map (resolve rootSchema)
375 | |> List.foldl
376 | (\schema res ->
377 | if res == Nothing then
378 | case actualValue of
379 | Just av ->
380 | case Validation.validate Ref.defaultPool av rootSchema schema of
381 | Ok _ ->
382 | schema
383 | |> whenObjectSchema
384 | |> Maybe.andThen (calcSubSchemaType actualValue rootSchema)
385 |
386 | Err _ ->
387 | Nothing
388 |
389 | Nothing ->
390 | schema
391 | |> whenObjectSchema
392 | |> Maybe.andThen (calcSubSchemaType actualValue rootSchema)
393 | else
394 | res
395 | )
396 | Nothing
397 | -}
398 |
399 |
400 | encodeDict : Dict String Value -> Value
401 | encodeDict dict =
402 | Encode.object (Dict.toList dict)
403 |
404 |
405 | decodeDict : Value -> Dict String Value
406 | decodeDict val =
407 | Decode.decodeValue (Decode.dict Decode.value) val
408 | |> Result.withDefault Dict.empty
409 |
410 |
411 | decodeList : Value -> List Value
412 | decodeList val =
413 | Decode.decodeValue (Decode.list Decode.value) val
414 | |> Result.withDefault []
415 |
416 |
417 |
418 | {-
419 | getDefinition : Maybe Schemata -> String -> Maybe Schema
420 | getDefinition defs name =
421 | defs
422 | |> Maybe.andThen
423 | (\(Schemata x) ->
424 | List.foldl
425 | (\( key, prop ) result ->
426 | if name == key then
427 | Just prop
428 | else
429 | result
430 | )
431 | Nothing
432 | x
433 | )
434 | -}
435 | {-
436 | debugSchema : String -> Maybe Schema -> Maybe Schema
437 | debugSchema msg schema =
438 | let
439 | a =
440 | case schema of
441 | Just s ->
442 | s
443 | |> Schema.encode
444 | |> Encode.encode 4
445 | |> Debug.log
446 | |> (\f -> f msg)
447 |
448 | Nothing ->
449 | Debug.log msg "Nothing"
450 | in
451 | schema
452 |
453 |
454 | debugSubSchema : String -> Maybe SubSchema -> Maybe SubSchema
455 | debugSubSchema msg schema =
456 | let
457 | a =
458 | case schema of
459 | Just s ->
460 | ObjectSchema s
461 | |> Schema.encode
462 | |> Encode.encode 4
463 | |> Debug.log
464 | |> (\f -> f msg)
465 |
466 | Nothing ->
467 | Debug.log msg "Nothing"
468 | in
469 | schema
470 | -}
471 |
472 |
473 | collectIds : Schema -> SchemataPool -> ( SchemataPool, String )
474 | collectIds schema pool =
475 | let
476 | getNs : Maybe String -> String
477 | getNs uri =
478 | case uri of
479 | Just s ->
480 | let
481 | ( isPointer, ns, _ ) =
482 | parseJsonPointer s ""
483 | in
484 | ns
485 |
486 | Nothing ->
487 | ""
488 |
489 | manageId : String -> Value -> SchemataPool -> List ( String, Value ) -> ( List ( String, Value ), ( SchemataPool, String ) )
490 | manageId ns source poolLocal obj =
491 | case List.filter (\( name, _ ) -> name == "id" || name == "$id") obj of
492 | ( _, val ) :: _ ->
493 | val
494 | |> Decode.decodeValue Decode.string
495 | |> Result.map
496 | (\id ->
497 | let
498 | ( isPointer, newNs, path ) =
499 | parseJsonPointer id ns
500 | in
501 | case Decode.decodeValue Schema.decoder source of
502 | Ok schemaLocal ->
503 | ( obj, ( Dict.insert (makeJsonPointer ( isPointer, newNs, path )) schemaLocal poolLocal, newNs ) )
504 |
505 | Err _ ->
506 | ( obj, ( poolLocal, ns ) )
507 | )
508 | |> Result.withDefault ( obj, ( poolLocal, ns ) )
509 |
510 | _ ->
511 | ( obj, ( poolLocal, ns ) )
512 |
513 | walkValue source ( poolLocal, ns ) =
514 | source
515 | |> Decode.decodeValue (Decode.keyValuePairs Decode.value)
516 | |> Result.withDefault []
517 | |> manageId ns source poolLocal
518 | |> (\( list, res ) -> List.foldl (\( key, val ) -> walkValue val) res list)
519 | in
520 | case schema of
521 | ObjectSchema { id, source } ->
522 | walkValue source ( pool, getNs id )
523 |
524 | _ ->
525 | ( pool, "" )
526 |
--------------------------------------------------------------------------------
/src/Json/Schema/Random.elm:
--------------------------------------------------------------------------------
1 | module Json.Schema.Random exposing
2 | ( value, valueAt
3 | , GeneratorSettings, defaultSettings
4 | )
5 |
6 | {-| Generate random values based on JSON Schema.
7 |
8 | Experimental module.
9 |
10 |
11 | # Generator
12 |
13 | @docs value, valueAt
14 |
15 |
16 | # Settings
17 |
18 | @docs GeneratorSettings, defaultSettings
19 |
20 | -}
21 |
22 | import Char
23 | import Dict
24 | import Json.Encode as Encode exposing (Value)
25 | import Json.Schema.Definitions
26 | exposing
27 | ( Items(..)
28 | , Schema(..)
29 | , Schemata(..)
30 | , SingleType(..)
31 | , Type(..)
32 | )
33 | import Json.Schema.Helpers exposing (collectIds)
34 | import Random exposing (Generator, Seed)
35 | import Ref exposing (defaultPool)
36 | import Util exposing (getAt, uncons)
37 |
38 |
39 | {-| Customize generator behaviour using following parameters:
40 |
41 | - optionalPropertyProbability : float from 0 to 1, which affects used while generating object with optional property, default 0.5
42 | - degradationMultiplier : used in nested objects to affect probability of optional property appearance (must have for recursive objects), default 0.2
43 | - defaultListLengthLimit : how many items in array to generate when limit is not set by a schema, default 100
44 | - defaultStringLengthLimit : how many characters in random string to generate when limit is not set by a schema, default 100
45 |
46 | -}
47 | type alias GeneratorSettings =
48 | { optionalPropertyProbability : Float
49 | , degradationMultiplier : Float
50 | , defaultListLengthLimit : Int
51 | , defaultStringLengthLimit : Int
52 | }
53 |
54 |
55 | {-| Defaults for GeneratorSettings
56 | -}
57 | defaultSettings : GeneratorSettings
58 | defaultSettings =
59 | GeneratorSettings
60 | -- optionalPropertyProbability
61 | 0.5
62 | -- degradationMultiplier
63 | 0.2
64 | -- defaultListLengthLimit
65 | 100
66 | -- defaultStringLengthLimit
67 | 100
68 |
69 |
70 | randomString : Int -> Int -> Maybe String -> Generator String
71 | randomString minLength maxLength format =
72 | case format of
73 | Just "url" ->
74 | randomBool
75 | |> Random.map
76 | (\x ->
77 | if x then
78 | "http://example.com/"
79 |
80 | else
81 | "https://github.com"
82 | )
83 |
84 | Just "uri" ->
85 | randomBool
86 | |> Random.map
87 | (\x ->
88 | if x then
89 | "http://example.com/"
90 |
91 | else
92 | "https://github.com"
93 | )
94 |
95 | Just "email" ->
96 | Random.int 1000 9999
97 | |> Random.map
98 | (\x -> "rcp" ++ (x |> String.fromInt) ++ "@receipt.to")
99 |
100 | Just "host-name" ->
101 | randomBool
102 | |> Random.map
103 | (\x ->
104 | if x then
105 | "example.com"
106 |
107 | else
108 | "github.com"
109 | )
110 |
111 | Just "date-time" ->
112 | randomBool
113 | |> Random.map (\_ -> "2018-01-01T09:00:00Z")
114 |
115 | Just "time" ->
116 | randomBool
117 | |> Random.map (\_ -> "09:00:00")
118 |
119 | Just "date" ->
120 | randomBool
121 | |> Random.map (\_ -> "2018-01-01")
122 |
123 | _ ->
124 | Random.int minLength maxLength
125 | |> Random.andThen (\a -> Random.list a lowercaseLetter)
126 | |> Random.map String.fromList
127 |
128 |
129 | lowercaseLetter : Generator Char
130 | lowercaseLetter =
131 | Random.map (\n -> Char.fromCode (n + 97)) (Random.int 0 25)
132 |
133 |
134 | randomItemFromList : ( a, List a ) -> Generator a
135 | randomItemFromList ( head, tail ) =
136 | let
137 | list =
138 | head :: tail
139 | in
140 | list
141 | |> List.length
142 | |> (+) -1
143 | |> Random.int 0
144 | |> Random.map ((\a -> getAt a list) >> Maybe.withDefault head)
145 |
146 |
147 | nullGenerator : Generator Value
148 | nullGenerator =
149 | randomBool |> Random.map (\_ -> Encode.null)
150 |
151 |
152 | upgradeSettings : GeneratorSettings -> GeneratorSettings
153 | upgradeSettings settings =
154 | { settings
155 | | optionalPropertyProbability =
156 | settings.optionalPropertyProbability * settings.degradationMultiplier
157 | }
158 |
159 |
160 | randomObject : GeneratorSettings -> String -> Ref.SchemataPool -> List ( String, Schema ) -> List String -> Generator Value
161 | randomObject settings ns pool props required =
162 | props
163 | |> List.foldl
164 | (\( k, v ) res ->
165 | if List.member k required then
166 | v
167 | |> valueGenerator (upgradeSettings settings) ns pool
168 | |> Random.andThen (\x -> res |> Random.map ((::) ( k, x )))
169 |
170 | else
171 | Random.float 0 1
172 | |> Random.andThen
173 | (\isRequired ->
174 | if isRequired < settings.optionalPropertyProbability then
175 | v
176 | |> valueGenerator (upgradeSettings settings) ns pool
177 | |> Random.andThen (\x -> res |> Random.map ((::) ( k, x )))
178 |
179 | else
180 | res
181 | )
182 | )
183 | (randomBool |> Random.map (\_ -> []))
184 | |> Random.map (List.reverse >> Encode.object)
185 |
186 |
187 | randomList : GeneratorSettings -> String -> Ref.SchemataPool -> Int -> Int -> Schema -> Generator Value
188 | randomList settings ns pool minItems maxItems schema =
189 | Random.int minItems maxItems
190 | |> Random.andThen (\a -> Random.list a (valueGenerator (upgradeSettings settings) ns pool schema))
191 | |> Random.map (Encode.list identity)
192 |
193 |
194 | {-| Random value generator.
195 |
196 | buildSchema
197 | |> withProperties
198 | [ ( "foo", buildSchema |> withType "integer" ) ]
199 | |> toSchema
200 | |> Result.withDefault blankSchema
201 | |> value defaultSettings
202 | |> (\a -> Random.step a (Random.initialSeed 2))
203 | |> (\( v, _ ) ->
204 | Expect.equal v (Encode.object [ ( "foo", Encode.int 688281600 ) ])
205 | )
206 |
207 | See tests for more examples.
208 |
209 | -}
210 | value : GeneratorSettings -> Schema -> Generator Value
211 | value settings s =
212 | let
213 | ( pool, ns ) =
214 | collectIds s defaultPool
215 | in
216 | valueGenerator settings ns pool s
217 |
218 |
219 | {-| Random value generator at path.
220 | -}
221 | valueAt : GeneratorSettings -> Schema -> String -> Generator Value
222 | valueAt settings s ref =
223 | let
224 | ( pool, ns ) =
225 | collectIds s defaultPool
226 |
227 | --|> Debug.log "pool is"
228 | a =
229 | pool
230 | |> Dict.keys
231 |
232 | -- |> Debug.log "pool keys are"
233 | in
234 | case Ref.resolveReference ns pool s ref of
235 | Just ( nsLocal, ss ) ->
236 | valueGenerator settings nsLocal pool ss
237 |
238 | Nothing ->
239 | nullGenerator
240 |
241 |
242 | resolve : String -> Ref.SchemataPool -> Schema -> Maybe ( String, Schema )
243 | resolve ns pool schema =
244 | case schema of
245 | BooleanSchema _ ->
246 | Just ( ns, schema )
247 |
248 | ObjectSchema os ->
249 | case os.ref of
250 | Just ref ->
251 | Ref.resolveReference ns pool schema ref
252 |
253 | -- |> Debug.log ("resolving this :( " ++ ref ++ " " ++ ns)
254 | Nothing ->
255 | Just ( ns, schema )
256 |
257 |
258 | randomBool : Generator Bool
259 | randomBool =
260 | Random.float 0 1
261 | |> Random.map (\x -> x > 0.5)
262 |
263 |
264 | valueGenerator : GeneratorSettings -> String -> Ref.SchemataPool -> Schema -> Generator Value
265 | valueGenerator settings ns pool schema =
266 | case schema |> resolve ns pool of
267 | Nothing ->
268 | nullGenerator
269 |
270 | Just ( nsLocal, BooleanSchema b ) ->
271 | if b then
272 | randomBool |> Random.map (\_ -> Encode.object [])
273 |
274 | else
275 | randomBool |> Random.map (\_ -> Encode.null)
276 |
277 | Just ( nsLocal, ObjectSchema os ) ->
278 | [ Maybe.andThen uncons os.examples
279 | |> Maybe.map randomItemFromList
280 | , Maybe.andThen uncons os.enum
281 | |> Maybe.map randomItemFromList
282 | , case os.type_ of
283 | SingleType NumberType ->
284 | Random.float
285 | (os.minimum |> Maybe.withDefault (toFloat Random.minInt))
286 | (os.maximum |> Maybe.withDefault (toFloat Random.maxInt))
287 | |> Random.map Encode.float
288 | |> Just
289 |
290 | SingleType IntegerType ->
291 | Random.int
292 | (os.minimum |> Maybe.map round |> Maybe.withDefault Random.minInt)
293 | (os.maximum |> Maybe.map round |> Maybe.withDefault Random.maxInt)
294 | |> Random.map Encode.int
295 | |> Just
296 |
297 | SingleType BooleanType ->
298 | randomBool
299 | |> Random.map Encode.bool
300 | |> Just
301 |
302 | SingleType StringType ->
303 | randomString
304 | (os.minLength |> Maybe.withDefault 0)
305 | (os.maxLength |> Maybe.withDefault settings.defaultStringLengthLimit)
306 | os.format
307 | |> Random.map Encode.string
308 | |> Just
309 |
310 | _ ->
311 | Nothing
312 | , os.properties
313 | |> Maybe.map (\(Schemata props) -> randomObject settings ns pool props (os.required |> Maybe.withDefault []))
314 | , case os.items of
315 | ItemDefinition schemaLocal ->
316 | randomList settings
317 | ns
318 | pool
319 | (os.minItems |> Maybe.withDefault 0)
320 | (os.maxItems |> Maybe.withDefault settings.defaultListLengthLimit)
321 | schemaLocal
322 | |> Just
323 |
324 | --NoItems ->
325 | _ ->
326 | Nothing
327 | ]
328 | |> List.foldl
329 | (\maybeGenerator res ->
330 | if res == Nothing then
331 | maybeGenerator
332 |
333 | else
334 | res
335 | )
336 | Nothing
337 | |> Maybe.withDefault nullGenerator
338 |
--------------------------------------------------------------------------------
/src/Json/Schemata.elm:
--------------------------------------------------------------------------------
1 | module Json.Schemata exposing (draft4, draft6)
2 |
3 | import Json.Decode as Decode
4 | import Json.Schema.Definitions exposing (Schema, blankSchema, decoder)
5 |
6 |
7 | draft4 : Schema
8 | draft4 =
9 | """
10 | {
11 | "id": "http://json-schema.org/draft-04/schema#",
12 | "$schema": "http://json-schema.org/draft-04/schema#",
13 | "description": "Core schema meta-schema",
14 | "definitions": {
15 | "schemaArray": {
16 | "type": "array",
17 | "minItems": 1,
18 | "items": { "$ref": "#" }
19 | },
20 | "positiveInteger": {
21 | "type": "integer",
22 | "minimum": 0
23 | },
24 | "positiveIntegerDefault0": {
25 | "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
26 | },
27 | "simpleTypes": {
28 | "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
29 | },
30 | "stringArray": {
31 | "type": "array",
32 | "items": { "type": "string" },
33 | "minItems": 1,
34 | "uniqueItems": true
35 | }
36 | },
37 | "type": "object",
38 | "properties": {
39 | "id": {
40 | "type": "string",
41 | "format": "uri",
42 | "description": "Identifier of schema"
43 | },
44 | "$schema": {
45 | "type": "string",
46 | "format": "uri",
47 | "description": "Link to a schema which validates this object"
48 | },
49 | "title": {
50 | "type": "string"
51 | },
52 | "description": {
53 | "type": "string"
54 | },
55 | "default": {},
56 | "multipleOf": {
57 | "type": "number",
58 | "minimum": 0,
59 | "exclusiveMinimum": true
60 | },
61 | "maximum": {
62 | "type": "number"
63 | },
64 | "exclusiveMaximum": {
65 | "type": "boolean",
66 | "default": false
67 | },
68 | "minimum": {
69 | "type": "number"
70 | },
71 | "exclusiveMinimum": {
72 | "type": "boolean",
73 | "default": false
74 | },
75 | "maxLength": { "$ref": "#/definitions/positiveInteger" },
76 | "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
77 | "pattern": {
78 | "type": "string",
79 | "format": "regex"
80 | },
81 | "additionalItems": {
82 | "anyOf": [
83 | { "type": "boolean" },
84 | { "$ref": "#" }
85 | ],
86 | "default": {}
87 | },
88 | "items": {
89 | "anyOf": [
90 | { "$ref": "#" },
91 | { "$ref": "#/definitions/schemaArray" }
92 | ],
93 | "default": {}
94 | },
95 | "maxItems": { "$ref": "#/definitions/positiveInteger" },
96 | "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
97 | "uniqueItems": {
98 | "type": "boolean",
99 | "default": false
100 | },
101 | "maxProperties": { "$ref": "#/definitions/positiveInteger" },
102 | "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
103 | "required": { "$ref": "#/definitions/stringArray" },
104 | "additionalProperties": {
105 | "anyOf": [
106 | { "type": "boolean" },
107 | { "$ref": "#" }
108 | ],
109 | "default": {}
110 | },
111 | "definitions": {
112 | "type": "object",
113 | "additionalProperties": { "$ref": "#" },
114 | "default": {}
115 | },
116 | "properties": {
117 | "type": "object",
118 | "additionalProperties": { "$ref": "#" },
119 | "default": {}
120 | },
121 | "patternProperties": {
122 | "type": "object",
123 | "additionalProperties": { "$ref": "#" },
124 | "default": {}
125 | },
126 | "dependencies": {
127 | "type": "object",
128 | "additionalProperties": {
129 | "anyOf": [
130 | { "$ref": "#" },
131 | { "$ref": "#/definitions/stringArray" }
132 | ]
133 | }
134 | },
135 | "enum": {
136 | "type": "array",
137 | "minItems": 1,
138 | "uniqueItems": true
139 | },
140 | "type": {
141 | "anyOf": [
142 | { "$ref": "#/definitions/simpleTypes" },
143 | {
144 | "type": "array",
145 | "items": { "$ref": "#/definitions/simpleTypes" },
146 | "minItems": 1,
147 | "uniqueItems": true
148 | }
149 | ]
150 | },
151 | "allOf": { "$ref": "#/definitions/schemaArray" },
152 | "anyOf": { "$ref": "#/definitions/schemaArray" },
153 | "oneOf": { "$ref": "#/definitions/schemaArray" },
154 | "not": { "$ref": "#" }
155 | },
156 | "dependencies": {
157 | "exclusiveMaximum": [ "maximum" ],
158 | "exclusiveMinimum": [ "minimum" ]
159 | },
160 | "default": {}
161 | }
162 | """
163 | |> decodeUnsafe
164 |
165 |
166 | draft6 : Schema
167 | draft6 =
168 | """
169 | {
170 | "$schema": "http://json-schema.org/draft-06/schema#",
171 | "$id": "http://json-schema.org/draft-06/schema#",
172 | "title": "Core schema meta-schema",
173 | "definitions": {
174 | "schemaArray": {
175 | "type": "array",
176 | "minItems": 1,
177 | "items": { "$ref": "#" }
178 | },
179 | "nonNegativeInteger": {
180 | "type": "integer",
181 | "minimum": 0
182 | },
183 | "nonNegativeIntegerDefault0": {
184 | "allOf": [
185 | { "$ref": "#/definitions/nonNegativeInteger" },
186 | { "default": 0 }
187 | ]
188 | },
189 | "simpleTypes": {
190 | "enum": [
191 | "array",
192 | "boolean",
193 | "integer",
194 | "null",
195 | "number",
196 | "object",
197 | "string"
198 | ]
199 | },
200 | "stringArray": {
201 | "type": "array",
202 | "items": { "type": "string" },
203 | "uniqueItems": true,
204 | "default": []
205 | }
206 | },
207 | "type": ["object", "boolean"],
208 | "properties": {
209 | "$id": {
210 | "type": "string",
211 | "format": "uri-reference",
212 | "description": "Identifier of schema"
213 | },
214 | "$schema": {
215 | "type": "string",
216 | "format": "uri",
217 | "description": "Link to a schema which validates this object"
218 | },
219 | "$ref": {
220 | "type": "string",
221 | "format": "uri-reference"
222 | },
223 | "title": {
224 | "type": "string"
225 | },
226 | "description": {
227 | "type": "string"
228 | },
229 | "default": {},
230 | "multipleOf": {
231 | "type": "number",
232 | "exclusiveMinimum": 0
233 | },
234 | "maximum": {
235 | "type": "number"
236 | },
237 | "exclusiveMaximum": {
238 | "type": "number"
239 | },
240 | "minimum": {
241 | "type": "number"
242 | },
243 | "exclusiveMinimum": {
244 | "type": "number"
245 | },
246 | "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
247 | "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
248 | "pattern": {
249 | "type": "string",
250 | "format": "regex"
251 | },
252 | "additionalItems": { "$ref": "#" },
253 | "items": {
254 | "anyOf": [
255 | { "$ref": "#" },
256 | { "$ref": "#/definitions/schemaArray" }
257 | ],
258 | "default": {}
259 | },
260 | "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
261 | "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
262 | "uniqueItems": {
263 | "type": "boolean",
264 | "default": false
265 | },
266 | "contains": { "$ref": "#" },
267 | "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
268 | "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
269 | "required": { "$ref": "#/definitions/stringArray" },
270 | "additionalProperties": { "$ref": "#" },
271 | "definitions": {
272 | "type": "object",
273 | "additionalProperties": { "$ref": "#" },
274 | "default": {}
275 | },
276 | "properties": {
277 | "type": "object",
278 | "additionalProperties": { "$ref": "#" },
279 | "default": {}
280 | },
281 | "patternProperties": {
282 | "type": "object",
283 | "additionalProperties": { "$ref": "#" },
284 | "default": {}
285 | },
286 | "dependencies": {
287 | "type": "object",
288 | "additionalProperties": {
289 | "anyOf": [
290 | { "$ref": "#" },
291 | { "$ref": "#/definitions/stringArray" }
292 | ]
293 | }
294 | },
295 | "propertyNames": { "$ref": "#" },
296 | "const": {},
297 | "enum": {
298 | "type": "array",
299 | "minItems": 1,
300 | "uniqueItems": true
301 | },
302 | "type": {
303 | "anyOf": [
304 | { "$ref": "#/definitions/simpleTypes" },
305 | {
306 | "type": "array",
307 | "items": { "$ref": "#/definitions/simpleTypes" },
308 | "minItems": 1,
309 | "uniqueItems": true
310 | }
311 | ]
312 | },
313 | "format": { "type": "string" },
314 | "allOf": { "$ref": "#/definitions/schemaArray" },
315 | "anyOf": { "$ref": "#/definitions/schemaArray" },
316 | "oneOf": { "$ref": "#/definitions/schemaArray" },
317 | "not": { "$ref": "#" }
318 | },
319 | "default": {}
320 | }
321 | """
322 | |> decodeUnsafe
323 |
324 |
325 | decodeUnsafe : String -> Schema
326 | decodeUnsafe =
327 | Decode.decodeString decoder >> Result.withDefault blankSchema
328 |
--------------------------------------------------------------------------------
/src/Ref.elm:
--------------------------------------------------------------------------------
1 | module Ref exposing (SchemataPool, defaultPool, parseJsonPointer, resolveReference)
2 |
3 | import Dict exposing (Dict)
4 | import Json.Decode as Decode
5 | import Json.Schema.Definitions exposing (Schema(..), Schemata(..), SubSchema, decoder)
6 | import Json.Schemata as Schemata
7 | import Regex exposing (fromString)
8 |
9 |
10 | {-| Pool of schemata used in refs lookup by id
11 | -}
12 | type alias SchemataPool =
13 | Dict String Schema
14 |
15 |
16 | {-| Default schemata pool containing schemata draft-04 and draft-06
17 | -}
18 | defaultPool : SchemataPool
19 | defaultPool =
20 | Dict.empty
21 | |> Dict.insert "http://json-schema.org/draft-06/schema" Schemata.draft6
22 | |> Dict.insert "http://json-schema.org/draft-06/schema#" Schemata.draft6
23 | |> Dict.insert "http://json-schema.org/draft-04/schema" Schemata.draft4
24 |
25 |
26 | parseJsonPointer : String -> String -> ( Bool, String, List String )
27 | parseJsonPointer pointer currentNamespace =
28 | let
29 | merge base relative =
30 | if isAbsolute base && hasFragments base then
31 | base |> Regex.replace lastFragment (\_ -> "/" ++ relative)
32 |
33 | else
34 | relative
35 |
36 | hasFragments =
37 | Regex.contains lastFragment
38 |
39 | isAbsolute =
40 | Regex.contains absoluteUri
41 |
42 | ( ns, hash ) =
43 | case String.split "#" pointer of
44 | [] ->
45 | ( currentNamespace, "" )
46 |
47 | a :: [] ->
48 | if a == "" then
49 | ( currentNamespace, "" )
50 |
51 | else if isAbsolute a then
52 | ( a, "" )
53 |
54 | else
55 | ( merge currentNamespace a, "" )
56 |
57 | a :: b :: _ ->
58 | if a == "" then
59 | ( currentNamespace, b )
60 | --|> Debug.log "case 3.1"
61 |
62 | else if isAbsolute a then
63 | ( a, b )
64 | --|> Debug.log "case 3.2"
65 |
66 | else
67 | ( merge currentNamespace a, b )
68 |
69 | --|> Debug.log "case 3.4"
70 | isPointer =
71 | hasFragments hash
72 | in
73 | ( isPointer
74 | , ns
75 | , if isPointer then
76 | hash
77 | |> String.split "/"
78 | |> List.drop 1
79 | |> List.map unescapeJsonPathFragment
80 |
81 | else if hash /= "" then
82 | [ hash ]
83 |
84 | else
85 | []
86 | )
87 |
88 |
89 | absoluteUri : Regex.Regex
90 | absoluteUri =
91 | fromString "\\/\\/|^\\/" |> Maybe.withDefault Regex.never
92 |
93 |
94 | lastFragment : Regex.Regex
95 | lastFragment =
96 | fromString "\\/[^\\/]*$" |> Maybe.withDefault Regex.never
97 |
98 |
99 | tilde : Regex.Regex
100 | tilde =
101 | fromString "~0" |> Maybe.withDefault Regex.never
102 |
103 |
104 | slash : Regex.Regex
105 | slash =
106 | fromString "~1" |> Maybe.withDefault Regex.never
107 |
108 |
109 | percent : Regex.Regex
110 | percent =
111 | fromString "%25" |> Maybe.withDefault Regex.never
112 |
113 |
114 | unescapeJsonPathFragment : String -> String
115 | unescapeJsonPathFragment s =
116 | s
117 | |> Regex.replace tilde (\_ -> "~")
118 | |> Regex.replace slash (\_ -> "/")
119 | |> Regex.replace percent (\_ -> "%")
120 |
121 |
122 | makeJsonPointer : ( Bool, String, List String ) -> String
123 | makeJsonPointer ( isPointer, ns, path ) =
124 | if isPointer then
125 | ("#" :: path)
126 | |> String.join "/"
127 | |> (++) ns
128 |
129 | else if List.isEmpty path then
130 | ns
131 | -- |> Debug.log "path was empty"
132 |
133 | else
134 | path
135 | |> String.join "/"
136 | |> (++) (ns ++ "#")
137 |
138 |
139 | removeTrailingSlash : String -> String
140 | removeTrailingSlash s =
141 | if String.endsWith "#" s then
142 | String.dropRight 1 s
143 |
144 | else
145 | s
146 |
147 |
148 | resolveReference : String -> SchemataPool -> Schema -> String -> Maybe ( String, Schema )
149 | resolveReference ns pool schema ref =
150 | let
151 | rootNs =
152 | schema
153 | |> whenObjectSchema
154 | |> Maybe.andThen .id
155 | |> Maybe.map removeTrailingSlash
156 | |> Maybe.withDefault ns
157 |
158 | resolveRecursively namespace limit localSchema localRef =
159 | let
160 | ( isPointer, localNs, path ) =
161 | parseJsonPointer {- Debug.log "resolving ref" -} localRef namespace
162 |
163 | --|> Debug.log "new json pointer (parsed)"
164 | --|> Debug.log ("parse " ++ (toString localRef) ++ " within ns " ++ (toString namespace))
165 | newJsonPointer =
166 | makeJsonPointer ( isPointer, localNs, path )
167 |
168 | --|> Debug.log "new json pointer (combined)"
169 | a =
170 | pool
171 | |> Dict.keys
172 |
173 | --|> Debug.log "pool keys"
174 | in
175 | if limit > 0 then
176 | if isPointer then
177 | (if localNs == "" then
178 | Just localSchema
179 |
180 | else
181 | pool
182 | |> Dict.get localNs
183 | )
184 | |> Maybe.andThen whenObjectSchema
185 | |> Maybe.andThen
186 | (\os ->
187 | os.source
188 | |> Decode.decodeValue (Decode.at path decoder)
189 | |> Result.toMaybe
190 | |> Maybe.andThen
191 | (\def ->
192 | case def of
193 | ObjectSchema oss ->
194 | case oss.ref of
195 | Just r ->
196 | resolveRecursively localNs (limit - 1) localSchema r
197 |
198 | Nothing ->
199 | Just ( localNs, def )
200 |
201 | BooleanSchema _ ->
202 | Just ( localNs, def )
203 | )
204 | )
205 |
206 | else if newJsonPointer == "" then
207 | Just ( "", localSchema )
208 |
209 | else
210 | pool
211 | |> Dict.get newJsonPointer
212 | |> Maybe.map (\x -> ( localNs, x ))
213 |
214 | else
215 | Just ( localNs, localSchema )
216 | in
217 | resolveRecursively rootNs 10 schema ref
218 |
219 |
220 |
221 | --|> Debug.log ("resolution result for " ++ ref ++ " " ++ rootNs)
222 |
223 |
224 | whenObjectSchema : Schema -> Maybe SubSchema
225 | whenObjectSchema schema =
226 | case schema of
227 | ObjectSchema os ->
228 | Just os
229 |
230 | BooleanSchema _ ->
231 | Nothing
232 |
--------------------------------------------------------------------------------
/src/Util.elm:
--------------------------------------------------------------------------------
1 | module Util exposing (foldResults, getAt, indexOfFirstDuplicate, isInt, isUnique, resultToDecoder, uncons)
2 |
3 | import Json.Decode exposing (Decoder, fail, succeed)
4 |
5 |
6 | foldResults : List (Result x y) -> Result x (List y)
7 | foldResults results =
8 | results
9 | |> List.foldl
10 | (\t -> Result.andThen (\r -> t |> Result.map (\a -> (::) a r)))
11 | (Ok [])
12 | |> Result.map List.reverse
13 |
14 |
15 | resultToDecoder : Result String a -> Decoder a
16 | resultToDecoder res =
17 | case res of
18 | Ok a ->
19 | succeed a
20 |
21 | Err e ->
22 | fail e
23 |
24 |
25 | isInt : Float -> Bool
26 | isInt x =
27 | x == (round >> toFloat) x
28 |
29 |
30 | uncons : List a -> Maybe ( a, List a )
31 | uncons l =
32 | case l of
33 | head :: tail ->
34 | Just ( head, tail )
35 |
36 | _ ->
37 | Nothing
38 |
39 |
40 | getAt : Int -> List a -> Maybe a
41 | getAt index =
42 | List.drop index >> List.head
43 |
44 |
45 | isUnique : List comparable -> Bool
46 | isUnique list =
47 | indexOfFirstDuplicate list == -1
48 |
49 |
50 | indexOfFirstDuplicate : List comparable -> Int
51 | indexOfFirstDuplicate list =
52 | list
53 | |> List.foldl
54 | (\x ( index, res, sublist ) ->
55 | ( index + 1
56 | , if res > -1 then
57 | res
58 |
59 | else if List.member x sublist then
60 | index
61 |
62 | else
63 | -1
64 | , sublist |> List.drop 1
65 | )
66 | )
67 | ( 0, -1, list |> List.drop 1 )
68 | |> (\( _, r, _ ) -> r)
69 |
--------------------------------------------------------------------------------
/tests/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | repl-temp-*
3 | elm-stuff
4 | dist
5 | .DS_Store
6 | npm-debug.log
7 |
--------------------------------------------------------------------------------
/tests/Decoding.elm:
--------------------------------------------------------------------------------
1 | module Decoding exposing (all)
2 |
3 | import Test exposing (Test, describe, test, only, skip)
4 | import Json.Schema.Builder
5 | exposing
6 | ( SchemaBuilder
7 | , buildSchema
8 | , boolSchema
9 | , toSchema
10 | , withType
11 | , withNullableType
12 | , withUnionType
13 | , withContains
14 | , withDefinitions
15 | , withItems
16 | , withItem
17 | , withAdditionalItems
18 | , withProperties
19 | , withPatternProperties
20 | , withAdditionalProperties
21 | , withSchemaDependency
22 | , withPropNamesDependency
23 | , withPropertyNames
24 | , withAllOf
25 | , withAnyOf
26 | , withOneOf
27 | , withTitle
28 | , withNot
29 | )
30 | import Json.Schema.Definitions as Schema exposing (Schema, decoder)
31 | import Expect
32 | import Json.Encode as Encode exposing (Value)
33 | import Json.Decode as Decode exposing (decodeValue)
34 |
35 |
36 | all : Test
37 | all =
38 | describe "decoding of JSON Schema"
39 | [ test "type=integer" <|
40 | \() ->
41 | [ ( "type", Encode.string "integer" ) ]
42 | |> decodesInto
43 | (buildSchema
44 | |> withType "integer"
45 | )
46 | , test "type=number" <|
47 | \() ->
48 | [ ( "type", Encode.string "number" ) ]
49 | |> decodesInto
50 | (buildSchema
51 | |> withType "number"
52 | )
53 | , test "type=string" <|
54 | \() ->
55 | [ ( "type", Encode.string "string" ) ]
56 | |> decodesInto
57 | (buildSchema
58 | |> withType "string"
59 | )
60 | , test "type=object" <|
61 | \() ->
62 | [ ( "type", Encode.string "object" ) ]
63 | |> decodesInto
64 | (buildSchema
65 | |> withType "object"
66 | )
67 | , test "type=array" <|
68 | \() ->
69 | [ ( "type", Encode.string "array" ) ]
70 | |> decodesInto
71 | (buildSchema
72 | |> withType "array"
73 | )
74 | , test "type=null" <|
75 | \() ->
76 | [ ( "type", Encode.string "null" ) ]
77 | |> decodesInto
78 | (buildSchema
79 | |> withType "null"
80 | )
81 | , test "type=[null,integer]" <|
82 | \() ->
83 | [ ( "type"
84 | , Encode.list
85 | [ Encode.string "null"
86 | , Encode.string "integer"
87 | ]
88 | )
89 | ]
90 | |> decodesInto
91 | (buildSchema
92 | |> withNullableType "integer"
93 | )
94 | , test "type=[string,integer]" <|
95 | \() ->
96 | [ ( "type"
97 | , Encode.list
98 | [ Encode.string "integer"
99 | , Encode.string "string"
100 | ]
101 | )
102 | ]
103 | |> decodesInto
104 | (buildSchema
105 | |> withUnionType [ "string", "integer" ]
106 | )
107 | , test "title=smth" <|
108 | \() ->
109 | [ ( "title", Encode.string "smth" ) ]
110 | |> decodesInto
111 | (buildSchema
112 | |> withTitle "smth"
113 | )
114 | , test "definitions={foo=blankSchema}" <|
115 | \() ->
116 | [ ( "definitions", Encode.object [ ( "foo", Encode.object [] ) ] ) ]
117 | |> decodesInto
118 | (buildSchema
119 | |> withDefinitions [ ( "foo", buildSchema ) ]
120 | )
121 | , test "items=[blankSchema]" <|
122 | \() ->
123 | [ ( "items", Encode.list <| [ Encode.object [] ] ) ]
124 | |> decodesInto
125 | (buildSchema
126 | |> withItems [ buildSchema ]
127 | )
128 | , test "items=blankSchema" <|
129 | \() ->
130 | [ ( "items", Encode.object [] ) ]
131 | |> decodesInto
132 | (buildSchema
133 | |> withItem buildSchema
134 | )
135 | , test "additionalItems=blankSchema" <|
136 | \() ->
137 | [ ( "additionalItems", Encode.object [] ) ]
138 | |> decodesInto
139 | (buildSchema
140 | |> withAdditionalItems buildSchema
141 | )
142 | , test "contains={}" <|
143 | \() ->
144 | [ ( "contains", Encode.object [] ) ]
145 | |> decodesInto
146 | (buildSchema
147 | |> withContains buildSchema
148 | )
149 | , test "properties={foo=blankSchema}" <|
150 | \() ->
151 | [ ( "properties", Encode.object [ ( "foo", Encode.object [] ) ] ) ]
152 | |> decodesInto
153 | (buildSchema
154 | |> withProperties [ ( "foo", buildSchema ) ]
155 | )
156 | , test "patternProperties={foo=blankSchema}" <|
157 | \() ->
158 | [ ( "patternProperties", Encode.object [ ( "foo", Encode.object [] ) ] ) ]
159 | |> decodesInto
160 | (buildSchema
161 | |> withPatternProperties [ ( "foo", buildSchema ) ]
162 | )
163 | , test "additionalProperties=blankSchema" <|
164 | \() ->
165 | [ ( "additionalProperties", Encode.object [] ) ]
166 | |> decodesInto
167 | (buildSchema
168 | |> withAdditionalProperties buildSchema
169 | )
170 | , test "dependencies={foo=blankSchema}" <|
171 | \() ->
172 | [ ( "dependencies", Encode.object [ ( "foo", Encode.object [] ) ] ) ]
173 | |> decodesInto
174 | (buildSchema
175 | |> withSchemaDependency "foo" buildSchema
176 | )
177 | , test "dependencies={foo=[bar]}" <|
178 | \() ->
179 | [ ( "dependencies", Encode.object [ ( "foo", Encode.list [ Encode.string "bar" ] ) ] ) ]
180 | |> decodesInto
181 | (buildSchema
182 | |> withPropNamesDependency "foo" [ "bar" ]
183 | )
184 | , test "propertyNames={}" <|
185 | \() ->
186 | [ ( "propertyNames", Encode.object [ ( "type", Encode.string "string" ) ] ) ]
187 | |> decodesInto
188 | (buildSchema
189 | |> withPropertyNames (buildSchema |> withType "string")
190 | )
191 | , test "enum=[]" <|
192 | \() ->
193 | [ ( "enum", Encode.list [] ) ]
194 | |> decodeSchema
195 | |> Expect.err
196 | , test "allOf=[]" <|
197 | \() ->
198 | [ ( "allOf", Encode.list [] ) ]
199 | |> decodeSchema
200 | |> Expect.err
201 | , test "allOf=[blankSchema]" <|
202 | \() ->
203 | [ ( "allOf", Encode.list [ Encode.object [] ] ) ]
204 | |> decodesInto
205 | (buildSchema
206 | |> withAllOf [ buildSchema ]
207 | )
208 | , test "oneOf=[blankSchema]" <|
209 | \() ->
210 | [ ( "oneOf", Encode.list [ Encode.object [] ] ) ]
211 | |> decodesInto
212 | (buildSchema
213 | |> withOneOf [ buildSchema ]
214 | )
215 | , test "anyOf=[blankSchema]" <|
216 | \() ->
217 | [ ( "anyOf", Encode.list [ Encode.object [] ] ) ]
218 | |> decodesInto
219 | (buildSchema
220 | |> withAnyOf [ buildSchema ]
221 | )
222 | , describe "boolean schema"
223 | [ test "true always validates any value" <|
224 | \() ->
225 | Encode.bool True
226 | |> decodeValue Schema.decoder
227 | |> Expect.equal (boolSchema True |> toSchema)
228 | , test "false always fails validation" <|
229 | \() ->
230 | Encode.bool False
231 | |> decodeValue Schema.decoder
232 | |> Expect.equal (boolSchema False |> toSchema)
233 | ]
234 | ]
235 |
236 |
237 | shouldResultWithSchema : Schema -> Result x Schema -> Expect.Expectation
238 | shouldResultWithSchema s =
239 | s
240 | |> Ok
241 | |> Expect.equal
242 |
243 |
244 | decodeSchema : List ( String, Value ) -> Result String Schema
245 | decodeSchema list =
246 | list
247 | |> Encode.object
248 | |> decodeValue Schema.decoder
249 |
250 |
251 | decodesInto : SchemaBuilder -> List ( String, Value ) -> Expect.Expectation
252 | decodesInto sb list =
253 | list
254 | |> Encode.object
255 | |> decodeValue Schema.decoder
256 | |> Expect.equal (sb |> toSchema)
257 |
--------------------------------------------------------------------------------
/tests/Generator.elm:
--------------------------------------------------------------------------------
1 | module Generator exposing (all)
2 |
3 | import Test exposing (Test, describe, test, only)
4 | import Json.Schema.Builder
5 | exposing
6 | ( SchemaBuilder
7 | , buildSchema
8 | , boolSchema
9 | , toSchema
10 | , withType
11 | , withNullableType
12 | , withUnionType
13 | , withMinimum
14 | , withMaximum
15 | , withContains
16 | , withDefinitions
17 | , withItems
18 | , withItem
19 | , withMaxItems
20 | , withMinItems
21 | , withAdditionalItems
22 | , withProperties
23 | , withPatternProperties
24 | , withAdditionalProperties
25 | , withSchemaDependency
26 | , withPropNamesDependency
27 | , withPropertyNames
28 | , withAllOf
29 | , withAnyOf
30 | , withOneOf
31 | , withTitle
32 | , withNot
33 | , withExamples
34 | , withEnum
35 | )
36 | import Json.Schema.Definitions as Schema exposing (Schema, decoder, blankSchema)
37 | import Json.Schema.Random as JSR
38 | import Expect
39 | import Json.Encode as Encode exposing (Value)
40 | import Random exposing (initialSeed, step)
41 |
42 |
43 | all : Test
44 | all =
45 | describe "random value generator"
46 | [ describe "generate by example"
47 | [ test "enough examples" <|
48 | \() ->
49 | buildSchema
50 | |> withExamples
51 | [ Encode.string "dime"
52 | , Encode.int 2
53 | , Encode.int 3
54 | , Encode.int 4
55 | , Encode.int 5
56 | ]
57 | |> toSchema
58 | |> Result.withDefault (blankSchema)
59 | |> JSR.value JSR.defaultSettings
60 | |> flip step (initialSeed 178)
61 | |> (\( v, _ ) -> Expect.equal v (Encode.string "dime"))
62 | , test "not enough examples" <|
63 | \() ->
64 | buildSchema
65 | |> withExamples []
66 | |> toSchema
67 | |> Result.withDefault (blankSchema)
68 | |> JSR.value JSR.defaultSettings
69 | |> flip step (initialSeed 178)
70 | |> (\( v, _ ) -> Expect.equal v Encode.null)
71 | ]
72 | , describe "generate by enum"
73 | [ test "enough enums" <|
74 | \() ->
75 | buildSchema
76 | |> withEnum
77 | [ Encode.string "dime"
78 | , Encode.int 2
79 | , Encode.int 3
80 | , Encode.int 4
81 | , Encode.int 5
82 | ]
83 | |> toSchema
84 | |> Result.withDefault (blankSchema)
85 | |> JSR.value JSR.defaultSettings
86 | |> flip step (initialSeed 178)
87 | |> (\( v, _ ) -> Expect.equal v (Encode.string "dime"))
88 | , test "not enough examples" <|
89 | \() ->
90 | buildSchema
91 | |> withEnum []
92 | |> toSchema
93 | |> Result.withDefault (blankSchema)
94 | |> JSR.value JSR.defaultSettings
95 | |> flip step (initialSeed 178)
96 | |> (\( v, _ ) -> Expect.equal v Encode.null)
97 | ]
98 | , describe "random object generation"
99 | [ test "object with required fields" <|
100 | \() ->
101 | buildSchema
102 | |> withProperties
103 | [ ( "foo", buildSchema |> withType "integer" ) ]
104 | |> toSchema
105 | |> Result.withDefault (blankSchema)
106 | |> JSR.value JSR.defaultSettings
107 | |> flip step (initialSeed 2)
108 | |> (\( v, _ ) -> Expect.equal v (Encode.object [ ( "foo", Encode.int 688281600 ) ]))
109 | ]
110 | , describe "random array generation"
111 | [ test "list of similar items" <|
112 | \() ->
113 | buildSchema
114 | |> withItem (buildSchema |> withType "integer" |> withMinimum 0 |> withMaximum 10)
115 | |> withMaxItems 10
116 | |> toSchema
117 | |> Result.withDefault (blankSchema)
118 | |> JSR.value JSR.defaultSettings
119 | |> flip step (initialSeed 1)
120 | |> (\( v, _ ) ->
121 | [ 3, 9, 7 ]
122 | |> List.map Encode.int
123 | |> Encode.list
124 | |> Expect.equal v
125 | )
126 | ]
127 | ]
128 |
--------------------------------------------------------------------------------
/tests/Type.elm.rm:
--------------------------------------------------------------------------------
1 | module Type exposing (all)
2 |
3 | import Json.Schema as JS exposing (empty)
4 | import Test exposing (Test, describe, test, only)
5 | import Data.Schema
6 | exposing
7 | ( Validation
8 | ( IntegerSchema
9 | , FloatSchema
10 | , StringSchema
11 | , Undefined
12 | )
13 | , Meta
14 | , Schema
15 | )
16 | import Data.NumberValidations exposing (NumberValidations)
17 | import Data.StringValidations exposing (StringValidations)
18 | import Expect
19 | import Json.Encode as Encode exposing (Value)
20 |
21 |
22 | all : Test
23 | all = describe "deprecated" []
24 |
25 |
26 | deprecated : Test
27 | deprecated =
28 | describe "schema.type"
29 | [ test "integer schema" <|
30 | \() ->
31 | [ ( "type", Encode.string "integer" ) ]
32 | |> decodeSchema
33 | |> shouldResultWithSchema
34 | (Schema
35 | withEmptyMeta
36 | (IntegerSchema
37 | (NumberValidations
38 | Nothing
39 | Nothing
40 | Nothing
41 | Nothing
42 | Nothing
43 | )
44 | )
45 | Nothing
46 | )
47 | , test "integer schema with validations" <|
48 | \() ->
49 | [ ( "type", Encode.string "integer" )
50 | , ( "multipleOf", Encode.float 2.0 )
51 | , ( "maximum", Encode.float 2.0 )
52 | , ( "exclusiveMaximum", Encode.float 2.0 )
53 | , ( "minimum", Encode.float 1.0 )
54 | , ( "exclusiveMinimum", Encode.float 1.0 )
55 | ]
56 | |> decodeSchema
57 | |> shouldResultWithSchema
58 | (Schema
59 | withEmptyMeta
60 | (IntegerSchema
61 | (NumberValidations
62 | (Just 2.0)
63 | (Just 2.0)
64 | (Just 2.0)
65 | (Just 1.0)
66 | (Just 1.0)
67 | )
68 | )
69 | Nothing
70 | )
71 | , test "number schema" <|
72 | \() ->
73 | [ ( "type", Encode.string "number" ) ]
74 | |> decodeSchema
75 | |> shouldResultWithSchema
76 | (Schema
77 | withEmptyMeta
78 | (FloatSchema
79 | (NumberValidations
80 | Nothing
81 | Nothing
82 | Nothing
83 | Nothing
84 | Nothing
85 | )
86 | )
87 | Nothing
88 | )
89 | , test "string schema" <|
90 | \() ->
91 | [ ( "type", Encode.string "string" ) ]
92 | |> decodeSchema
93 | |> shouldResultWithSchema
94 | (Schema
95 | withEmptyMeta
96 | (StringSchema
97 | (StringValidations
98 | Nothing
99 | Nothing
100 | Nothing
101 | )
102 | )
103 | Nothing
104 | )
105 | , test "undefined schema" <|
106 | \() ->
107 | []
108 | |> decodeSchema
109 | |> shouldResultWithSchema
110 | (Schema
111 | withEmptyMeta
112 | (Undefined
113 | (NumberValidations
114 | Nothing
115 | Nothing
116 | Nothing
117 | Nothing
118 | Nothing
119 | )
120 | (StringValidations
121 | Nothing
122 | Nothing
123 | Nothing
124 | )
125 | )
126 | Nothing
127 | )
128 | , test "list of one" <|
129 | \() ->
130 | [ ( "type"
131 | , Encode.list [ Encode.string "string" ]
132 | )
133 | ]
134 | |> decodeSchema
135 | |> shouldResultWithSchema
136 | (Schema
137 | withEmptyMeta
138 | (StringSchema
139 | (StringValidations
140 | Nothing
141 | Nothing
142 | Nothing
143 | )
144 | )
145 | Nothing
146 | )
147 | , test "nullable type" <|
148 | \() ->
149 | [ ( "type"
150 | , [ "string", "null" ]
151 | |> List.map Encode.string
152 | |> Encode.list
153 | )
154 | ]
155 | |> decodeSchema
156 | |> shouldResultWithSchema
157 | (Schema
158 | withEmptyMeta
159 | (Undefined
160 | (NumberValidations
161 | Nothing
162 | Nothing
163 | Nothing
164 | Nothing
165 | Nothing
166 | )
167 | (StringValidations
168 | Nothing
169 | Nothing
170 | Nothing
171 | )
172 | )
173 | Nothing
174 | )
175 | ]
176 |
177 | withEmptyMeta : Meta
178 | withEmptyMeta =
179 | Meta
180 | Nothing
181 | Nothing
182 | Nothing
183 | Nothing
184 |
185 | shouldResultWithSchema : Schema -> Result x Schema -> Expect.Expectation
186 | shouldResultWithSchema s =
187 | s
188 | |> Ok
189 | |> Expect.equal
190 |
191 |
192 | decodeSchema : List ( String, Value ) -> Result String Schema
193 | decodeSchema list =
194 | list
195 | |> Encode.object
196 | |> JS.fromValue
197 |
--------------------------------------------------------------------------------
/tests/Validations.elm:
--------------------------------------------------------------------------------
1 | module Validations exposing (all)
2 |
3 | import Json.Schema.Builder as JSB
4 | exposing
5 | ( buildSchema
6 | , boolSchema
7 | , withItem
8 | , withItems
9 | , withAdditionalItems
10 | , withContains
11 | , withProperties
12 | , withPatternProperties
13 | , withAdditionalProperties
14 | , withSchemaDependency
15 | , withPropNamesDependency
16 | , withPropertyNames
17 | , withType
18 | , withNullableType
19 | , withUnionType
20 | , withAllOf
21 | , withAnyOf
22 | , withOneOf
23 | , withMultipleOf
24 | , withMaximum
25 | , withMinimum
26 | , withExclusiveMaximum
27 | , withExclusiveMinimum
28 | , withPattern
29 | , withEnum
30 | , withRequired
31 | , withMaxLength
32 | , withMinLength
33 | , withMaxProperties
34 | , withMinProperties
35 | , withMaxItems
36 | , withMinItems
37 | , withUniqueItems
38 | , withConst
39 | , validate
40 | )
41 | import Json.Encode as Encode exposing (int)
42 | import Json.Decode as Decode exposing (decodeValue)
43 | import Json.Schema.Validation as Validation exposing (Error, defaultOptions, JsonPointer, ValidationError(..))
44 | import Json.Schema.Definitions exposing (blankSchema)
45 | import Test exposing (Test, describe, test, only)
46 | import Ref
47 | import Expect
48 |
49 |
50 | all : Test
51 | all =
52 | describe "validations"
53 | [ describe "multipleOf"
54 | [ test "success with int" <|
55 | \() ->
56 | buildSchema
57 | |> withMultipleOf 2
58 | |> JSB.validate defaultOptions (Encode.int 4)
59 | |> expectOk
60 | , test "success with float" <|
61 | \() ->
62 | buildSchema
63 | |> withMultipleOf 2.1
64 | |> JSB.validate defaultOptions (Encode.float 4.2)
65 | |> expectOk
66 | , test "success with periodic float" <|
67 | \() ->
68 | buildSchema
69 | |> withMultipleOf (1 / 3)
70 | |> JSB.validate defaultOptions (Encode.float (2 / 3))
71 | |> expectOk
72 | , test "failure" <|
73 | \() ->
74 | buildSchema
75 | |> withMultipleOf 3
76 | |> JSB.validate defaultOptions (Encode.float (2 / 7))
77 | |> Expect.equal (Err [ error [] <| MultipleOf 3 (2 / 7) ])
78 | ]
79 | , describe "maximum"
80 | [ test "success" <|
81 | \() ->
82 | buildSchema
83 | |> withMaximum 2
84 | |> JSB.validate defaultOptions (Encode.int 2)
85 | |> expectOk
86 | , test "failure" <|
87 | \() ->
88 | buildSchema
89 | |> withMaximum 2
90 | |> JSB.validate defaultOptions (Encode.float 2.1)
91 | |> Expect.equal (Err [ error [] <| Maximum 2.0 2.1 ])
92 | ]
93 | , describe "minimum"
94 | [ test "success" <|
95 | \() ->
96 | buildSchema
97 | |> withMinimum 2
98 | |> JSB.validate defaultOptions (Encode.int 2)
99 | |> expectOk
100 | , test "failure" <|
101 | \() ->
102 | buildSchema
103 | |> withMinimum 2
104 | |> JSB.validate defaultOptions (Encode.float 1.9)
105 | |> Expect.equal (Err [ error [] <| Minimum 2.0 1.9 ])
106 | ]
107 | , describe "exclusiveMaximum"
108 | [ test "success" <|
109 | \() ->
110 | buildSchema
111 | |> withExclusiveMaximum 2
112 | |> JSB.validate defaultOptions (Encode.float 1.9)
113 | |> expectOk
114 | , test "failure" <|
115 | \() ->
116 | buildSchema
117 | |> withExclusiveMaximum 2
118 | |> JSB.validate defaultOptions (Encode.float 2)
119 | |> Expect.equal (Err [ error [] <| ExclusiveMaximum 2 2 ])
120 | ]
121 | , describe "exclusiveMinimum"
122 | [ test "success" <|
123 | \() ->
124 | buildSchema
125 | |> withExclusiveMinimum 2
126 | |> JSB.validate defaultOptions (Encode.float 2.1)
127 | |> expectOk
128 | , test "failure" <|
129 | \() ->
130 | buildSchema
131 | |> withExclusiveMinimum 2
132 | |> JSB.validate defaultOptions (Encode.float 2)
133 | |> Expect.equal (Err [ error [] <| ExclusiveMinimum 2 2 ])
134 | ]
135 | , describe "maxLength"
136 | [ test "success" <|
137 | \() ->
138 | buildSchema
139 | |> withMaxLength 3
140 | |> JSB.validate defaultOptions (Encode.string "foo")
141 | |> expectOk
142 | , test "success for non-strings" <|
143 | \() ->
144 | buildSchema
145 | |> withMaxLength 3
146 | |> JSB.validate defaultOptions (Encode.int 10000)
147 | |> expectOk
148 | , test "failure" <|
149 | \() ->
150 | buildSchema
151 | |> withMaxLength 2
152 | |> validate defaultOptions (Encode.string "foo")
153 | |> Expect.equal (Err [ error [] <| MaxLength 2 3 ])
154 | ]
155 | , describe "minLength"
156 | [ test "success" <|
157 | \() ->
158 | buildSchema
159 | |> withMinLength 3
160 | |> validate defaultOptions (Encode.string "foo")
161 | |> expectOk
162 | , test "failure" <|
163 | \() ->
164 | buildSchema
165 | |> withMinLength 4
166 | |> validate defaultOptions (Encode.string "foo")
167 | |> Expect.equal (Err [ error [] <| MinLength 4 3 ])
168 | ]
169 | , describe "pattern"
170 | [ test "success" <|
171 | \() ->
172 | buildSchema
173 | |> withPattern "o{2}"
174 | |> JSB.validate defaultOptions (Encode.string "foo")
175 | |> expectOk
176 | , test "failure" <|
177 | \() ->
178 | buildSchema
179 | |> withPattern "o{3}"
180 | |> JSB.validate defaultOptions (Encode.string "foo")
181 | |> Expect.equal (Err [ error [] <| Pattern "o{3}" "foo" ])
182 | ]
183 | , describe "items: schema"
184 | [ test "success" <|
185 | \() ->
186 | buildSchema
187 | |> withItem (buildSchema |> withMaximum 10)
188 | |> JSB.validate defaultOptions (Encode.list [ int 1 ])
189 | |> expectOk
190 | , test "failure" <|
191 | \() ->
192 | buildSchema
193 | |> withItem (buildSchema |> withMaximum 10)
194 | |> JSB.validate defaultOptions (Encode.list [ int 1, int 11 ])
195 | |> Expect.equal (Err [ error [ "1" ] <| Maximum 10 11 ])
196 | ]
197 | , describe "items: array of schema"
198 | [ test "success" <|
199 | \() ->
200 | buildSchema
201 | |> withItems
202 | [ buildSchema
203 | |> withMaximum 10
204 | , buildSchema
205 | |> withMaximum 100
206 | ]
207 | |> JSB.validate defaultOptions (Encode.list [ int 1, int 20 ])
208 | |> expectOk
209 | , test "failure" <|
210 | \() ->
211 | buildSchema
212 | |> withItems
213 | [ buildSchema
214 | |> withMaximum 11
215 | , buildSchema
216 | |> withMaximum 100
217 | ]
218 | |> JSB.validate defaultOptions (Encode.list [ int 100, int 2 ])
219 | |> Expect.equal (Err [ error [ "0" ] <| Maximum 11 100 ])
220 | ]
221 | , describe "items: array of schema with additional items"
222 | [ test "success" <|
223 | \() ->
224 | buildSchema
225 | |> withItems
226 | [ buildSchema
227 | |> withMaximum 10
228 | , buildSchema
229 | |> withMaximum 100
230 | ]
231 | |> withAdditionalItems (buildSchema |> withMaximum 1)
232 | |> JSB.validate defaultOptions (Encode.list [ int 1, int 20, int 1 ])
233 | |> expectOk
234 | , test "failure" <|
235 | \() ->
236 | buildSchema
237 | |> withItems
238 | [ buildSchema
239 | |> withMaximum 11
240 | , buildSchema
241 | |> withMaximum 100
242 | ]
243 | |> withAdditionalItems (buildSchema |> withMaximum 1)
244 | |> JSB.validate defaultOptions (Encode.list [ int 2, int 2, int 100 ])
245 | |> Expect.equal (Err [ error [ "2" ] <| Maximum 1 100 ])
246 | ]
247 | , describe "maxItems"
248 | [ test "success" <|
249 | \() ->
250 | buildSchema
251 | |> withMaxItems 3
252 | |> validate defaultOptions (Encode.list [ int 1, int 2 ])
253 | |> expectOk
254 | , test "failure" <|
255 | \() ->
256 | buildSchema
257 | |> withMaxItems 2
258 | |> validate defaultOptions (Encode.list [ int 1, int 2, int 3 ])
259 | |> Expect.equal (Err [ error [] <| MaxItems 2 3 ])
260 | ]
261 | , describe "minItems"
262 | [ test "success" <|
263 | \() ->
264 | buildSchema
265 | |> withMinItems 2
266 | |> validate defaultOptions (Encode.list [ int 1, int 2, int 3 ])
267 | |> expectOk
268 | , test "failure" <|
269 | \() ->
270 | buildSchema
271 | |> withMinItems 3
272 | |> validate defaultOptions (Encode.list [ int 1, int 2 ])
273 | |> Expect.equal (Err [ error [] <| MinItems 3 2 ])
274 | ]
275 | , describe "uniqueItems"
276 | [ test "success" <|
277 | \() ->
278 | buildSchema
279 | |> withUniqueItems True
280 | |> validate defaultOptions (Encode.list [ int 1, int 2, int 3 ])
281 | |> expectOk
282 | , test "failure" <|
283 | \() ->
284 | buildSchema
285 | |> withUniqueItems True
286 | |> validate defaultOptions (Encode.list [ int 1, int 1 ])
287 | |> Expect.equal (Err [ error [] <| UniqueItems (int 1) ])
288 | ]
289 | , describe "contains"
290 | [ test "success" <|
291 | \() ->
292 | buildSchema
293 | |> withContains (buildSchema |> withMaximum 1)
294 | |> JSB.validate defaultOptions (Encode.list [ int 10, int 20, int 1 ])
295 | |> expectOk
296 | , test "failure" <|
297 | \() ->
298 | buildSchema
299 | |> withContains (buildSchema |> withMaximum 1)
300 | |> JSB.validate defaultOptions (Encode.list [ int 10, int 20 ])
301 | |> Expect.equal (Err [ error [] Contains ])
302 | ]
303 | , describe "maxProperties"
304 | [ test "success" <|
305 | \() ->
306 | buildSchema
307 | |> withMaxProperties 3
308 | |> validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
309 | |> expectOk
310 | , test "failure" <|
311 | \() ->
312 | buildSchema
313 | |> withMaxProperties 1
314 | |> validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
315 | |> Expect.equal (Err [ error [] <| MaxProperties 1 2 ])
316 | ]
317 | , describe "minProperties"
318 | [ test "success" <|
319 | \() ->
320 | buildSchema
321 | |> withMinProperties 1
322 | |> validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
323 | |> expectOk
324 | , test "failure" <|
325 | \() ->
326 | buildSchema
327 | |> withMinProperties 3
328 | |> validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
329 | |> Expect.equal (Err [ error [] <| MinProperties 3 2 ])
330 | ]
331 | , describe "required"
332 | [ test "success" <|
333 | \() ->
334 | buildSchema
335 | |> withRequired [ "foo", "bar" ]
336 | |> validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
337 | |> expectOk
338 | , test "failure" <|
339 | \() ->
340 | buildSchema
341 | |> withRequired [ "foo", "bar" ]
342 | |> validate defaultOptions (Encode.object [ ( "foo", int 1 ) ])
343 | |> Expect.equal (Err [ error [] <| Required [ "bar" ], error [ "bar" ] RequiredProperty ])
344 | ]
345 | , describe "properties"
346 | [ test "success" <|
347 | \() ->
348 | buildSchema
349 | |> withProperties
350 | [ ( "foo", buildSchema |> withMaximum 10 )
351 | , ( "bar", buildSchema |> withMaximum 20 )
352 | ]
353 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
354 | |> expectOk
355 | , test "failure" <|
356 | \() ->
357 | buildSchema
358 | |> withProperties
359 | [ ( "foo", buildSchema |> withMaximum 10 )
360 | , ( "bar", buildSchema |> withMaximum 20 )
361 | ]
362 | |> JSB.validate defaultOptions (Encode.object [ ( "bar", int 28 ) ])
363 | |> Expect.equal (Err [ error [ "bar" ] <| Maximum 20 28 ])
364 | ]
365 | , describe "patternProperties"
366 | [ test "success" <|
367 | \() ->
368 | buildSchema
369 | |> withPatternProperties
370 | [ ( "o{2}", buildSchema |> withMaximum 10 )
371 | , ( "a", buildSchema |> withMaximum 20 )
372 | ]
373 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
374 | |> expectOk
375 | , test "failure" <|
376 | \() ->
377 | buildSchema
378 | |> withPatternProperties
379 | [ ( "o{2}", buildSchema |> withMaximum 10 )
380 | , ( "a", buildSchema |> withMaximum 20 )
381 | ]
382 | |> JSB.validate defaultOptions (Encode.object [ ( "bar", int 28 ) ])
383 | |> Expect.equal (Err [ error [ "bar" ] <| Maximum 20 28 ])
384 | ]
385 | , describe "additionalProperties"
386 | [ test "success: pattern" <|
387 | \() ->
388 | buildSchema
389 | |> withPatternProperties
390 | [ ( "o{2}", buildSchema |> withMaximum 100 )
391 | ]
392 | |> withAdditionalProperties (buildSchema |> withMaximum 20)
393 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 100 ), ( "bar", int 2 ) ])
394 | |> expectOk
395 | , test "success: props" <|
396 | \() ->
397 | buildSchema
398 | |> withProperties
399 | [ ( "foo", buildSchema |> withMaximum 100 )
400 | ]
401 | |> withAdditionalProperties (buildSchema |> withMaximum 20)
402 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 100 ), ( "bar", int 2 ) ])
403 | |> expectOk
404 | , test "success: boolean true" <|
405 | \() ->
406 | buildSchema
407 | |> withProperties
408 | [ ( "foo", buildSchema |> withMaximum 100 )
409 | ]
410 | |> withAdditionalProperties (boolSchema True)
411 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 100 ), ( "bar", int 2 ) ])
412 | |> expectOk
413 | , test "failure" <|
414 | \() ->
415 | buildSchema
416 | |> withPatternProperties
417 | [ ( "o{2}", buildSchema |> withMaximum 100 )
418 | ]
419 | |> withAdditionalProperties (buildSchema |> withMaximum 20)
420 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 100 ), ( "bar", int 200 ) ])
421 | |> Expect.equal (Err [ error [ "bar" ] <| Maximum 20 200 ])
422 | , test "success: boolean false" <|
423 | \() ->
424 | buildSchema
425 | |> withPatternProperties
426 | [ ( "o{2}", buildSchema |> withMaximum 100 )
427 | ]
428 | |> withAdditionalProperties (boolSchema False)
429 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 100 ) ])
430 | |> expectOk
431 | , test "failure: boolean false" <|
432 | \() ->
433 | buildSchema
434 | |> withPatternProperties
435 | [ ( "o{2}", buildSchema |> withMaximum 100 )
436 | ]
437 | |> withAdditionalProperties (boolSchema False)
438 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 100 ), ( "bar", int 200 ) ])
439 | |> Expect.equal (Err [ error [] <| AdditionalPropertiesDisallowed [ "bar" ], error [ "bar" ] AdditionalPropertyDisallowed ])
440 | ]
441 | , describe "dependencies"
442 | [ test "success" <|
443 | \() ->
444 | buildSchema
445 | |> withSchemaDependency
446 | "foo"
447 | (buildSchema |> withRequired [ "bar" ])
448 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
449 | |> expectOk
450 | , test "failure when dependency is a schema" <|
451 | \() ->
452 | buildSchema
453 | |> withSchemaDependency
454 | "foo"
455 | (buildSchema |> withRequired [ "bar" ])
456 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 1 ) ])
457 | |> Expect.equal (Err [ error [] <| Required [ "bar" ], error [ "bar" ] RequiredProperty ])
458 | --|> Expect.equal (Err "Required property 'bar' is missing")
459 | , test "failure when dependency is array of strings" <|
460 | \() ->
461 | buildSchema
462 | |> withPropNamesDependency "foo" [ "bar" ]
463 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 1 ) ])
464 | |> Expect.equal (Err [ error [] <| Required [ "bar" ], error [ "bar" ] RequiredProperty ])
465 | ]
466 | , describe "propertyNames"
467 | [ test "success" <|
468 | \() ->
469 | buildSchema
470 | |> withPropertyNames (buildSchema |> withPattern "^ba")
471 | |> JSB.validate defaultOptions (Encode.object [ ( "baz", int 1 ), ( "bar", int 2 ) ])
472 | |> expectOk
473 | , test "failure" <|
474 | \() ->
475 | buildSchema
476 | |> withPropertyNames (buildSchema |> withPattern "^ba")
477 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 1 ), ( "bar", int 2 ) ])
478 | |> Expect.equal (Err [ error [] <| InvalidPropertyName [ error [ "foo" ] <| Pattern "^ba" "foo" ] ])
479 | ]
480 | , describe "enum"
481 | [ test "success" <|
482 | \() ->
483 | buildSchema
484 | |> withEnum [ int 1, int 2 ]
485 | |> validate defaultOptions (Encode.int 2)
486 | |> expectOk
487 | , test "failure" <|
488 | \() ->
489 | buildSchema
490 | |> withEnum [ int 1, int 2 ]
491 | |> validate defaultOptions (Encode.int 3)
492 | |> Expect.equal (Err [ error [] Enum ])
493 | ]
494 | , describe "const"
495 | [ test "success" <|
496 | \() ->
497 | buildSchema
498 | |> withConst (int 1)
499 | |> validate defaultOptions (Encode.int 1)
500 | |> expectOk
501 | , test "failure" <|
502 | \() ->
503 | buildSchema
504 | |> withConst (int 1)
505 | |> validate defaultOptions (Encode.int 2)
506 | |> Expect.equal (Err [ error [] Const ])
507 | ]
508 | , describe "type=string"
509 | [ test "success" <|
510 | \() ->
511 | buildSchema
512 | |> withType "string"
513 | |> JSB.validate defaultOptions (Encode.string "foo")
514 | |> expectOk
515 | , test "failure" <|
516 | \() ->
517 | buildSchema
518 | |> withType "string"
519 | |> JSB.validate defaultOptions (Encode.int 1)
520 | |> Expect.equal (Err [ error [] <| InvalidType "Expecting a String but instead got: 1" ])
521 | ]
522 | , describe "type=number"
523 | [ test "success" <|
524 | \() ->
525 | buildSchema
526 | |> withType "number"
527 | |> JSB.validate defaultOptions (Encode.int 1)
528 | |> expectOk
529 | , test "failure" <|
530 | \() ->
531 | buildSchema
532 | |> withType "number"
533 | |> JSB.validate defaultOptions (Encode.string "bar")
534 | |> Expect.equal (Err [ error [] <| InvalidType "Expecting a Float but instead got: \"bar\"" ])
535 | , test "failure with null" <|
536 | \() ->
537 | buildSchema
538 | |> withType "number"
539 | |> JSB.validate defaultOptions Encode.null
540 | |> Expect.equal (Err [ error [] <| InvalidType "Expecting a Float but instead got: null" ])
541 | ]
542 | , describe "type=null,number"
543 | [ test "success" <|
544 | \() ->
545 | buildSchema
546 | |> withNullableType "number"
547 | |> JSB.validate defaultOptions (Encode.int 1)
548 | |> expectOk
549 | , test "success with null" <|
550 | \() ->
551 | buildSchema
552 | |> withNullableType "number"
553 | |> JSB.validate defaultOptions Encode.null
554 | |> expectOk
555 | , test "failure" <|
556 | \() ->
557 | buildSchema
558 | |> withNullableType "number"
559 | |> JSB.validate defaultOptions (Encode.string "bar")
560 | |> Expect.equal (Err [ error [] <| InvalidType "Expecting a Float but instead got: \"bar\"" ])
561 | ]
562 | , describe "type=number,string"
563 | [ test "success for number" <|
564 | \() ->
565 | buildSchema
566 | |> withUnionType [ "number", "string" ]
567 | |> JSB.validate defaultOptions (Encode.int 1)
568 | |> expectOk
569 | , test "success for string" <|
570 | \() ->
571 | buildSchema
572 | |> withUnionType [ "number", "string" ]
573 | |> JSB.validate defaultOptions (Encode.string "str")
574 | |> expectOk
575 | , test "failure for object" <|
576 | \() ->
577 | buildSchema
578 | |> withUnionType [ "number", "string" ]
579 | |> JSB.validate defaultOptions (Encode.object [])
580 | |> Expect.equal (Err [ error [] <| InvalidType "None of desired types match" ])
581 | ]
582 | , describe "allOf"
583 | [ test "success" <|
584 | \() ->
585 | buildSchema
586 | |> withAllOf
587 | [ buildSchema |> withMinimum 0
588 | , buildSchema |> withMaximum 1
589 | ]
590 | |> JSB.validate defaultOptions (Encode.int 1)
591 | |> expectOk
592 | , test "failure because of minimum" <|
593 | \() ->
594 | buildSchema
595 | |> withAllOf
596 | [ buildSchema |> withMinimum 0
597 | , buildSchema |> withMaximum 1
598 | ]
599 | |> JSB.validate defaultOptions (Encode.int -1)
600 | |> Expect.equal (Err [ error [] <| Minimum 0 -1 ])
601 | , test "failure because of maximum" <|
602 | \() ->
603 | buildSchema
604 | |> withAllOf
605 | [ buildSchema |> withMinimum 0
606 | , buildSchema |> withMaximum 1
607 | ]
608 | |> JSB.validate defaultOptions (Encode.int 2)
609 | |> Expect.equal (Err [ error [] <| Maximum 1 2 ])
610 | ]
611 | , describe "anyOf"
612 | [ test "success for enum" <|
613 | \() ->
614 | buildSchema
615 | |> withAllOf
616 | [ buildSchema |> withMinimum 0
617 | , buildSchema |> withEnum [ int 1 ]
618 | ]
619 | |> JSB.validate defaultOptions (Encode.int 1)
620 | |> expectOk
621 | , test "success for minimum" <|
622 | \() ->
623 | buildSchema
624 | |> withAnyOf
625 | [ buildSchema |> withMinimum 0
626 | , buildSchema |> withEnum [ int 1 ]
627 | ]
628 | |> JSB.validate defaultOptions (Encode.float 0.5)
629 | |> expectOk
630 | , test "failure" <|
631 | \() ->
632 | buildSchema
633 | |> withAnyOf
634 | [ buildSchema |> withMinimum 0
635 | , buildSchema |> withEnum [ int 1 ]
636 | ]
637 | |> JSB.validate defaultOptions (Encode.int -1)
638 | |> Expect.equal
639 | (Err
640 | [ error [] <| Minimum 0 -1
641 | , error [] <| Enum
642 | ]
643 | )
644 | ]
645 | , describe "oneOf"
646 | [ test "success for enum" <|
647 | \() ->
648 | buildSchema
649 | |> withOneOf
650 | [ buildSchema |> withMinimum 10
651 | , buildSchema |> withEnum [ int 1 ]
652 | ]
653 | |> JSB.validate defaultOptions (Encode.int 1)
654 | |> expectOk
655 | , test "success for minimum" <|
656 | \() ->
657 | buildSchema
658 | |> withOneOf
659 | [ buildSchema |> withMinimum 0
660 | , buildSchema |> withEnum [ int 1 ]
661 | ]
662 | |> JSB.validate defaultOptions (Encode.int 0)
663 | |> expectOk
664 | , test "failure for all" <|
665 | \() ->
666 | buildSchema
667 | |> withOneOf
668 | [ buildSchema |> withMinimum 0
669 | , buildSchema |> withEnum [ int 1 ]
670 | ]
671 | |> JSB.validate defaultOptions (Encode.int -1)
672 | |> Expect.equal (Err [ error [] OneOfNoneSucceed ])
673 | , test "failure because of success for both" <|
674 | \() ->
675 | buildSchema
676 | |> withOneOf
677 | [ buildSchema |> withMinimum 0
678 | , buildSchema |> withEnum [ int 1 ]
679 | ]
680 | |> JSB.validate defaultOptions (Encode.int 1)
681 | |> Expect.equal (Err [ error [] <| OneOfManySucceed 2 ])
682 | ]
683 | , describe "boolean schema"
684 | [ test "true always validates any value" <|
685 | \() ->
686 | Encode.bool True
687 | |> decodeValue Json.Schema.Definitions.decoder
688 | |> Result.withDefault blankSchema
689 | |> (\s -> Validation.validate defaultOptions Ref.defaultPool (int 1) s s)
690 | |> expectOk
691 | , test "false always fails validation" <|
692 | \() ->
693 | Encode.bool False
694 | |> decodeValue Json.Schema.Definitions.decoder
695 | |> Result.withDefault blankSchema
696 | |> (\s -> Validation.validate defaultOptions Ref.defaultPool (int 1) s s)
697 | |> Expect.equal (Err [ error [] AlwaysFail ])
698 | ]
699 | , describe "multiple errors"
700 | [ test "validation should return multiple errors" <|
701 | \() ->
702 | buildSchema
703 | |> withProperties
704 | [ ( "foo", buildSchema |> withMaximum 1 )
705 | , ( "bar", buildSchema |> withMaximum 2 )
706 | ]
707 | |> JSB.validate defaultOptions (Encode.object [ ( "foo", int 7 ), ( "bar", int 28 ) ])
708 | |> Expect.equal
709 | (Err
710 | [ error [ "foo" ] <| Maximum 1 7
711 | , error [ "bar" ] <| Maximum 2 28
712 | ]
713 | )
714 | ]
715 | , describe "defaults"
716 | [ test "apply default value" <|
717 | \() ->
718 | buildSchema
719 | |> withProperties
720 | [ ( "key", buildSchema
721 | |> withType "string"
722 | |> JSB.withDefault (Encode.string "def")
723 | ) ]
724 | |> JSB.validate { applyDefaults = True } (Encode.object [])
725 | |> Expect.equal
726 | (Ok <| Encode.object [ ("key", Encode.string "def" ) ])
727 | , test "apply default value in nested object" <|
728 | \() ->
729 | buildSchema
730 | |> withProperties
731 | [ ( "obj", buildSchema
732 | |> withProperties
733 | [ ( "key", buildSchema
734 | |> withType "string"
735 | |> JSB.withDefault (Encode.string "def")
736 | ) ]
737 | ) ]
738 | |> JSB.validate { applyDefaults = True } (Encode.object [])
739 | |> Result.map (Encode.encode 0)
740 | |> Expect.equal
741 | (Ok """{"obj":{"key":"def"}}""")
742 | ]
743 | ]
744 |
745 |
746 | error : List String -> ValidationError -> Error
747 | error path =
748 | Error (JsonPointer "" path)
749 |
750 |
751 | expectOk : Result x a -> Expect.Expectation
752 | expectOk e =
753 | case e of
754 | Err x ->
755 | Expect.fail <| "Unexpected error: " ++ (toString x)
756 |
757 | Ok _ ->
758 | Expect.pass
759 |
--------------------------------------------------------------------------------
/tests/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "Test Suites",
4 | "repository": "https://github.com/1602/elm-json-schema.git",
5 | "license": "BSD3",
6 | "source-directories": [
7 | "../src",
8 | "."
9 | ],
10 | "exposed-modules": [],
11 | "dependencies": {
12 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
13 | "elm-community/elm-test": "4.0.0 <= v < 5.0.0",
14 | "elm-community/json-extra": "2.0.0 <= v < 3.0.0",
15 | "elm-community/list-extra": "6.1.0 <= v < 7.0.0",
16 | "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0",
17 | "zwilias/elm-utf-tools": "1.0.1 <= v < 2.0.0"
18 | },
19 | "elm-version": "0.18.0 <= v < 0.19.0"
20 | }
21 |
--------------------------------------------------------------------------------