dry-schema 1.6.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/README.md +4 -3
- data/dry-schema.gemspec +16 -14
- data/lib/dry/schema/compiler.rb +1 -1
- data/lib/dry/schema/config.rb +9 -9
- data/lib/dry/schema/dsl.rb +7 -4
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +9 -4
- data/lib/dry/schema/extensions/hints.rb +11 -9
- data/lib/dry/schema/extensions/info/schema_compiler.rb +10 -1
- data/lib/dry/schema/extensions/json_schema/schema_compiler.rb +232 -0
- data/lib/dry/schema/extensions/json_schema.rb +29 -0
- data/lib/dry/schema/extensions/struct.rb +1 -1
- data/lib/dry/schema/extensions.rb +4 -0
- data/lib/dry/schema/key.rb +75 -70
- data/lib/dry/schema/key_coercer.rb +2 -2
- data/lib/dry/schema/key_validator.rb +46 -20
- data/lib/dry/schema/macros/array.rb +4 -0
- data/lib/dry/schema/macros/core.rb +1 -1
- data/lib/dry/schema/macros/dsl.rb +17 -15
- data/lib/dry/schema/macros/hash.rb +1 -1
- data/lib/dry/schema/macros/key.rb +2 -2
- data/lib/dry/schema/macros/schema.rb +2 -0
- data/lib/dry/schema/macros/value.rb +13 -1
- data/lib/dry/schema/message/or/multi_path.rb +7 -5
- data/lib/dry/schema/message_compiler.rb +13 -10
- data/lib/dry/schema/messages/abstract.rb +9 -9
- data/lib/dry/schema/messages/i18n.rb +98 -96
- data/lib/dry/schema/messages/namespaced.rb +1 -0
- data/lib/dry/schema/messages/yaml.rb +165 -151
- data/lib/dry/schema/path.rb +10 -60
- data/lib/dry/schema/predicate.rb +2 -2
- data/lib/dry/schema/predicate_inferrer.rb +2 -0
- data/lib/dry/schema/primitive_inferrer.rb +2 -0
- data/lib/dry/schema/processor.rb +6 -6
- data/lib/dry/schema/processor_steps.rb +7 -3
- data/lib/dry/schema/result.rb +38 -31
- data/lib/dry/schema/step.rb +14 -33
- data/lib/dry/schema/trace.rb +5 -1
- data/lib/dry/schema/type_registry.rb +1 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 630c8c7019a02d6f22386ef4ac1ca68321af93bd3a5e34f8b62b21d28a7eee69
|
4
|
+
data.tar.gz: e53baa861af349395446deb0183e7358438e4ca4ea7fa78b25d4c4f04e4ea2a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ed11d1eba31d808c75018d5ea78fc649f4f59486c2047554aeabac5cd71e9b2d6e64b88661c8f0615b9a9402c177d2c51f49cd49e3ae6e697c45f9f952dab18
|
7
|
+
data.tar.gz: 5c085c4994f662a44eb372e92a894c76d83eefdce88389ed6c1947004c7bd39204b843ea5eb408af86ad986e6978dde67227b9bdcf7d888d1a8e277b457c4345
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,58 @@
|
|
1
1
|
<!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
|
2
2
|
|
3
|
+
## 1.9.0 2022-02-15
|
4
|
+
|
5
|
+
|
6
|
+
### Added
|
7
|
+
|
8
|
+
- [EXPERIMENTAL] `json_schema` extension which allows you to convert a schema into a JSON schema (via #369) (@ianks)
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- Composing schemas no longer crashes in certain scenarios (issue #342 fixed via #366) (@vsuhachev)
|
13
|
+
- Fix info extension for typed arrays (issue #394 fixed via #397) (@CandyFet)
|
14
|
+
|
15
|
+
|
16
|
+
[Compare v1.8.0...v1.9.0](https://github.com/dry-rb/dry-schema/compare/v1.8.0...v1.9.0)
|
17
|
+
|
18
|
+
## 1.8.0 2021-09-12
|
19
|
+
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
|
23
|
+
- [internal] Upgraded to new `setting` API provided in dry-configurable 0.13.0 (@timriley in #356)
|
24
|
+
|
25
|
+
[Compare v1.7.1...v1.8.0](https://github.com/dry-rb/dry-schema/compare/v1.7.1...v1.8.0)
|
26
|
+
|
27
|
+
## 1.7.1 2021-08-29
|
28
|
+
|
29
|
+
|
30
|
+
### Changed
|
31
|
+
|
32
|
+
- [internal] Use explicit `#to_h` conversion of Dry::Configurable::Config, to ensure compatibility with upcoming dry-configurable 0.13.0 release (via #371) (@timriley)
|
33
|
+
|
34
|
+
[Compare v1.7.0...v1.7.1](https://github.com/dry-rb/dry-schema/compare/v1.7.0...v1.7.1)
|
35
|
+
|
36
|
+
## 1.7.0 2021-06-29
|
37
|
+
|
38
|
+
This release ships with a bunch of internal refactorings that should improve performance but if you see any unexpected behavior please do report issues.
|
39
|
+
|
40
|
+
### Fixed
|
41
|
+
|
42
|
+
- Handle arrays of hashes where Array constructor coerces non-Hash input (#351 fixed via #354) (@ojab)
|
43
|
+
- Run outer schema processor steps before inner ones (issue #350 fixed via #361) (@ojab)
|
44
|
+
- Fix key validator false negatives on empty collections (see #363) (@Drenmi)
|
45
|
+
- Prevent error message YAML files from being parsed multiple times (issue #352 via #364) (@alassek)
|
46
|
+
- Using constructor types should work fine now ie `required(:foo).filled(Types::Params::Integer.constructor(&:succ))` (issue #280 fixed via #365) (@solnic)
|
47
|
+
- Handle non-Hash to Hash transformation in `before(:key_coercer)` (issue #350 fixed via #362) (@ojab)
|
48
|
+
|
49
|
+
### Changed
|
50
|
+
|
51
|
+
- [internal] `Dry::Schema::Path` clean up and performance improvements (via #358) (@ojab)
|
52
|
+
- [internal] simplify and speed up handling of steps in nested schemas (via #360) (@ojab)
|
53
|
+
|
54
|
+
[Compare v1.6.2...v1.7.0](https://github.com/dry-rb/dry-schema/compare/v1.6.2...v1.7.0)
|
55
|
+
|
3
56
|
## 1.6.2 2021-04-15
|
4
57
|
|
5
58
|
|
data/README.md
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
<!--- this file is synced from dry-rb/template-gem project -->
|
1
2
|
[gem]: https://rubygems.org/gems/dry-schema
|
2
3
|
[actions]: https://github.com/dry-rb/dry-schema/actions
|
3
4
|
[codacy]: https://www.codacy.com/gh/dry-rb/dry-schema
|
@@ -14,15 +15,15 @@
|
|
14
15
|
|
15
16
|
## Links
|
16
17
|
|
17
|
-
* [User documentation](
|
18
|
+
* [User documentation](https://dry-rb.org/gems/dry-schema)
|
18
19
|
* [API documentation](http://rubydoc.info/gems/dry-schema)
|
19
20
|
|
20
21
|
## Supported Ruby versions
|
21
22
|
|
22
23
|
This library officially supports the following Ruby versions:
|
23
24
|
|
24
|
-
* MRI
|
25
|
-
* jruby
|
25
|
+
* MRI `>= 2.7.0`
|
26
|
+
* jruby `>= 9.3` (postponed until 2.7 is supported)
|
26
27
|
|
27
28
|
## License
|
28
29
|
|
data/dry-schema.gemspec
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# this file is managed by dry-rb/devtools project
|
3
2
|
|
4
|
-
|
3
|
+
# this file is synced from dry-rb/template-gem project
|
4
|
+
|
5
|
+
lib = File.expand_path("lib", __dir__)
|
5
6
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
-
require
|
7
|
+
require "dry/schema/version"
|
7
8
|
|
8
9
|
Gem::Specification.new do |spec|
|
9
|
-
spec.name =
|
10
|
+
spec.name = "dry-schema"
|
10
11
|
spec.authors = ["Piotr Solnica"]
|
11
12
|
spec.email = ["piotr.solnica@gmail.com"]
|
12
|
-
spec.license =
|
13
|
+
spec.license = "MIT"
|
13
14
|
spec.version = Dry::Schema::VERSION.dup
|
14
15
|
|
15
16
|
spec.summary = "Coercion and validation for data structures"
|
@@ -17,23 +18,24 @@ Gem::Specification.new do |spec|
|
|
17
18
|
dry-schema provides a DSL for defining schemas with keys and rules that should be applied to
|
18
19
|
values. It supports coercion, input sanitization, custom types and localized error messages
|
19
20
|
(with or without I18n gem). It's also used as the schema engine in dry-validation.
|
21
|
+
|
20
22
|
TEXT
|
21
|
-
spec.homepage =
|
23
|
+
spec.homepage = "https://dry-rb.org/gems/dry-schema"
|
22
24
|
spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-schema.gemspec", "lib/**/*", "config/*.yml"]
|
23
|
-
spec.bindir =
|
25
|
+
spec.bindir = "bin"
|
24
26
|
spec.executables = []
|
25
|
-
spec.require_paths = [
|
27
|
+
spec.require_paths = ["lib"]
|
26
28
|
|
27
|
-
spec.metadata[
|
28
|
-
spec.metadata[
|
29
|
-
spec.metadata[
|
30
|
-
spec.metadata[
|
29
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
30
|
+
spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-schema/blob/master/CHANGELOG.md"
|
31
|
+
spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-schema"
|
32
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-schema/issues"
|
31
33
|
|
32
|
-
spec.required_ruby_version = ">= 2.
|
34
|
+
spec.required_ruby_version = ">= 2.7.0"
|
33
35
|
|
34
36
|
# to update dependencies edit project.yml
|
35
37
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
36
|
-
spec.add_runtime_dependency "dry-configurable", "~> 0.
|
38
|
+
spec.add_runtime_dependency "dry-configurable", "~> 0.13", ">= 0.13.0"
|
37
39
|
spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
|
38
40
|
spec.add_runtime_dependency "dry-initializer", "~> 3.0"
|
39
41
|
spec.add_runtime_dependency "dry-logic", "~> 1.0"
|
data/lib/dry/schema/compiler.rb
CHANGED
data/lib/dry/schema/config.rb
CHANGED
@@ -25,7 +25,7 @@ module Dry
|
|
25
25
|
# @return [Schema::PredicateRegistry]
|
26
26
|
#
|
27
27
|
# @api public
|
28
|
-
setting
|
28
|
+
setting :predicates, default: Schema::PredicateRegistry.new
|
29
29
|
|
30
30
|
# @!method types
|
31
31
|
#
|
@@ -34,7 +34,7 @@ module Dry
|
|
34
34
|
# @return [Hash]
|
35
35
|
#
|
36
36
|
# @api public
|
37
|
-
setting
|
37
|
+
setting :types, default: Dry::Types
|
38
38
|
|
39
39
|
# @!method messages
|
40
40
|
#
|
@@ -43,12 +43,12 @@ module Dry
|
|
43
43
|
# @return [Dry::Configurable::Config]
|
44
44
|
#
|
45
45
|
# @api public
|
46
|
-
setting
|
47
|
-
setting
|
48
|
-
setting
|
49
|
-
setting
|
50
|
-
setting
|
51
|
-
setting
|
46
|
+
setting :messages do
|
47
|
+
setting :backend, default: :yaml
|
48
|
+
setting :namespace
|
49
|
+
setting :load_paths, default: Set[DEFAULT_MESSAGES_PATH], constructor: :dup.to_proc
|
50
|
+
setting :top_namespace, default: DEFAULT_MESSAGES_ROOT
|
51
|
+
setting :default_locale
|
52
52
|
end
|
53
53
|
|
54
54
|
# @!method validate_keys
|
@@ -58,7 +58,7 @@ module Dry
|
|
58
58
|
# @return [Boolean]
|
59
59
|
#
|
60
60
|
# @api public
|
61
|
-
setting
|
61
|
+
setting :validate_keys, default: false
|
62
62
|
|
63
63
|
# @api private
|
64
64
|
def respond_to_missing?(meth, include_private = false)
|
data/lib/dry/schema/dsl.rb
CHANGED
@@ -80,8 +80,10 @@ module Dry
|
|
80
80
|
# Build a new DSL object and evaluate provided block
|
81
81
|
#
|
82
82
|
# @param [Hash] options
|
83
|
-
# @option options [Class] :processor The processor type
|
84
|
-
#
|
83
|
+
# @option options [Class] :processor The processor type
|
84
|
+
# (`Params`, `JSON` or a custom sub-class)
|
85
|
+
# @option options [Compiler] :compiler An instance of a rule compiler
|
86
|
+
# (must be compatible with `Schema::Compiler`) (optional)
|
85
87
|
# @option options [Array[DSL]] :parent One or more instances of the parent DSL (optional)
|
86
88
|
# @option options [Config] :config A configuration object (optional)
|
87
89
|
#
|
@@ -168,7 +170,8 @@ module Dry
|
|
168
170
|
# A generic method for defining keys
|
169
171
|
#
|
170
172
|
# @param [Symbol] name The key name
|
171
|
-
# @param [Class] macro The macro sub-class (ie `Macros::Required` or
|
173
|
+
# @param [Class] macro The macro sub-class (ie `Macros::Required` or
|
174
|
+
# any other `Macros::Key` subclass)
|
172
175
|
#
|
173
176
|
# @return [Macros::Key]
|
174
177
|
#
|
@@ -383,7 +386,7 @@ module Dry
|
|
383
386
|
#
|
384
387
|
# @api protected
|
385
388
|
def rules
|
386
|
-
parent_rules.merge(macros.
|
389
|
+
parent_rules.merge(macros.to_h { [_1.name, _1.to_rule] }.compact)
|
387
390
|
end
|
388
391
|
|
389
392
|
# Build a key map from defined types
|
@@ -35,12 +35,14 @@ module Dry
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# @api private
|
38
|
+
# rubocop: disable Metrics/AbcSize
|
39
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
40
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
38
41
|
def exclude?(messages, opts)
|
39
42
|
Array(messages).all? do |msg|
|
40
|
-
hints = opts
|
41
|
-
.
|
42
|
-
|
43
|
-
.reject { |hint| hint.predicate == :filled? }
|
43
|
+
hints = opts.hints.reject { |h|
|
44
|
+
msg.eql?(h) || h.predicate.eql?(:filled?)
|
45
|
+
}
|
44
46
|
|
45
47
|
key_failure = opts.key_failure?(msg.path)
|
46
48
|
predicate = msg.predicate
|
@@ -52,6 +54,9 @@ module Dry
|
|
52
54
|
HINT_OTHER_EXCLUSION.include?(predicate)
|
53
55
|
end
|
54
56
|
end
|
57
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
58
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
59
|
+
# rubocop: enable Metrics/AbcSize
|
55
60
|
|
56
61
|
# @api private
|
57
62
|
def message_type(options)
|
@@ -22,17 +22,19 @@ module Dry
|
|
22
22
|
# @see Message::Or
|
23
23
|
#
|
24
24
|
# @api public
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
module Or
|
26
|
+
class SinglePath
|
27
|
+
# @api private
|
28
|
+
def hint?
|
29
|
+
false
|
30
|
+
end
|
29
31
|
end
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
class MultiPath
|
34
|
+
# @api private
|
35
|
+
def hint?
|
36
|
+
false
|
37
|
+
end
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -96,7 +96,16 @@ module Dry
|
|
96
96
|
keys[rest[0][1]] = {required: opts.fetch(:required, true)}
|
97
97
|
else
|
98
98
|
type = PREDICATE_TO_TYPE[name]
|
99
|
-
|
99
|
+
assign_type(key, type) if type
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
def assign_type(key, type)
|
105
|
+
if keys[key][:type]
|
106
|
+
keys[key][:member] = type
|
107
|
+
else
|
108
|
+
keys[key][:type] = type
|
100
109
|
end
|
101
110
|
end
|
102
111
|
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema/constants"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Schema
|
7
|
+
# @api private
|
8
|
+
module JSONSchema
|
9
|
+
# @api private
|
10
|
+
class SchemaCompiler
|
11
|
+
# An error raised when a predicate cannot be converted
|
12
|
+
UnknownConversionError = Class.new(StandardError)
|
13
|
+
|
14
|
+
IDENTITY = ->(v, _) { v }.freeze
|
15
|
+
TO_INTEGER = ->(v, _) { v.to_i }.freeze
|
16
|
+
|
17
|
+
PREDICATE_TO_TYPE = {
|
18
|
+
array?: {type: "array"},
|
19
|
+
bool?: {type: "boolean"},
|
20
|
+
date?: {type: "string", format: "date"},
|
21
|
+
date_time?: {type: "string", format: "date-time"},
|
22
|
+
decimal?: {type: "number"},
|
23
|
+
float?: {type: "number"},
|
24
|
+
hash?: {type: "object"},
|
25
|
+
int?: {type: "integer"},
|
26
|
+
nil?: {type: "null"},
|
27
|
+
str?: {type: "string"},
|
28
|
+
time?: {type: "string", format: "time"},
|
29
|
+
min_size?: {minLength: TO_INTEGER},
|
30
|
+
max_size?: {maxLength: TO_INTEGER},
|
31
|
+
included_in?: {enum: ->(v, _) { v.to_a }},
|
32
|
+
filled?: EMPTY_HASH,
|
33
|
+
uri?: {format: "uri"},
|
34
|
+
uuid_v1?: {
|
35
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
36
|
+
},
|
37
|
+
uuid_v2?: {
|
38
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
39
|
+
},
|
40
|
+
uuid_v3?: {
|
41
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
42
|
+
},
|
43
|
+
uuid_v4?: {
|
44
|
+
pattern: "^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}$"
|
45
|
+
},
|
46
|
+
uuid_v5?: {
|
47
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
48
|
+
},
|
49
|
+
gt?: {exclusiveMinimum: IDENTITY},
|
50
|
+
gteq?: {mininum: IDENTITY},
|
51
|
+
lt?: {exclusiveMaximum: IDENTITY},
|
52
|
+
lteq?: {maximum: IDENTITY},
|
53
|
+
odd?: {type: "integer", not: {multipleOf: 2}},
|
54
|
+
even?: {type: "integer", multipleOf: 2}
|
55
|
+
}.freeze
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
attr_reader :keys, :required
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
def initialize(root: false, loose: false)
|
62
|
+
@keys = EMPTY_HASH.dup
|
63
|
+
@required = Set.new
|
64
|
+
@root = root
|
65
|
+
@loose = loose
|
66
|
+
end
|
67
|
+
|
68
|
+
# @api private
|
69
|
+
def to_hash
|
70
|
+
result = {}
|
71
|
+
result[:$schema] = "http://json-schema.org/draft-06/schema#" if root?
|
72
|
+
result.merge!(type: "object", properties: keys, required: required.to_a)
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
alias_method :to_h, :to_hash
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def call(ast)
|
80
|
+
visit(ast)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
def visit(node, opts = EMPTY_HASH)
|
85
|
+
meth, rest = node
|
86
|
+
public_send(:"visit_#{meth}", rest, opts)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @api private
|
90
|
+
def visit_set(node, opts = EMPTY_HASH)
|
91
|
+
target = (key = opts[:key]) ? self.class.new : self
|
92
|
+
|
93
|
+
node.map { |child| target.visit(child, opts) }
|
94
|
+
|
95
|
+
return unless key
|
96
|
+
|
97
|
+
target_info = opts[:member] ? {items: target.to_h} : target.to_h
|
98
|
+
type = opts[:member] ? "array" : "object"
|
99
|
+
|
100
|
+
keys.update(key => {**keys[key], type: type, **target_info})
|
101
|
+
end
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
def visit_and(node, opts = EMPTY_HASH)
|
105
|
+
left, right = node
|
106
|
+
|
107
|
+
# We need to know the type first to apply filled macro
|
108
|
+
if left[1][0] == :filled?
|
109
|
+
visit(right, opts)
|
110
|
+
visit(left, opts)
|
111
|
+
else
|
112
|
+
visit(left, opts)
|
113
|
+
visit(right, opts)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
def visit_implication(node, opts = EMPTY_HASH)
|
119
|
+
node.each do |el|
|
120
|
+
visit(el, **opts, required: false)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# @api private
|
125
|
+
def visit_each(node, opts = EMPTY_HASH)
|
126
|
+
visit(node, opts.merge(member: true))
|
127
|
+
end
|
128
|
+
|
129
|
+
# @api private
|
130
|
+
def visit_key(node, opts = EMPTY_HASH)
|
131
|
+
name, rest = node
|
132
|
+
|
133
|
+
if opts.fetch(:required, :true)
|
134
|
+
required << name.to_s
|
135
|
+
else
|
136
|
+
opts.delete(:required)
|
137
|
+
end
|
138
|
+
|
139
|
+
visit(rest, opts.merge(key: name))
|
140
|
+
end
|
141
|
+
|
142
|
+
# @api private
|
143
|
+
def visit_not(node, opts = EMPTY_HASH)
|
144
|
+
_name, rest = node
|
145
|
+
|
146
|
+
visit_predicate(rest, opts)
|
147
|
+
end
|
148
|
+
|
149
|
+
# @api private
|
150
|
+
def visit_predicate(node, opts = EMPTY_HASH)
|
151
|
+
name, rest = node
|
152
|
+
|
153
|
+
if name.equal?(:key?)
|
154
|
+
prop_name = rest[0][1]
|
155
|
+
keys[prop_name] = {}
|
156
|
+
else
|
157
|
+
target = keys[opts[:key]]
|
158
|
+
type_opts = fetch_type_opts_for_predicate(name, rest, target)
|
159
|
+
|
160
|
+
if target[:type]&.include?("array")
|
161
|
+
target[:items] ||= {}
|
162
|
+
merge_opts!(target[:items], type_opts)
|
163
|
+
else
|
164
|
+
merge_opts!(target, type_opts)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# @api private
|
170
|
+
def fetch_type_opts_for_predicate(name, rest, target)
|
171
|
+
type_opts = PREDICATE_TO_TYPE.fetch(name) do
|
172
|
+
raise_unknown_conversion_error!(:predicate, name) unless loose?
|
173
|
+
|
174
|
+
EMPTY_HASH
|
175
|
+
end.dup
|
176
|
+
type_opts.transform_values! { |v| v.respond_to?(:call) ? v.call(rest[0][1], target) : v }
|
177
|
+
type_opts.merge!(fetch_filled_options(target[:type], target)) if name == :filled?
|
178
|
+
type_opts
|
179
|
+
end
|
180
|
+
|
181
|
+
# @api private
|
182
|
+
def fetch_filled_options(type, _target)
|
183
|
+
case type
|
184
|
+
when "string"
|
185
|
+
{minLength: 1}
|
186
|
+
when "array"
|
187
|
+
raise_unknown_conversion_error!(:type, :array) unless loose?
|
188
|
+
|
189
|
+
{not: {type: "null"}}
|
190
|
+
else
|
191
|
+
{not: {type: "null"}}
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# @api private
|
196
|
+
def merge_opts!(orig_opts, new_opts)
|
197
|
+
new_type = new_opts[:type]
|
198
|
+
orig_type = orig_opts[:type]
|
199
|
+
|
200
|
+
if orig_type && new_type && orig_type != new_type
|
201
|
+
new_opts[:type] = [orig_type, new_type]
|
202
|
+
end
|
203
|
+
|
204
|
+
orig_opts.merge!(new_opts)
|
205
|
+
end
|
206
|
+
|
207
|
+
# @api private
|
208
|
+
def root?
|
209
|
+
@root
|
210
|
+
end
|
211
|
+
|
212
|
+
# @api private
|
213
|
+
def loose?
|
214
|
+
@loose
|
215
|
+
end
|
216
|
+
|
217
|
+
def raise_unknown_conversion_error!(type, name)
|
218
|
+
message = <<~MSG
|
219
|
+
Could not find an equivalent conversion for #{type} #{name.inspect}.
|
220
|
+
|
221
|
+
This means that your generated JSON schema may be missing this validation.
|
222
|
+
|
223
|
+
You can ignore this by generating the schema in "loose" mode, i.e.:
|
224
|
+
my_schema.json_schema(loose: true)
|
225
|
+
MSG
|
226
|
+
|
227
|
+
raise UnknownConversionError, message.chomp
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema/extensions/json_schema/schema_compiler"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Schema
|
7
|
+
# JSONSchema extension
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
module JSONSchema
|
11
|
+
module SchemaMethods
|
12
|
+
# Convert the schema into a JSON schema hash
|
13
|
+
#
|
14
|
+
# @param [Symbol] loose Compile the schema in "loose" mode
|
15
|
+
#
|
16
|
+
# @return [Hash<Symbol=>Hash>]
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def json_schema(loose: false)
|
20
|
+
compiler = SchemaCompiler.new(root: true, loose: loose)
|
21
|
+
compiler.call(to_ast)
|
22
|
+
compiler.to_hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Processor.include(JSONSchema::SchemaMethods)
|
28
|
+
end
|
29
|
+
end
|