attributor 5.4 → 6.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 (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