dry-validation 0.7.4 → 0.8.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.
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)