attributor 5.4 → 6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/lib/attributor/attribute.rb +101 -84
  4. data/lib/attributor/extras/field_selector.rb +4 -0
  5. data/lib/attributor/families/numeric.rb +19 -6
  6. data/lib/attributor/families/temporal.rb +16 -9
  7. data/lib/attributor/hash_dsl_compiler.rb +6 -5
  8. data/lib/attributor/type.rb +26 -3
  9. data/lib/attributor/types/bigdecimal.rb +6 -1
  10. data/lib/attributor/types/boolean.rb +5 -0
  11. data/lib/attributor/types/collection.rb +19 -0
  12. data/lib/attributor/types/csv.rb +4 -0
  13. data/lib/attributor/types/date.rb +7 -1
  14. data/lib/attributor/types/date_time.rb +7 -1
  15. data/lib/attributor/types/float.rb +4 -3
  16. data/lib/attributor/types/hash.rb +86 -23
  17. data/lib/attributor/types/integer.rb +7 -1
  18. data/lib/attributor/types/model.rb +9 -21
  19. data/lib/attributor/types/object.rb +5 -0
  20. data/lib/attributor/types/polymorphic.rb +0 -1
  21. data/lib/attributor/types/string.rb +19 -0
  22. data/lib/attributor/types/symbol.rb +5 -0
  23. data/lib/attributor/types/tempfile.rb +4 -0
  24. data/lib/attributor/types/time.rb +6 -2
  25. data/lib/attributor/types/uri.rb +8 -0
  26. data/lib/attributor/version.rb +1 -1
  27. data/lib/attributor.rb +3 -7
  28. data/spec/attribute_spec.rb +148 -124
  29. data/spec/extras/field_selector/field_selector_spec.rb +9 -0
  30. data/spec/hash_dsl_compiler_spec.rb +5 -5
  31. data/spec/spec_helper.rb +0 -2
  32. data/spec/support/integers.rb +7 -0
  33. data/spec/support/models.rb +7 -7
  34. data/spec/types/bigdecimal_spec.rb +8 -0
  35. data/spec/types/boolean_spec.rb +10 -0
  36. data/spec/types/collection_spec.rb +16 -0
  37. data/spec/types/date_spec.rb +9 -0
  38. data/spec/types/date_time_spec.rb +9 -0
  39. data/spec/types/float_spec.rb +8 -0
  40. data/spec/types/hash_spec.rb +181 -22
  41. data/spec/types/integer_spec.rb +9 -0
  42. data/spec/types/model_spec.rb +7 -1
  43. data/spec/types/string_spec.rb +10 -0
  44. data/spec/types/temporal_spec.rb +5 -1
  45. data/spec/types/time_spec.rb +9 -0
  46. data/spec/types/uri_spec.rb +9 -0
  47. metadata +5 -6
  48. data/lib/attributor/attribute_resolver.rb +0 -111
  49. data/spec/attribute_resolver_spec.rb +0 -237
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb9d8a77a4e71c5f44cc2eaa6559c7d40882c88ec675b0400170d961386d66c1
4
- data.tar.gz: 0b4d4bec370cbca4fa15d61dd08b0bc9b2cdcb8f1f0d39d21287e7471f60075e
3
+ metadata.gz: 108abdb1d351f95b78cfae24b61bf8e5bf3943626d8badb691e28ce0df23c45d
4
+ data.tar.gz: 96f4e678e7bccb85526a7a3788aa2df538902200fcb50b49e0de3a36c5d3778d
5
5
  SHA512:
6
- metadata.gz: 78a4367ada6bebcb2a164da33b08e05bd0c20207d11df7631b700346bb3e4ec74df47bef3373178cbd619a2f0252609c59586a1fb46c0913830a57b547361b67
7
- data.tar.gz: e2a0fa2ecb088ff61c184624fcadfcc992d1fd60c5694727e8ffd594be1188ee6d60e2b14d7cc1daab4a9e8fbd67b584612d3be6e7574fbda4cfca004a182e00
6
+ metadata.gz: 54217688f13fad04f249aa351062afd490dd3962278f7dbdcc5d31e05bd5df3151fc51a704208a1502d8d9cf67a2382ca10b85f331c577610223a39c7b96caa9
7
+ data.tar.gz: 8eb61d8a53f01d2529d1d2f15c77cd9b55a6a0f53f9a9f83db61ee87ae6091761c330ca026759146e05b73b43625fa5f51236e50a98cbee5aa55afe94b6f1c25
data/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 6.0 (22/11/2021)
6
+ - removed `required_if` support and all of the necessary code.
7
+ - changed the semantics of the `required:` option in attributes, to really mean if the "key" is required to be passed in or not (i.e., check if the key is null, not if its value is null)
8
+ - Introduced a new option`null: true|false` to allow for the value of an attribute to be nullable or not when the attribute is passed in.
9
+ * The default behavior for an attribute nullability currently `null: false` (but it can be easily changed by overriding the `Attributor::Attribute.default_for_null` function to return `true`)
10
+ ## 5.7 (1/7/2021)
11
+
12
+ - added `custom_option` to Attributor::Attribute class, accepting a name and Attribute arguments that will be used to validate the option value(s) provided.
13
+
14
+ ## 5.6 (11/02/2020)
15
+
16
+ - Small fixes for dumping JSON-schema default values if they're Proc's or dumpable objects
17
+
18
+ ## 5.5 (21/08/2020)
19
+
20
+ - JSON-schema support. Enhanced all of the types to suppport describing themselves as JSON-schema format (`.as_json_schema`). This description includes a few `x-*` extensions to capture some information that Attributor supports, that JSON-schema does not.
21
+
5
22
  ## 5.3 (24/04/2020)
6
23
 
7
24
  - Fixed deprecation warnings in Ruby 2.7
@@ -17,18 +17,30 @@ module Attributor
17
17
  FakeParent
18
18
  end
19
19
  end
20
+
20
21
  # It is the abstract base class to hold an attribute, both a leaf and a container (hash/Array...)
21
22
  # TODO: should this be a mixin since it is an abstract class?
22
23
  class Attribute
23
24
  attr_reader :type, :options
24
25
 
26
+ @custom_options = {}
27
+
28
+ class << self
29
+ attr_accessor :custom_options
30
+ end
31
+
32
+ def self.custom_option(name, attr_type, options = {}, &block)
33
+ if TOP_LEVEL_OPTIONS.include?(name) || INTERNAL_OPTIONS.include?(name)
34
+ raise ArgumentError, "can not define custom_option with name #{name.inspect}, it is reserved by Attributor"
35
+ end
36
+ self.custom_options[name] = Attributor::Attribute.new(attr_type, options, &block)
37
+ end
38
+
25
39
  # @options: metadata about the attribute
26
40
  # @block: code definition for struct attributes (nil for predefined types or leaf/simple types)
27
41
  def initialize(type, options = {}, &block)
28
42
  @type = Attributor.resolve_type(type, options, block)
29
-
30
- @options = options
31
- @options = @type.options.merge(@options) if @type.respond_to?(:options)
43
+ @options = @type.respond_to?(:options) ? @type.options.merge(options) : options
32
44
 
33
45
  check_options!
34
46
  end
@@ -82,23 +94,21 @@ module Attributor
82
94
 
83
95
  def validate_type(value, context)
84
96
  # delegate check to type subclass if it exists
85
- unless type.valid_type?(value)
86
- msg = "Attribute #{Attributor.humanize_context(context)} received value: "
87
- msg += "#{Attributor.errorize_value(value)} is of the wrong type "
88
- msg += "(got: #{value.class.name}, expected: #{type.name})"
89
- return [msg]
90
- end
91
- []
97
+ return [] if value.nil? || type.valid_type?(value)
98
+
99
+ msg = "Attribute #{Attributor.humanize_context(context)} received value: "
100
+ msg += "#{Attributor.errorize_value(value)} is of the wrong type "
101
+ msg += "(got: #{value.class.name}, expected: #{type.name})"
102
+ [msg]
92
103
  end
93
104
 
94
- TOP_LEVEL_OPTIONS = [:description, :values, :default, :example, :required, :required_if, :custom_data].freeze
105
+ TOP_LEVEL_OPTIONS = [:description, :values, :default, :example, :required, :null, :custom_data].freeze
95
106
  INTERNAL_OPTIONS = [:dsl_compiler, :dsl_compiler_options].freeze # Options we don't want to expose when describing attributes
96
-
97
- def describe(shallow = true, example: nil)
98
- description = {}
107
+ def describe(shallow=true, example: nil)
108
+ description = { }
99
109
  # Clone the common options
100
110
  TOP_LEVEL_OPTIONS.each do |option_name|
101
- description[option_name] = options[option_name] if options.key? option_name
111
+ description[option_name] = self.describe_option(option_name) if self.options.has_key? option_name
102
112
  end
103
113
 
104
114
  # Make sure this option definition is not mistaken for the real generated example
@@ -109,7 +119,7 @@ module Attributor
109
119
  special_options = options.keys - TOP_LEVEL_OPTIONS - INTERNAL_OPTIONS
110
120
  description[:options] = {} unless special_options.empty?
111
121
  special_options.each do |opt_name|
112
- description[:options][opt_name] = options[opt_name]
122
+ description[:options][opt_name] = self.describe_option(opt_name)
113
123
  end
114
124
  # Change the reference option to the actual class name.
115
125
  if (reference = options[:reference])
@@ -146,8 +156,45 @@ module Attributor
146
156
  load(generated, context)
147
157
  end
148
158
 
149
- def example(context = nil, parent: nil, values: {})
150
- raise ArgumentError, 'attribute example cannot take a context of type String' if context.is_a? ::String
159
+ def describe_option( option_name )
160
+ self.type.describe_option( option_name, self.options[option_name] )
161
+ end
162
+
163
+ # FiXME: pass and utilize the "shallow" parameter
164
+ #required
165
+ #options
166
+ #type
167
+ #example
168
+ # UTILIZE THIS SITE! http://jsonschema.net/#/
169
+ def as_json_schema(shallow: true, example: nil)
170
+ description = self.type.as_json_schema(shallow: shallow, example: example, attribute_options: self.options )
171
+
172
+ description[:description] = self.options[:description] if self.options[:description]
173
+ description[:enum] = self.options[:values] if self.options[:values]
174
+ if the_default = self.options[:default]
175
+ the_object = the_default.is_a?(Proc) ? the_default.call : the_default
176
+ description[:default] = the_object.is_a?(Attributor::Dumpable) ? the_object.dump : the_object
177
+ end
178
+ #TODO description[:title] = "TODO: do we want to use a title??..."
179
+
180
+ # Change the reference option to the actual class name.
181
+ if ( reference = self.options[:reference] )
182
+ description[:'x-reference'] = reference.name
183
+ end
184
+
185
+ # TODO: not sure if that's correct (we used to get it from the described hash...
186
+ description[:example] = self.dump(example) if example
187
+
188
+ # add custom options as x-optionname
189
+ self.class.custom_options.each do |name, _|
190
+ description["x-#{name}".to_sym] = self.options[name] if self.options.key?(name)
191
+ end
192
+
193
+ description
194
+ end
195
+
196
+ def example(context=nil, parent: nil, values:{})
197
+ raise ArgumentError, "attribute example cannot take a context of type String" if (context.is_a? ::String )
151
198
  if context
152
199
  ctx = Attributor.humanize_context(context)
153
200
  seed, = Digest::SHA1.digest(ctx).unpack('QQ')
@@ -177,78 +224,38 @@ module Attributor
177
224
  type.attributes if @type_has_attributes ||= type.respond_to?(:attributes)
178
225
  end
179
226
 
227
+ # Default value for a non-specified null: option
228
+ def self.default_for_null
229
+ false
230
+ end
231
+
232
+ # It is only nullable if there is an explicit null: true (or if it's not passed/set, and the default is true)
233
+ def self.nullable_attribute?(options)
234
+ !options.key?(:null) ? default_for_null : options[:null]
235
+ end
236
+
180
237
  # Validates stuff and checks dependencies
181
238
  def validate(object, context = Attributor::DEFAULT_ROOT_CONTEXT)
182
239
  raise "INVALID CONTEXT!! #{context}" unless context
183
240
  # Validate any requirements, absolute or conditional, and return.
184
241
 
185
- if object.nil? # == Attributor::UNSET
186
- # With no value, we can only validate whether that is acceptable or not and return.
187
- # Beyond that, no further validation should be done.
188
- return validate_missing_value(context)
189
- end
190
-
191
- # TODO: support validation for other types of conditional dependencies based on values of other attributes
192
-
193
- errors = validate_type(object, context)
194
-
195
- # End validation if we don't even have the proper type to begin with
196
- return errors if errors.any?
197
-
198
- if options[:values] && !options[:values].include?(object)
199
- errors << "Attribute #{Attributor.humanize_context(context)}: #{Attributor.errorize_value(object)} is not within the allowed values=#{options[:values].inspect} "
200
- end
201
-
202
- errors + type.validate(object, context, self)
203
- end
204
-
205
- def validate_missing_value(context)
206
- raise "INVALID CONTEXT!!! (got: #{context.inspect})" unless context.is_a? Enumerable
207
-
208
- # Missing attribute was required if :required option was set
209
- return ["Attribute #{Attributor.humanize_context(context)} is required"] if options[:required]
210
-
211
- # Missing attribute was not required if :required_if (and :required)
212
- # option was NOT set
213
- requirement = options[:required_if]
214
- return [] unless requirement
215
-
216
- case requirement
217
- when ::String
218
- key_path = requirement
219
- predicate = nil
220
- when ::Hash
221
- # TODO: support multiple dependencies?
222
- key_path = requirement.keys.first
223
- predicate = requirement.values.first
242
+ errors = []
243
+ if object.nil? && !self.class.nullable_attribute?(options)
244
+ errors << "Attribute #{Attributor.humanize_context(context)} is not nullable"
224
245
  else
225
- # should never get here if the option validation worked...
226
- raise AttributorException, "unknown type of dependency: #{requirement.inspect} for #{Attributor.humanize_context(context)}"
227
- end
246
+ errors.push *validate_type(object, context)
228
247
 
229
- # chop off the last part
230
- requirement_context = context[0..-2]
231
- requirement_context_string = requirement_context.join(Attributor::SEPARATOR)
232
-
233
- # FIXME: we're having to reconstruct a string context just to use the resolver...smell.
234
- if AttributeResolver.current.check(requirement_context_string, key_path, predicate)
235
- message = "Attribute #{Attributor.humanize_context(context)} is required when #{key_path} "
236
-
237
- # give a hint about what the full path for a relative key_path would be
238
- unless key_path[0..0] == Attributor::AttributeResolver::ROOT_PREFIX
239
- message << "(for #{Attributor.humanize_context(requirement_context)}) "
248
+ # If the value is null we skip value validation:
249
+ # a) If null wasn't allowed, it would have failed above.
250
+ # b) If null was allowed, we always allow that as a valid value
251
+ if !object.nil? && options[:values] && !options[:values].include?(object)
252
+ errors << "Attribute #{Attributor.humanize_context(context)}: #{Attributor.errorize_value(object)} is not within the allowed values=#{options[:values].inspect} "
240
253
  end
241
-
242
- message << if predicate
243
- "matches #{predicate.inspect}."
244
- else
245
- 'is present.'
246
- end
247
-
248
- [message]
249
- else
250
- []
251
254
  end
255
+
256
+ return errors if errors.any?
257
+
258
+ object.nil? ? errors : errors + type.validate(object, context, self)
252
259
  end
253
260
 
254
261
  def check_options!
@@ -264,6 +271,8 @@ module Attributor
264
271
 
265
272
  # TODO: override in type subclass
266
273
  def check_option!(name, definition)
274
+ return check_custom_option(name, definition) if self.class.custom_options.include? name
275
+
267
276
  case name
268
277
  when :values
269
278
  raise AttributorException, "Allowed set of values requires an array. Got (#{definition})" unless definition.is_a? ::Array
@@ -275,9 +284,8 @@ module Attributor
275
284
  when :required
276
285
  raise AttributorException, 'Required must be a boolean' unless definition == true || definition == false
277
286
  raise AttributorException, 'Required cannot be enabled in combination with :default' if definition == true && options.key?(:default)
278
- when :required_if
279
- raise AttributorException, 'Required_if must be a String, a Hash definition or a Proc' unless definition.is_a?(::String) || definition.is_a?(::Hash) || definition.is_a?(::Proc)
280
- raise AttributorException, 'Required_if cannot be specified together with :required' if options[:required]
287
+ when :null
288
+ raise AttributorException, 'Null must be a boolean' unless definition == true || definition == false
281
289
  when :example
282
290
  unless definition.is_a?(::Regexp) || definition.is_a?(::String) || definition.is_a?(::Array) || definition.is_a?(::Proc) || definition.nil? || type.valid_type?(definition)
283
291
  raise AttributorException, "Invalid example type (got: #{definition.class.name}). It must always match the type of the attribute (except if passing Regex that is allowed for some types)"
@@ -290,5 +298,14 @@ module Attributor
290
298
 
291
299
  :ok # passes
292
300
  end
301
+
302
+ def check_custom_option(name, definition)
303
+ attribute = self.class.custom_options.fetch(name)
304
+
305
+ errors = attribute.validate(definition)
306
+ raise AttributorException, "Custom option #{name.inspect} is invalid: #{errors.inspect}" if errors.any?
307
+
308
+ :ok
309
+ end
293
310
  end
294
311
  end
@@ -12,6 +12,10 @@ module Attributor
12
12
 
13
13
  include Attributor::Type
14
14
 
15
+ def self.json_schema_type
16
+ :string
17
+ end
18
+
15
19
  def self.native_type
16
20
  ::Hash
17
21
  end
@@ -1,15 +1,28 @@
1
1
  # Abstract type for the 'numeric' family
2
2
 
3
3
  module Attributor
4
- class Numeric
4
+ module Numeric
5
+ extend ActiveSupport::Concern
5
6
  include Type
6
7
 
7
- def self.native_type
8
- raise NotImplementedError
9
- end
8
+ module ClassMethods
9
+
10
+ def native_type
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def family
15
+ 'numeric'
16
+ end
10
17
 
11
- def self.family
12
- 'numeric'
18
+ def as_json_schema( shallow: false, example: nil, attribute_options: {} )
19
+ h = super
20
+ opts = ( self.respond_to?(:options) ) ? self.options.merge( attribute_options ) : attribute_options
21
+ h[:minimum] = opts[:min] if opts[:min]
22
+ h[:maximum] = opts[:max] if opts[:max]
23
+ # We're not explicitly setting false to exclusiveMinimum and exclusiveMaximum (as that's the default)
24
+ h
25
+ end
13
26
  end
14
27
  end
15
28
  end
@@ -1,19 +1,26 @@
1
1
  # Abstract type for the 'temporal' family
2
2
 
3
3
  module Attributor
4
- class Temporal
4
+ module Temporal
5
+ extend ActiveSupport::Concern
5
6
  include Type
6
7
 
7
- def self.native_type
8
- raise NotImplementedError
9
- end
8
+ module ClassMethods
9
+ def native_type
10
+ raise NotImplementedError
11
+ end
10
12
 
11
- def self.family
12
- 'temporal'
13
- end
13
+ def family
14
+ 'temporal'
15
+ end
16
+
17
+ def dump(value, **_opts)
18
+ value && value.iso8601
19
+ end
14
20
 
15
- def self.dump(value, **_opts)
16
- value && value.iso8601
21
+ def json_schema_type
22
+ :string
23
+ end
17
24
  end
18
25
  end
19
26
  end
@@ -35,30 +35,31 @@ module Attributor
35
35
  rest = attr_names - keys
36
36
  unless rest.empty?
37
37
  rest.each do |attr|
38
- result.push "Key #{attr} is required for #{Attributor.humanize_context(context)}."
38
+ sub_context = Attributor::Hash.generate_subcontext(context, attr)
39
+ result.push "Attribute #{Attributor.humanize_context(sub_context)} is required."
39
40
  end
40
41
  end
41
42
  when :exactly
42
43
  included = attr_names & keys
43
44
  unless included.size == number
44
- result.push "Exactly #{number} of the following keys #{attr_names} are required for #{Attributor.humanize_context(context)}. Found #{included.size} instead: #{included.inspect}"
45
+ result.push "Exactly #{number} of the following attributes #{attr_names} are required for #{Attributor.humanize_context(context)}. Found #{included.size} instead: #{included.inspect}"
45
46
  end
46
47
  when :at_most
47
48
  rest = attr_names & keys
48
49
  if rest.size > number
49
50
  found = rest.empty? ? 'none' : rest.inspect
50
- result.push "At most #{number} keys out of #{attr_names} can be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
51
+ result.push "At most #{number} attributes out of #{attr_names} can be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
51
52
  end
52
53
  when :at_least
53
54
  rest = attr_names & keys
54
55
  if rest.size < number
55
56
  found = rest.empty? ? 'none' : rest.inspect
56
- result.push "At least #{number} keys out of #{attr_names} are required to be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
57
+ result.push "At least #{number} attributes out of #{attr_names} are required to be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
57
58
  end
58
59
  when :exclusive
59
60
  intersection = attr_names & keys
60
61
  if intersection.size > 1
61
- result.push "keys #{intersection.inspect} are mutually exclusive for #{Attributor.humanize_context(context)}."
62
+ result.push "Attributes #{intersection.inspect} are mutually exclusive for #{Attributor.humanize_context(context)}."
62
63
  end
63
64
  end
64
65
  result
@@ -2,9 +2,7 @@ module Attributor
2
2
  # It is the abstract base class to hold an attribute, both a leaf and a container (hash/Array...)
3
3
  # TODO: should this be a mixin since it is an abstract class?
4
4
  module Type
5
- def self.included(klass)
6
- klass.extend(ClassMethods)
7
- end
5
+ extend ActiveSupport::Concern
8
6
 
9
7
  module ClassMethods
10
8
  # Does this type support the generation of subtypes?
@@ -124,6 +122,31 @@ module Attributor
124
122
  def family
125
123
  'any'
126
124
  end
125
+
126
+ # Default no format in case it's a string type
127
+ def json_schema_string_format
128
+ nil
129
+ end
130
+
131
+ def as_json_schema( shallow: false, example: nil, attribute_options: {} )
132
+ type_name = self.ancestors.find { |k| k.name && !k.name.empty? }.name
133
+ hash = { type: json_schema_type, 'x-type_name': type_name.gsub( Attributor::MODULE_PREFIX_REGEX, '' )}
134
+ # Add a format, if the type has defined
135
+ if hash[:type] == :string && the_format = json_schema_string_format
136
+ hash[:format] = the_format
137
+ end
138
+ hash
139
+ end
140
+
141
+ def describe_option( option_name, option_value )
142
+ return case option_name
143
+ when :description
144
+ option_value
145
+ else
146
+ option_value # By default, describing an option returns the hash with the specification
147
+ end
148
+ end
149
+
127
150
  end
128
151
  end
129
152
  end
@@ -1,7 +1,8 @@
1
1
  require 'bigdecimal'
2
2
 
3
3
  module Attributor
4
- class BigDecimal < Numeric
4
+ class BigDecimal
5
+ include Numeric
5
6
  def self.native_type
6
7
  ::BigDecimal
7
8
  end
@@ -16,5 +17,9 @@ module Attributor
16
17
  return BigDecimal(value, 10) if value.is_a?(::Float)
17
18
  BigDecimal(value)
18
19
  end
20
+
21
+ def self.json_schema_type
22
+ :number
23
+ end
19
24
  end
20
25
  end
@@ -26,5 +26,10 @@ module Attributor
26
26
  def self.family
27
27
  'boolean'
28
28
  end
29
+
30
+ def self.json_schema_type
31
+ :boolean
32
+ end
33
+
29
34
  end
30
35
  end
@@ -117,6 +117,25 @@ module Attributor
117
117
  hash
118
118
  end
119
119
 
120
+ def self.json_schema_type
121
+ :array
122
+ end
123
+
124
+ def self.as_json_schema( shallow: false, example: nil, attribute_options: {} )
125
+ hash = super
126
+ opts = self.options.merge( attribute_options )
127
+ hash[:description] = opts[:description] if opts[:description]
128
+ if the_default = opts[:default]
129
+ the_object = the_default.is_a?(Proc) ? the_default.call : the_default
130
+ hash[:description] = the_object.is_a?(Attributor::Dumpable) ? the_object.dump : the_object
131
+ end
132
+
133
+ #hash[:examples] = [ example.dump ] if example
134
+ member_example = example && example.first
135
+ hash[:items] = member_attribute.as_json_schema(example: member_example)
136
+ hash
137
+ end
138
+
120
139
  def self.constructable?
121
140
  true
122
141
  end
@@ -37,5 +37,9 @@ module Attributor
37
37
  def self.family
38
38
  Collection.family
39
39
  end
40
+
41
+ def self.json_schema_type
42
+ :string
43
+ end
40
44
  end
41
45
  end
@@ -1,7 +1,9 @@
1
1
  require 'date'
2
2
 
3
3
  module Attributor
4
- class Date < Temporal
4
+ class Date
5
+ include Temporal
6
+
5
7
  def self.native_type
6
8
  ::Date
7
9
  end
@@ -27,5 +29,9 @@ module Attributor
27
29
  raise CoercionError.new(context: context, from: value.class, to: self, value: value)
28
30
  end
29
31
  end
32
+
33
+ def self.json_schema_string_format
34
+ :date
35
+ end
30
36
  end
31
37
  end
@@ -5,7 +5,9 @@ require_relative '../exceptions'
5
5
  require 'date'
6
6
 
7
7
  module Attributor
8
- class DateTime < Temporal
8
+ class DateTime
9
+ include Temporal
10
+
9
11
  def self.native_type
10
12
  ::DateTime
11
13
  end
@@ -27,5 +29,9 @@ module Attributor
27
29
  raise Attributor::DeserializationError.new(context: context, from: value.class, encoding: 'DateTime', value: value)
28
30
  end
29
31
  end
32
+
33
+ def self.json_schema_string_format
34
+ :'date-time'
35
+ end
30
36
  end
31
37
  end
@@ -2,8 +2,9 @@
2
2
  # See: http://ruby-doc.org/core-2.1.0/Float.html
3
3
 
4
4
  module Attributor
5
+
5
6
  class Float
6
- include Type
7
+ include Numeric
7
8
 
8
9
  def self.native_type
9
10
  ::Float
@@ -22,8 +23,8 @@ module Attributor
22
23
  super
23
24
  end
24
25
 
25
- def self.family
26
- 'numeric'
26
+ def self.json_schema_type
27
+ :number
27
28
  end
28
29
  end
29
30
  end