dry-schema 1.6.2 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/README.md +4 -3
  4. data/dry-schema.gemspec +16 -14
  5. data/lib/dry/schema/compiler.rb +1 -1
  6. data/lib/dry/schema/config.rb +9 -9
  7. data/lib/dry/schema/dsl.rb +7 -4
  8. data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +9 -4
  9. data/lib/dry/schema/extensions/hints.rb +11 -9
  10. data/lib/dry/schema/extensions/info/schema_compiler.rb +10 -1
  11. data/lib/dry/schema/extensions/json_schema/schema_compiler.rb +232 -0
  12. data/lib/dry/schema/extensions/json_schema.rb +29 -0
  13. data/lib/dry/schema/extensions/struct.rb +1 -1
  14. data/lib/dry/schema/extensions.rb +4 -0
  15. data/lib/dry/schema/key.rb +75 -70
  16. data/lib/dry/schema/key_coercer.rb +2 -2
  17. data/lib/dry/schema/key_validator.rb +46 -20
  18. data/lib/dry/schema/macros/array.rb +4 -0
  19. data/lib/dry/schema/macros/core.rb +1 -1
  20. data/lib/dry/schema/macros/dsl.rb +17 -15
  21. data/lib/dry/schema/macros/hash.rb +1 -1
  22. data/lib/dry/schema/macros/key.rb +2 -2
  23. data/lib/dry/schema/macros/schema.rb +2 -0
  24. data/lib/dry/schema/macros/value.rb +13 -1
  25. data/lib/dry/schema/message/or/multi_path.rb +7 -5
  26. data/lib/dry/schema/message_compiler.rb +13 -10
  27. data/lib/dry/schema/messages/abstract.rb +9 -9
  28. data/lib/dry/schema/messages/i18n.rb +98 -96
  29. data/lib/dry/schema/messages/namespaced.rb +1 -0
  30. data/lib/dry/schema/messages/yaml.rb +165 -151
  31. data/lib/dry/schema/path.rb +10 -60
  32. data/lib/dry/schema/predicate.rb +2 -2
  33. data/lib/dry/schema/predicate_inferrer.rb +2 -0
  34. data/lib/dry/schema/primitive_inferrer.rb +2 -0
  35. data/lib/dry/schema/processor.rb +6 -6
  36. data/lib/dry/schema/processor_steps.rb +7 -3
  37. data/lib/dry/schema/result.rb +38 -31
  38. data/lib/dry/schema/step.rb +14 -33
  39. data/lib/dry/schema/trace.rb +5 -1
  40. data/lib/dry/schema/type_registry.rb +1 -2
  41. data/lib/dry/schema/version.rb +1 -1
  42. metadata +11 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad4d59e0bdc2cc46cc165f50e1ab741c07d1c00dca7467c4a6bfd647460a6d3b
4
- data.tar.gz: 295466e7e905103612280d5fdb549f116212c99c2c96b54f3561b1264174a499
3
+ metadata.gz: 630c8c7019a02d6f22386ef4ac1ca68321af93bd3a5e34f8b62b21d28a7eee69
4
+ data.tar.gz: e53baa861af349395446deb0183e7358438e4ca4ea7fa78b25d4c4f04e4ea2a5
5
5
  SHA512:
6
- metadata.gz: 635185546a80867d4fc34db6d30153e43533865c3003e64bd7d7fc638cbe0cb97a7770598d3902ccf8fbd53b3b8e15253a0bb2ce4dc36c9e553fe2486491d9f1
7
- data.tar.gz: 465f1b34aab9ff0c74f690aedcfef27536fea6372df401f5193b90a2347752c58d5e2a7ed020a674ac8f0c468f809b7448cda6f303eb5dd9a48575c7cea43cde
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](http://dry-rb.org/gems/dry-schema)
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 >= `2.5`
25
- * jruby >= `9.2`
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
- lib = File.expand_path('lib', __dir__)
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 'dry/schema/version'
7
+ require "dry/schema/version"
7
8
 
8
9
  Gem::Specification.new do |spec|
9
- spec.name = 'dry-schema'
10
+ spec.name = "dry-schema"
10
11
  spec.authors = ["Piotr Solnica"]
11
12
  spec.email = ["piotr.solnica@gmail.com"]
12
- spec.license = 'MIT'
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 = 'https://dry-rb.org/gems/dry-schema'
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 = 'bin'
25
+ spec.bindir = "bin"
24
26
  spec.executables = []
25
- spec.require_paths = ['lib']
27
+ spec.require_paths = ["lib"]
26
28
 
27
- spec.metadata['allowed_push_host'] = 'https://rubygems.org'
28
- spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-schema/blob/master/CHANGELOG.md'
29
- spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-schema'
30
- spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-schema/issues'
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.5.0"
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.8", ">= 0.8.3"
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"
@@ -47,7 +47,7 @@ module Dry
47
47
  # @return [Boolean]
48
48
  #
49
49
  # @api private
50
- def supports?(predicate)
50
+ def support?(predicate)
51
51
  predicates.key?(predicate)
52
52
  end
53
53
  end
@@ -25,7 +25,7 @@ module Dry
25
25
  # @return [Schema::PredicateRegistry]
26
26
  #
27
27
  # @api public
28
- setting(:predicates, Schema::PredicateRegistry.new)
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(:types, Dry::Types)
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(:messages) do
47
- setting(:backend, :yaml)
48
- setting(:namespace)
49
- setting(:load_paths, Set[DEFAULT_MESSAGES_PATH], &:dup)
50
- setting(:top_namespace, DEFAULT_MESSAGES_ROOT)
51
- setting(:default_locale, nil)
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(:validate_keys, false)
61
+ setting :validate_keys, default: false
62
62
 
63
63
  # @api private
64
64
  def respond_to_missing?(meth, include_private = false)
@@ -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 (`Params`, `JSON` or a custom sub-class)
84
- # @option options [Compiler] :compiler An instance of a rule compiler (must be compatible with `Schema::Compiler`) (optional)
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 any other `Macros::Key` subclass)
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.map { |m| [m.name, m.to_rule] }.to_h.compact)
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
- .hints
42
- .reject { |hint| msg == hint }
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
- class Or::SinglePath
26
- # @api private
27
- def hint?
28
- false
25
+ module Or
26
+ class SinglePath
27
+ # @api private
28
+ def hint?
29
+ false
30
+ end
29
31
  end
30
- end
31
32
 
32
- class Or::MultiPath
33
- # @api private
34
- def hint?
35
- false
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
- keys[key][:type] = type if type
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
@@ -18,7 +18,7 @@ module Dry
18
18
  end
19
19
 
20
20
  super(args[0].schema, *args.drop(1))
21
- type(schema_dsl.types[name].constructor(args[0]))
21
+ type(schema_dsl.types[name].constructor(args[0].schema))
22
22
  else
23
23
  super
24
24
  end
@@ -15,3 +15,7 @@ end
15
15
  Dry::Schema.register_extension(:info) do
16
16
  require "dry/schema/extensions/info"
17
17
  end
18
+
19
+ Dry::Schema.register_extension(:json_schema) do
20
+ require "dry/schema/extensions/json_schema"
21
+ end