dry-validation 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +9 -6
  4. data/CHANGELOG.md +58 -0
  5. data/Gemfile +3 -3
  6. data/benchmarks/benchmark_form_invalid.rb +64 -0
  7. data/benchmarks/benchmark_form_valid.rb +64 -0
  8. data/benchmarks/profile_schema_call_invalid.rb +20 -0
  9. data/benchmarks/profile_schema_call_valid.rb +20 -0
  10. data/benchmarks/profile_schema_definition.rb +14 -0
  11. data/benchmarks/profile_schema_messages_invalid.rb +20 -0
  12. data/benchmarks/suite.rb +5 -0
  13. data/config/errors.yml +12 -5
  14. data/dry-validation.gemspec +2 -2
  15. data/examples/basic.rb +2 -2
  16. data/examples/form.rb +2 -2
  17. data/examples/json.rb +2 -2
  18. data/examples/nested.rb +6 -6
  19. data/lib/dry/validation.rb +22 -3
  20. data/lib/dry/validation/deprecations.rb +23 -0
  21. data/lib/dry/validation/error_compiler.rb +31 -11
  22. data/lib/dry/validation/error_compiler/input.rb +44 -57
  23. data/lib/dry/validation/hint_compiler.rb +15 -7
  24. data/lib/dry/validation/input_processor_compiler.rb +13 -6
  25. data/lib/dry/validation/input_processor_compiler/form.rb +2 -0
  26. data/lib/dry/validation/input_processor_compiler/sanitizer.rb +1 -1
  27. data/lib/dry/validation/messages/abstract.rb +9 -1
  28. data/lib/dry/validation/predicate_registry.rb +101 -0
  29. data/lib/dry/validation/result.rb +8 -1
  30. data/lib/dry/validation/schema.rb +110 -44
  31. data/lib/dry/validation/schema/check.rb +4 -2
  32. data/lib/dry/validation/schema/deprecated.rb +31 -0
  33. data/lib/dry/validation/schema/dsl.rb +18 -11
  34. data/lib/dry/validation/schema/form.rb +1 -0
  35. data/lib/dry/validation/schema/json.rb +1 -0
  36. data/lib/dry/validation/schema/key.rb +23 -10
  37. data/lib/dry/validation/schema/rule.rb +81 -20
  38. data/lib/dry/validation/schema/value.rb +110 -25
  39. data/lib/dry/validation/version.rb +1 -1
  40. data/spec/fixtures/locales/en.yml +1 -0
  41. data/spec/fixtures/locales/pl.yml +1 -1
  42. data/spec/integration/custom_error_messages_spec.rb +2 -2
  43. data/spec/integration/custom_predicates_spec.rb +98 -15
  44. data/spec/integration/error_compiler_spec.rb +111 -96
  45. data/spec/integration/form/predicates/array_spec.rb +243 -0
  46. data/spec/integration/form/predicates/empty_spec.rb +263 -0
  47. data/spec/integration/form/predicates/eql_spec.rb +327 -0
  48. data/spec/integration/form/predicates/even_spec.rb +455 -0
  49. data/spec/integration/form/predicates/excluded_from_spec.rb +455 -0
  50. data/spec/integration/form/predicates/excludes_spec.rb +391 -0
  51. data/spec/integration/form/predicates/false_spec.rb +455 -0
  52. data/spec/integration/form/predicates/filled_spec.rb +467 -0
  53. data/spec/integration/form/predicates/format_spec.rb +454 -0
  54. data/spec/integration/form/predicates/gt_spec.rb +519 -0
  55. data/spec/integration/form/predicates/gteq_spec.rb +519 -0
  56. data/spec/integration/form/predicates/included_in_spec.rb +455 -0
  57. data/spec/integration/form/predicates/includes_spec.rb +391 -0
  58. data/spec/integration/form/predicates/key_spec.rb +75 -0
  59. data/spec/integration/form/predicates/lt_spec.rb +519 -0
  60. data/spec/integration/form/predicates/lteq_spec.rb +519 -0
  61. data/spec/integration/form/predicates/max_size_spec.rb +391 -0
  62. data/spec/integration/form/predicates/min_size_spec.rb +391 -0
  63. data/spec/integration/form/predicates/none_spec.rb +265 -0
  64. data/spec/integration/form/predicates/not_eql_spec.rb +327 -0
  65. data/spec/integration/form/predicates/odd_spec.rb +455 -0
  66. data/spec/integration/form/predicates/size/fixed_spec.rb +399 -0
  67. data/spec/integration/form/predicates/size/range_spec.rb +398 -0
  68. data/spec/integration/form/predicates/true_spec.rb +455 -0
  69. data/spec/integration/form/predicates/type_spec.rb +391 -0
  70. data/spec/integration/hints_spec.rb +90 -4
  71. data/spec/integration/injecting_rules_spec.rb +2 -2
  72. data/spec/integration/localized_error_messages_spec.rb +2 -2
  73. data/spec/integration/messages/i18n_spec.rb +3 -3
  74. data/spec/integration/optional_keys_spec.rb +3 -3
  75. data/spec/integration/schema/array_schema_spec.rb +49 -13
  76. data/spec/integration/schema/check_rules_spec.rb +6 -6
  77. data/spec/integration/schema/check_with_nested_el_spec.rb +3 -3
  78. data/spec/integration/schema/check_with_nth_el_spec.rb +1 -1
  79. data/spec/integration/schema/each_with_set_spec.rb +3 -3
  80. data/spec/integration/schema/extending_dsl_spec.rb +27 -0
  81. data/spec/integration/schema/form/explicit_types_spec.rb +182 -0
  82. data/spec/integration/schema/form_spec.rb +90 -18
  83. data/spec/integration/schema/hash_schema_spec.rb +47 -0
  84. data/spec/integration/schema/inheriting_schema_spec.rb +4 -4
  85. data/spec/integration/schema/input_processor_spec.rb +8 -8
  86. data/spec/integration/schema/json/explicit_types_spec.rb +157 -0
  87. data/spec/integration/schema/json_spec.rb +18 -18
  88. data/spec/integration/schema/macros/confirmation_spec.rb +1 -1
  89. data/spec/integration/schema/macros/each_spec.rb +177 -43
  90. data/spec/integration/schema/macros/{required_spec.rb → filled_spec.rb} +34 -6
  91. data/spec/integration/schema/macros/input_spec.rb +21 -0
  92. data/spec/integration/schema/macros/maybe_spec.rb +39 -2
  93. data/spec/integration/schema/macros/value_spec.rb +105 -0
  94. data/spec/integration/schema/macros/when_spec.rb +28 -8
  95. data/spec/integration/schema/nested_values_spec.rb +11 -8
  96. data/spec/integration/schema/not_spec.rb +2 -2
  97. data/spec/integration/schema/numbers_spec.rb +1 -1
  98. data/spec/integration/schema/option_with_default_spec.rb +1 -1
  99. data/spec/integration/schema/predicate_verification_spec.rb +9 -0
  100. data/spec/integration/schema/predicates/array_spec.rb +295 -0
  101. data/spec/integration/schema/predicates/custom_spec.rb +103 -0
  102. data/spec/integration/schema/predicates/empty_spec.rb +263 -0
  103. data/spec/integration/schema/predicates/eql_spec.rb +327 -0
  104. data/spec/integration/schema/predicates/even_spec.rb +455 -0
  105. data/spec/integration/schema/predicates/excluded_from_spec.rb +455 -0
  106. data/spec/integration/schema/predicates/excludes_spec.rb +391 -0
  107. data/spec/integration/schema/predicates/filled_spec.rb +467 -0
  108. data/spec/integration/schema/predicates/format_spec.rb +455 -0
  109. data/spec/integration/schema/predicates/gt_spec.rb +519 -0
  110. data/spec/integration/schema/predicates/gteq_spec.rb +519 -0
  111. data/spec/integration/schema/predicates/hash_spec.rb +69 -0
  112. data/spec/integration/schema/predicates/included_in_spec.rb +455 -0
  113. data/spec/integration/schema/predicates/includes_spec.rb +391 -0
  114. data/spec/integration/schema/predicates/key_spec.rb +88 -0
  115. data/spec/integration/schema/predicates/lt_spec.rb +520 -0
  116. data/spec/integration/schema/predicates/lteq_spec.rb +519 -0
  117. data/spec/integration/schema/predicates/max_size_spec.rb +391 -0
  118. data/spec/integration/schema/predicates/min_size_spec.rb +391 -0
  119. data/spec/integration/schema/predicates/none_spec.rb +265 -0
  120. data/spec/integration/schema/predicates/not_eql_spec.rb +391 -0
  121. data/spec/integration/schema/predicates/odd_spec.rb +455 -0
  122. data/spec/integration/schema/predicates/size/fixed_spec.rb +401 -0
  123. data/spec/integration/schema/predicates/size/range_spec.rb +399 -0
  124. data/spec/integration/schema/predicates/type_spec.rb +391 -0
  125. data/spec/integration/schema/reusing_schema_spec.rb +4 -4
  126. data/spec/integration/schema/using_types_spec.rb +24 -6
  127. data/spec/integration/schema/xor_spec.rb +2 -2
  128. data/spec/integration/schema_builders_spec.rb +15 -0
  129. data/spec/integration/schema_spec.rb +37 -12
  130. data/spec/shared/predicate_helper.rb +13 -0
  131. data/spec/spec_helper.rb +10 -0
  132. data/spec/support/matchers.rb +38 -0
  133. data/spec/support/predicates_integration.rb +7 -0
  134. data/spec/unit/hint_compiler_spec.rb +10 -8
  135. data/spec/unit/input_processor_compiler/form_spec.rb +45 -43
  136. data/spec/unit/input_processor_compiler/json_spec.rb +37 -35
  137. data/spec/unit/predicate_registry_spec.rb +34 -0
  138. data/spec/unit/schema/key_spec.rb +12 -14
  139. data/spec/unit/schema/rule_spec.rb +4 -2
  140. data/spec/unit/schema/value_spec.rb +38 -121
  141. metadata +150 -16
  142. data/lib/dry/validation/schema/attr.rb +0 -15
  143. data/spec/integration/attr_spec.rb +0 -122
data/examples/nested.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  require 'dry-validation'
2
2
 
3
3
  schema = Dry::Validation.Schema do
4
- key(:address).schema do
5
- key(:city).required(min_size?: 3)
4
+ required(:address).schema do
5
+ required(:city).filled(min_size?: 3)
6
6
 
7
- key(:street).required
7
+ required(:street).filled
8
8
 
9
- key(:country).schema do
10
- key(:name).required
11
- key(:code).required
9
+ required(:country).schema do
10
+ required(:name).filled
11
+ required(:code).filled
12
12
  end
13
13
  end
14
14
  end
@@ -9,26 +9,45 @@ require 'dry/validation/schema/json'
9
9
  module Dry
10
10
  module Validation
11
11
  MissingMessageError = Class.new(StandardError)
12
+ InvalidSchemaError = Class.new(StandardError)
12
13
 
13
14
  def self.messages_paths
14
15
  Messages::Abstract.config.paths
15
16
  end
16
17
 
17
18
  def self.Schema(base = Schema, **options, &block)
19
+ schema_class = Class.new(base.is_a?(Schema) ? base.class : base)
20
+
18
21
  dsl_opts = {
19
- schema_class: Class.new(base.is_a?(Schema) ? base.class : base),
22
+ schema_class: schema_class,
23
+ registry: schema_class.registry,
20
24
  parent: options[:parent]
21
25
  }
22
26
 
27
+ dsl_ext = schema_class.config.dsl_extensions
28
+
23
29
  dsl = Schema::Value.new(dsl_opts)
24
- dsl.instance_exec(&block)
30
+ dsl_ext.__send__(:extend_object, dsl) if dsl_ext
31
+ dsl.predicates(options[:predicates]) if options.key?(:predicates)
32
+ dsl.instance_exec(&block) if block
25
33
 
26
34
  klass = dsl.schema_class
27
35
 
36
+ base_rules = klass.config.rules + (options.fetch(:rules, []) + dsl.rules)
37
+
38
+ rules =
39
+ if klass.config.input
40
+ input_rule = dsl.__send__(klass.config.input)
41
+ [input_rule.and(dsl.with(rules: base_rules))]
42
+ else
43
+ base_rules
44
+ end
45
+
28
46
  klass.configure do |config|
29
- config.rules = config.rules + (options.fetch(:rules, []) + dsl.rules)
47
+ config.rules = rules
30
48
  config.checks = config.checks + dsl.checks
31
49
  config.path = dsl.path
50
+ config.type_map = klass.build_type_map(dsl.type_map) if config.type_specs
32
51
  end
33
52
 
34
53
  if options[:build] == false
@@ -0,0 +1,23 @@
1
+ require 'logger'
2
+
3
+ module Dry
4
+ module Validation
5
+ module Deprecations
6
+ extend Dry::Configurable
7
+
8
+ setting :logger, Logger.new($stdout)
9
+
10
+ def self.format(msg, caller)
11
+ "#{msg} [#{caller[1].split(':')[0..1].join(' line ')}]"
12
+ end
13
+
14
+ def logger
15
+ @logger ||= Deprecations.config.logger
16
+ end
17
+
18
+ def warn(msg)
19
+ logger.warn(Deprecations.format(msg, ::Kernel.caller))
20
+ end
21
+ end
22
+ end
23
+ end
@@ -10,7 +10,7 @@ module Dry
10
10
  def initialize(messages, options = {})
11
11
  @messages = messages
12
12
  @options = Hash[options]
13
- @hints = @options.fetch(:hints, {})
13
+ @hints = @options.fetch(:hints, DEFAULT_RESULT)
14
14
  @full = options.fetch(:full, false)
15
15
  end
16
16
 
@@ -49,8 +49,12 @@ module Dry
49
49
 
50
50
  if result.is_a?(Array)
51
51
  merge(result)
52
- else
52
+ elsif !schema
53
53
  merge_hints(result)
54
+ elsif schema
55
+ merge_hints(result, hints[schema] || DEFAULT_RESULT)
56
+ else
57
+ result
54
58
  end
55
59
  end
56
60
  end
@@ -84,18 +88,34 @@ module Dry
84
88
  visit(node)
85
89
  end
86
90
 
91
+ def dump_messages(hash)
92
+ hash.each_with_object({}) do |(key, val), res|
93
+ res[key] =
94
+ case val
95
+ when Hash then dump_messages(val)
96
+ when Array then val.map(&:to_s)
97
+ end
98
+ end
99
+ end
100
+
87
101
  private
88
102
 
89
- def merge_hints(messages)
103
+ def merge_hints(messages, hints = self.hints)
90
104
  messages.each_with_object({}) do |(name, msgs), res|
91
- if msgs.is_a?(Hash)
92
- res[name] = merge_hints(msgs)
93
- else
94
- all_msgs = msgs + (hints[name] || EMPTY_HINTS)
95
- all_msgs.uniq!
96
-
97
- res[name] = all_msgs
98
- end
105
+ res[name] =
106
+ if msgs.is_a?(Hash)
107
+ res[name] = merge_hints(msgs, hints)
108
+ else
109
+ all_hints = (hints[name] || EMPTY_HINTS)
110
+
111
+ if all_hints.is_a?(Array)
112
+ all_msgs = msgs + all_hints
113
+ all_msgs.uniq!(&:signature)
114
+ all_msgs
115
+ else
116
+ msgs
117
+ end
118
+ end
99
119
  end
100
120
  end
101
121
 
@@ -1,6 +1,24 @@
1
+ require 'dry/validation/deprecations'
2
+
1
3
  module Dry
2
4
  module Validation
5
+ Message = Struct.new(:rule, :predicate, :text) do
6
+ def to_s
7
+ text
8
+ end
9
+
10
+ def signature
11
+ @signature ||= [rule, predicate].hash
12
+ end
13
+
14
+ def eql?(other)
15
+ other.is_a?(String) ? text == other : super
16
+ end
17
+ end
18
+
3
19
  class ErrorCompiler::Input < ErrorCompiler
20
+ include Deprecations
21
+
4
22
  attr_reader :name, :input, :rule, :val_type
5
23
 
6
24
  def initialize(messages, options)
@@ -15,7 +33,7 @@ module Dry
15
33
  node.map { |el| visit(el) }
16
34
  end
17
35
 
18
- def visit_set(node)
36
+ def visit_set(node, *)
19
37
  result = node.map do |input|
20
38
  visit(input)
21
39
  end
@@ -24,8 +42,8 @@ module Dry
24
42
 
25
43
  def visit_el(node)
26
44
  idx, el = node
27
- name = [*Array(name), idx]
28
- visit(el, name)
45
+ path = [*Array(name), idx]
46
+ input_visitor(path, input[idx]).visit(el)
29
47
  end
30
48
 
31
49
  def visit_check(node)
@@ -37,7 +55,7 @@ module Dry
37
55
  predicate, args = node
38
56
 
39
57
  lookup_options = options.merge(
40
- rule: rule, val_type: val_type, arg_type: args[0].class
58
+ rule: rule, val_type: val_type, arg_type: args.size > 0 && args[0][1].class
41
59
  )
42
60
 
43
61
  tokens = options_for(predicate, args)
@@ -54,87 +72,56 @@ module Dry
54
72
  rule
55
73
  end
56
74
 
57
- message =
75
+ text =
58
76
  if full?
59
- "#{rule_name} #{template % tokens}"
77
+ "#{rule_name || tokens[:name]} #{template % tokens}"
60
78
  else
61
79
  template % tokens
62
80
  end
63
81
 
82
+ *arg_vals, _ = args.map(&:last)
83
+ message = Message.new(rule, [predicate, arg_vals], text)
64
84
  path = [[message], *[tokens[:name], *Array(name).reverse].uniq]
65
85
 
66
86
  path.reduce { |a, e| { e => a } }
67
87
  end
68
88
 
69
- def options_for_type?(*args)
70
- { type: args[0][0] }
71
- end
72
-
73
- def options_for_key?(*args)
74
- { name: args[0][0] }
75
- end
76
-
77
- def options_for_attr?(*args)
78
- { name: args[0][0] }
79
- end
80
-
81
- def options_for_exclusion?(*args)
82
- { list: args[0][0].join(', ') }
83
- end
84
-
85
- def options_for_inclusion?(*args)
86
- { list: args[0][0].join(', ') }
87
- end
88
-
89
- def options_for_gt?(*args)
90
- { num: args[0][0], value: input }
91
- end
92
-
93
- def options_for_gteq?(*args)
94
- { num: args[0][0], value: input }
95
- end
96
-
97
- def options_for_lt?(*args)
98
- { num: args[0][0], value: input }
99
- end
100
-
101
- def options_for_lteq?(*args)
102
- { num: args[0][0], value: input }
103
- end
104
-
105
- def options_for_int?(*args)
106
- { num: args[0][0], value: input }
89
+ def options_for_inclusion?(args)
90
+ warn 'inclusion is deprecated - use included_in instead.'
91
+ options_for_included_in?(args)
107
92
  end
108
93
 
109
- def options_for_max_size?(*args)
110
- { num: args[0][0], value: input }
94
+ def options_for_exclusion?(args)
95
+ warn 'exclusion is deprecated - use excluded_from instead.'
96
+ options_for_excluded_from?(args)
111
97
  end
112
98
 
113
- def options_for_min_size?(*args)
114
- { num: args[0][0], value: input }
99
+ def options_for_excluded_from?(args)
100
+ { list: args[:list].join(', ') }
115
101
  end
116
102
 
117
- def options_for_eql?(*args)
118
- { eql_value: args[0][0], value: input }
103
+ def options_for_included_in?(args)
104
+ { list: args[:list].join(', ') }
119
105
  end
120
106
 
121
- def options_for_size?(*args)
122
- num = args[0][0]
107
+ def options_for_size?(args)
108
+ size = args[:size]
123
109
 
124
- if num.is_a?(Range)
125
- { left: num.first, right: num.last, value: input }
110
+ if size.is_a?(Range)
111
+ { left: size.first, right: size.last }
126
112
  else
127
- { num: args[0][0], value: input }
113
+ args
128
114
  end
129
115
  end
130
116
 
131
117
  def options_for(predicate, args)
132
118
  meth = :"options_for_#{predicate}"
133
119
 
134
- defaults = { name: rule, rule: rule, value: input }
120
+ args_map = Hash[args]
121
+ defaults = { name: rule, rule: rule, value: input }.update(args_map)
135
122
 
136
123
  if respond_to?(meth)
137
- defaults.merge!(__send__(meth, args))
124
+ defaults.merge!(__send__(meth, args_map))
138
125
  end
139
126
 
140
127
  defaults
@@ -5,7 +5,7 @@ module Dry
5
5
  class HintCompiler < ErrorCompiler::Input
6
6
  include Dry::Equalizer(:messages, :rules, :options)
7
7
 
8
- attr_reader :rules, :excluded
8
+ attr_reader :rules, :excluded, :cache
9
9
 
10
10
  TYPES = {
11
11
  none?: NilClass,
@@ -23,25 +23,33 @@ module Dry
23
23
 
24
24
  EXCLUDED = [:none?, :filled?, :key?].freeze
25
25
 
26
+ DEFAULT_OPTIONS = { name: nil, input: nil, message_type: :hint }.freeze
27
+
28
+ EMPTY_MESSAGES = {}.freeze
29
+
26
30
  def self.cache
27
31
  @cache ||= Concurrent::Map.new
28
32
  end
29
33
 
30
34
  def initialize(messages, options = {})
31
- super(messages, { name: nil, input: nil }.merge(options))
35
+ super(messages, DEFAULT_OPTIONS.merge(options))
32
36
  @rules = @options.delete(:rules)
33
37
  @excluded = @options.fetch(:excluded, EXCLUDED)
34
38
  @val_type = options[:val_type]
39
+ @cache = self.class.cache
40
+ end
41
+
42
+ def hash
43
+ @hash ||= [messages, rules, options].hash
35
44
  end
36
45
 
37
46
  def with(new_options)
47
+ return self if new_options.empty?
38
48
  super(new_options.merge(rules: rules))
39
49
  end
40
50
 
41
51
  def call
42
- self.class.cache.fetch_or_store(hash) do
43
- super(rules)
44
- end
52
+ cache.fetch_or_store(hash) { super(rules) }
45
53
  end
46
54
 
47
55
  def visit_predicate(node)
@@ -50,7 +58,7 @@ module Dry
50
58
  val_type = TYPES[predicate]
51
59
 
52
60
  return with(val_type: val_type) if val_type
53
- return {} if excluded.include?(predicate)
61
+ return EMPTY_MESSAGES if excluded.include?(predicate)
54
62
 
55
63
  super
56
64
  end
@@ -99,7 +107,7 @@ module Dry
99
107
  end
100
108
 
101
109
  def visit_schema(node)
102
- DEFAULT_RESULT
110
+ merge(node.rule_ast.map(&method(:visit)))
103
111
  end
104
112
 
105
113
  def visit_check(node)
@@ -27,13 +27,15 @@ module Dry
27
27
  def visit_type(type, *args)
28
28
  if type.is_a?(Types::Constructor)
29
29
  [:constructor, [type.primitive, type.fn]]
30
+ elsif type.respond_to?(:rule)
31
+ visit(type.rule.to_ast, *args)
30
32
  else
31
- DEFAULT_TYPE_NODE
33
+ DEFAULT_TYPE_NODE.first
32
34
  end
33
35
  end
34
36
 
35
- def visit_schema(node, *args)
36
- hash_node(node.input_processor_ast(identifier))
37
+ def visit_schema(schema, *args)
38
+ hash_node(schema.input_processor_ast(identifier))
37
39
  end
38
40
 
39
41
  def visit_or(node, *args)
@@ -44,7 +46,12 @@ module Dry
44
46
  def visit_and(node, first = true)
45
47
  if first
46
48
  name, type = node.map { |n| visit(n, false) }.uniq
47
- [:key, [name, type]]
49
+
50
+ if name.is_a?(Array)
51
+ type
52
+ else
53
+ [:key, [name, type]]
54
+ end
48
55
  else
49
56
  result = node.map { |n| visit(n, first) }.uniq
50
57
 
@@ -82,9 +89,9 @@ module Dry
82
89
  id, args = node
83
90
 
84
91
  if id == :key?
85
- args[0]
92
+ args[0][1]
86
93
  else
87
- type(id, args)
94
+ type(id, args.map(&:last))
88
95
  end
89
96
  end
90
97
 
@@ -5,6 +5,8 @@ module Dry
5
5
  default: 'string',
6
6
  none?: 'form.nil',
7
7
  bool?: 'form.bool',
8
+ true?: 'form.true',
9
+ false?: 'form.false',
8
10
  str?: 'string',
9
11
  int?: 'form.int',
10
12
  float?: 'form.float',
@@ -37,7 +37,7 @@ module Dry
37
37
  end
38
38
 
39
39
  def hash_node(schema)
40
- [:type, ['hash', [:schema, schema]]]
40
+ [:type, ['hash', [:weak, schema]]]
41
41
  end
42
42
 
43
43
  def array_node(members)