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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/attributor/attribute.rb +101 -84
- data/lib/attributor/extras/field_selector.rb +4 -0
- data/lib/attributor/families/numeric.rb +19 -6
- data/lib/attributor/families/temporal.rb +16 -9
- data/lib/attributor/hash_dsl_compiler.rb +6 -5
- data/lib/attributor/type.rb +26 -3
- data/lib/attributor/types/bigdecimal.rb +6 -1
- data/lib/attributor/types/boolean.rb +5 -0
- data/lib/attributor/types/collection.rb +19 -0
- data/lib/attributor/types/csv.rb +4 -0
- data/lib/attributor/types/date.rb +7 -1
- data/lib/attributor/types/date_time.rb +7 -1
- data/lib/attributor/types/float.rb +4 -3
- data/lib/attributor/types/hash.rb +86 -23
- data/lib/attributor/types/integer.rb +7 -1
- data/lib/attributor/types/model.rb +9 -21
- data/lib/attributor/types/object.rb +5 -0
- data/lib/attributor/types/polymorphic.rb +0 -1
- data/lib/attributor/types/string.rb +19 -0
- data/lib/attributor/types/symbol.rb +5 -0
- data/lib/attributor/types/tempfile.rb +4 -0
- data/lib/attributor/types/time.rb +6 -2
- data/lib/attributor/types/uri.rb +8 -0
- data/lib/attributor/version.rb +1 -1
- data/lib/attributor.rb +3 -7
- data/spec/attribute_spec.rb +148 -124
- data/spec/extras/field_selector/field_selector_spec.rb +9 -0
- data/spec/hash_dsl_compiler_spec.rb +5 -5
- data/spec/spec_helper.rb +0 -2
- data/spec/support/integers.rb +7 -0
- data/spec/support/models.rb +7 -7
- data/spec/types/bigdecimal_spec.rb +8 -0
- data/spec/types/boolean_spec.rb +10 -0
- data/spec/types/collection_spec.rb +16 -0
- data/spec/types/date_spec.rb +9 -0
- data/spec/types/date_time_spec.rb +9 -0
- data/spec/types/float_spec.rb +8 -0
- data/spec/types/hash_spec.rb +181 -22
- data/spec/types/integer_spec.rb +9 -0
- data/spec/types/model_spec.rb +7 -1
- data/spec/types/string_spec.rb +10 -0
- data/spec/types/temporal_spec.rb +5 -1
- data/spec/types/time_spec.rb +9 -0
- data/spec/types/uri_spec.rb +9 -0
- metadata +5 -6
- data/lib/attributor/attribute_resolver.rb +0 -111
- data/spec/attribute_resolver_spec.rb +0 -237
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 108abdb1d351f95b78cfae24b61bf8e5bf3943626d8badb691e28ce0df23c45d
|
4
|
+
data.tar.gz: 96f4e678e7bccb85526a7a3788aa2df538902200fcb50b49e0de3a36c5d3778d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/attributor/attribute.rb
CHANGED
@@ -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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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, :
|
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
|
-
|
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] =
|
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] =
|
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
|
150
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
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
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
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 :
|
279
|
-
raise AttributorException, '
|
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
|
@@ -1,15 +1,28 @@
|
|
1
1
|
# Abstract type for the 'numeric' family
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
|
4
|
+
module Numeric
|
5
|
+
extend ActiveSupport::Concern
|
5
6
|
include Type
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def native_type
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def family
|
15
|
+
'numeric'
|
16
|
+
end
|
10
17
|
|
11
|
-
|
12
|
-
|
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
|
-
|
4
|
+
module Temporal
|
5
|
+
extend ActiveSupport::Concern
|
5
6
|
include Type
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
module ClassMethods
|
9
|
+
def native_type
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
def family
|
14
|
+
'temporal'
|
15
|
+
end
|
16
|
+
|
17
|
+
def dump(value, **_opts)
|
18
|
+
value && value.iso8601
|
19
|
+
end
|
14
20
|
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
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}
|
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}
|
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 "
|
62
|
+
result.push "Attributes #{intersection.inspect} are mutually exclusive for #{Attributor.humanize_context(context)}."
|
62
63
|
end
|
63
64
|
end
|
64
65
|
result
|
data/lib/attributor/type.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
@@ -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
|
data/lib/attributor/types/csv.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
class Date
|
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
|
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
|
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.
|
26
|
-
|
26
|
+
def self.json_schema_type
|
27
|
+
:number
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|