attributor 5.4 → 5.5
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 +5 -5
- data/CHANGELOG.md +4 -0
- data/lib/attributor.rb +1 -1
- data/lib/attributor/attribute.rb +36 -7
- 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/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 +16 -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 +63 -8
- data/lib/attributor/types/integer.rb +7 -1
- 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/spec/attribute_spec.rb +37 -6
- data/spec/extras/field_selector/field_selector_spec.rb +9 -0
- data/spec/support/integers.rb +7 -0
- 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 +118 -0
- data/spec/types/integer_spec.rb +9 -0
- 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 +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 30cd524aa1a60bb34487915e2201e20b075f8eaf
|
4
|
+
data.tar.gz: 4a9776a1a772b00636a46caec55f583aa75d54f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5a4832825c92b5530352b626ae281c134aa215f167c633a7912b22cee2927fde696d07b92bfd6159b0e88db4dc69b19b38a289181cb523e0c961005608e4f86
|
7
|
+
data.tar.gz: a44680f8509acf463ac0c4a22a872db8456300745d01b5c4a31a597bd17760d995e5d6ce26120d998f8df5c4c06fe2d11e7e00c35959f6c987860059425af663
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 5.5 (21/08/2020)
|
6
|
+
|
7
|
+
- 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.
|
8
|
+
|
5
9
|
## 5.3 (24/04/2020)
|
6
10
|
|
7
11
|
- Fixed deprecation warnings in Ruby 2.7
|
data/lib/attributor.rb
CHANGED
data/lib/attributor/attribute.rb
CHANGED
@@ -93,12 +93,12 @@ module Attributor
|
|
93
93
|
|
94
94
|
TOP_LEVEL_OPTIONS = [:description, :values, :default, :example, :required, :required_if, :custom_data].freeze
|
95
95
|
INTERNAL_OPTIONS = [:dsl_compiler, :dsl_compiler_options].freeze # Options we don't want to expose when describing attributes
|
96
|
-
|
97
|
-
def describe(shallow
|
98
|
-
description = {}
|
96
|
+
JSON_SCHEMA_UNSUPPORTED_OPTIONS = [ :required, :required_if ].freeze
|
97
|
+
def describe(shallow=true, example: nil)
|
98
|
+
description = { }
|
99
99
|
# Clone the common options
|
100
100
|
TOP_LEVEL_OPTIONS.each do |option_name|
|
101
|
-
description[option_name] =
|
101
|
+
description[option_name] = self.describe_option(option_name) if self.options.has_key? option_name
|
102
102
|
end
|
103
103
|
|
104
104
|
# Make sure this option definition is not mistaken for the real generated example
|
@@ -109,7 +109,7 @@ module Attributor
|
|
109
109
|
special_options = options.keys - TOP_LEVEL_OPTIONS - INTERNAL_OPTIONS
|
110
110
|
description[:options] = {} unless special_options.empty?
|
111
111
|
special_options.each do |opt_name|
|
112
|
-
description[:options][opt_name] =
|
112
|
+
description[:options][opt_name] = self.describe_option(opt_name)
|
113
113
|
end
|
114
114
|
# Change the reference option to the actual class name.
|
115
115
|
if (reference = options[:reference])
|
@@ -146,8 +146,37 @@ module Attributor
|
|
146
146
|
load(generated, context)
|
147
147
|
end
|
148
148
|
|
149
|
-
def
|
150
|
-
|
149
|
+
def describe_option( option_name )
|
150
|
+
self.type.describe_option( option_name, self.options[option_name] )
|
151
|
+
end
|
152
|
+
|
153
|
+
# FiXME: pass and utilize the "shallow" parameter
|
154
|
+
#required
|
155
|
+
#options
|
156
|
+
#type
|
157
|
+
#example
|
158
|
+
# UTILIZE THIS SITE! http://jsonschema.net/#/
|
159
|
+
def as_json_schema(shallow: true, example: nil)
|
160
|
+
description = self.type.as_json_schema(shallow: shallow, example: example, attribute_options: self.options )
|
161
|
+
|
162
|
+
description[:description] = self.options[:description] if self.options[:description]
|
163
|
+
description[:enum] = self.options[:values] if self.options[:values]
|
164
|
+
description[:default] = self.options[:default] if self.options[:default]
|
165
|
+
#TODO description[:title] = "TODO: do we want to use a title??..."
|
166
|
+
|
167
|
+
# Change the reference option to the actual class name.
|
168
|
+
if ( reference = self.options[:reference] )
|
169
|
+
description[:'x-reference'] = reference.name
|
170
|
+
end
|
171
|
+
|
172
|
+
# TODO: not sure if that's correct (we used to get it from the described hash...
|
173
|
+
description[:example] = self.dump(example) if example
|
174
|
+
|
175
|
+
description
|
176
|
+
end
|
177
|
+
|
178
|
+
def example(context=nil, parent: nil, values:{})
|
179
|
+
raise ArgumentError, "attribute example cannot take a context of type String" if (context.is_a? ::String )
|
151
180
|
if context
|
152
181
|
ctx = Attributor.humanize_context(context)
|
153
182
|
seed, = Digest::SHA1.digest(ctx).unpack('QQ')
|
@@ -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
|
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,22 @@ 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
|
+
hash[:default] = opts[:default] if opts[:default]
|
129
|
+
|
130
|
+
#hash[:examples] = [ example.dump ] if example
|
131
|
+
member_example = example && example.first
|
132
|
+
hash[:items] = member_attribute.as_json_schema(example: member_example)
|
133
|
+
hash
|
134
|
+
end
|
135
|
+
|
120
136
|
def self.constructable?
|
121
137
|
true
|
122
138
|
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
|
@@ -437,11 +437,11 @@ module Attributor
|
|
437
437
|
|
438
438
|
if keys.any?
|
439
439
|
# Spit keys if it's the root or if it's an anonymous structures
|
440
|
-
if !shallow || name
|
441
|
-
|
440
|
+
if ( !shallow || self.name == nil)
|
441
|
+
required_names_from_attr = []
|
442
442
|
# FIXME: change to :keys when the praxis doc browser supports displaying those
|
443
|
-
hash[:attributes] = keys.each_with_object({}) do |(sub_name, sub_attribute), sub_attributes|
|
444
|
-
|
443
|
+
hash[:attributes] = self.keys.each_with_object({}) do |(sub_name, sub_attribute), sub_attributes|
|
444
|
+
required_names_from_attr << sub_name if sub_attribute.options[:required] == true
|
445
445
|
sub_example = example.get(sub_name) if example
|
446
446
|
sub_attributes[sub_name] = sub_attribute.describe(true, example: sub_example)
|
447
447
|
end
|
@@ -449,14 +449,14 @@ module Attributor
|
|
449
449
|
described_req = req.describe(shallow)
|
450
450
|
if described_req[:type] == :all
|
451
451
|
# Add the names of the attributes that have the required flag too
|
452
|
-
described_req[:attributes] |=
|
453
|
-
|
452
|
+
described_req[:attributes] |= required_names_from_attr
|
453
|
+
required_names_from_attr = []
|
454
454
|
end
|
455
455
|
list << described_req
|
456
456
|
end
|
457
457
|
# Make sure we create an :all requirement, if there wasn't one so we can add the required: true attributes
|
458
|
-
unless
|
459
|
-
hash[:requirements] << {
|
458
|
+
unless required_names_from_attr.empty?
|
459
|
+
hash[:requirements] << {type: :all, attributes: required_names_from_attr }
|
460
460
|
end
|
461
461
|
end
|
462
462
|
else
|
@@ -467,6 +467,61 @@ module Attributor
|
|
467
467
|
hash
|
468
468
|
end
|
469
469
|
|
470
|
+
def self.as_json_schema( shallow: false, example: nil, attribute_options: {} )
|
471
|
+
hash = super
|
472
|
+
opts = self.options.merge( attribute_options )
|
473
|
+
|
474
|
+
if key_type
|
475
|
+
hash[:'x-key_type'] = key_type.as_json_schema
|
476
|
+
end
|
477
|
+
|
478
|
+
if self.keys.any?
|
479
|
+
# Spit keys if it's the root or if it's an anonymous structures
|
480
|
+
if ( !shallow || self.name == nil)
|
481
|
+
required_names_from_attr = []
|
482
|
+
# FIXME: change to :keys when the praxis doc browser supports displaying those
|
483
|
+
hash[:properties] = self.keys.each_with_object({}) do |(sub_name, sub_attribute), sub_attributes|
|
484
|
+
required_names_from_attr << sub_name if sub_attribute.options[:required] == true
|
485
|
+
sub_example = example.get(sub_name) if example
|
486
|
+
sub_attributes[sub_name] = sub_attribute.as_json_schema(shallow: true, example: sub_example)
|
487
|
+
end
|
488
|
+
|
489
|
+
# Expose the more complex requirements to in the x-tended attribute
|
490
|
+
extended_requirements = self.requirements.each_with_object([]) do |req, list|
|
491
|
+
described_req = req.describe(shallow)
|
492
|
+
if described_req[:type] == :all
|
493
|
+
# Add the names of the attributes that have the required flag too
|
494
|
+
described_req[:attributes] |= required_names_from_attr
|
495
|
+
required_names_from_attr = []
|
496
|
+
end
|
497
|
+
list << described_req
|
498
|
+
end
|
499
|
+
all = extended_requirements.find{|r| r[:type] == :all }
|
500
|
+
if ( all && !all[:attributes].empty? )
|
501
|
+
hash[:required] = all[:attributes]
|
502
|
+
end
|
503
|
+
hash[:'x-requirements'] = extended_requirements unless extended_requirements.empty?
|
504
|
+
end
|
505
|
+
else
|
506
|
+
hash[:'x-value_type'] = value_type.as_json_schema(shallow:true)
|
507
|
+
end
|
508
|
+
|
509
|
+
if opts[:allow_extra]
|
510
|
+
hash[:additionalProperties] = if value_type == Attributor::Object
|
511
|
+
true
|
512
|
+
else
|
513
|
+
value_type.as_json_schema(shallow: true)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
# TODO: minProperties and maxProperties and patternProperties
|
517
|
+
# TODO: map our required_if (and possible our above requirements 'at_least...' to json schema dependencies)
|
518
|
+
hash
|
519
|
+
end
|
520
|
+
|
521
|
+
def self.json_schema_type
|
522
|
+
:object
|
523
|
+
end
|
524
|
+
|
470
525
|
# TODO: Think about the format of the subcontexts to use: let's use .at(key.to_s)
|
471
526
|
attr_reader :contents
|
472
527
|
|
@@ -1,7 +1,9 @@
|
|
1
1
|
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
class Integer
|
4
|
+
class Integer
|
5
|
+
include Attributor::Numeric
|
6
|
+
|
5
7
|
EXAMPLE_RANGE = 1000
|
6
8
|
|
7
9
|
def self.native_type
|
@@ -53,5 +55,9 @@ module Attributor
|
|
53
55
|
end
|
54
56
|
true
|
55
57
|
end
|
58
|
+
|
59
|
+
def self.json_schema_type
|
60
|
+
:integer
|
61
|
+
end
|
56
62
|
end
|
57
63
|
end
|
@@ -32,5 +32,24 @@ module Attributor
|
|
32
32
|
def self.family
|
33
33
|
'string'
|
34
34
|
end
|
35
|
+
|
36
|
+
def self.json_schema_type
|
37
|
+
:string
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: we're passing the attribute options for now...might need to rethink ...although these are type-specific...
|
41
|
+
# TODO: multipleOf, minimum, maximum, exclusiveMinimum and exclusiveMaximum
|
42
|
+
def self.as_json_schema( shallow: false, example: nil, attribute_options: {} )
|
43
|
+
h = super
|
44
|
+
opts = ( self.respond_to?(:options) ) ? self.options.merge( attribute_options ) : attribute_options
|
45
|
+
h[:pattern] = self.human_readable_regexp(opts[:regexp]) if opts[:regexp]
|
46
|
+
# TODO: minLength, maxLength
|
47
|
+
h
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.human_readable_regexp( reg )
|
51
|
+
return $1 if reg.to_s =~ /\(\?[^:]+:(.+)\)/
|
52
|
+
reg
|
53
|
+
end
|
35
54
|
end
|
36
55
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
class Time
|
5
|
-
include
|
4
|
+
class Time
|
5
|
+
include Temporal
|
6
6
|
|
7
7
|
def self.native_type
|
8
8
|
::Time
|
@@ -35,5 +35,9 @@ module Attributor
|
|
35
35
|
raise CoercionError, context: context, from: value.class, to: self, value: value
|
36
36
|
end
|
37
37
|
end
|
38
|
+
|
39
|
+
def self.json_schema_string_format
|
40
|
+
:time
|
41
|
+
end
|
38
42
|
end
|
39
43
|
end
|
data/lib/attributor/types/uri.rb
CHANGED
data/lib/attributor/version.rb
CHANGED
data/spec/attribute_spec.rb
CHANGED
@@ -36,17 +36,48 @@ describe Attributor::Attribute do
|
|
36
36
|
it { should eq other_attribute }
|
37
37
|
end
|
38
38
|
|
39
|
+
context 'describe_json_schema' do
|
40
|
+
let(:type) { PositiveIntegerType }
|
41
|
+
|
42
|
+
let(:attribute_options) do
|
43
|
+
{
|
44
|
+
values: [1,20],
|
45
|
+
description: "something",
|
46
|
+
example: 20,
|
47
|
+
max: 1000,
|
48
|
+
default: 1
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'reports all of the possible attributes' do
|
53
|
+
let(:js){ subject.as_json_schema(example: 20) }
|
54
|
+
|
55
|
+
it 'including the attribute-specific ones' do
|
56
|
+
expect(js[:enum]).to eq( [1,20])
|
57
|
+
expect(js[:description]).to eq( "something")
|
58
|
+
expect(js[:default]).to eq(1)
|
59
|
+
expect(js[:example]).to eq(20)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'as well as the type-specific ones' do
|
63
|
+
expect(js[:type]).to eq(:integer)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
39
69
|
context 'describe' do
|
40
|
-
let(:attribute_options) { {
|
70
|
+
let(:attribute_options) { {required: true, values: ['one'], description: "something", min: 0} }
|
41
71
|
let(:expected) do
|
42
|
-
h = {
|
43
|
-
common = attribute_options.select
|
44
|
-
h.merge!(common)
|
45
|
-
h[:options] = {
|
72
|
+
h = {type: {name: 'String', id: type.id, family: type.family}}
|
73
|
+
common = attribute_options.select{|k,v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k }
|
74
|
+
h.merge!( common )
|
75
|
+
h[:options] = {:min => 0 }
|
46
76
|
h
|
47
77
|
end
|
48
78
|
|
49
|
-
|
79
|
+
# It has both the type-included options (min) as well as the attribute options (max)
|
80
|
+
its(:describe) { should == expected }
|
50
81
|
|
51
82
|
context 'with example options' do
|
52
83
|
let(:attribute_options) { { description: 'something', example: 'ex_def' } }
|
@@ -33,4 +33,13 @@ describe Attributor::FieldSelector do
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
context '.as_json_schema' do
|
38
|
+
subject(:js){ type.as_json_schema }
|
39
|
+
it 'adds the right attributes' do
|
40
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
41
|
+
expect(js[:type]).to eq(:string)
|
42
|
+
expect(js[:'x-type_name']).to eq('FieldSelector')
|
43
|
+
end
|
44
|
+
end
|
36
45
|
end
|
@@ -45,4 +45,12 @@ describe Attributor::BigDecimal do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
|
+
context '.as_json_schema' do
|
49
|
+
subject(:js){ type.as_json_schema }
|
50
|
+
it 'adds the right attributes' do
|
51
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
52
|
+
expect(js[:type]).to eq(:number)
|
53
|
+
expect(js[:'x-type_name']).to eq('BigDecimal')
|
54
|
+
end
|
55
|
+
end
|
48
56
|
end
|
data/spec/types/boolean_spec.rb
CHANGED
@@ -7,6 +7,8 @@ describe Attributor::Boolean do
|
|
7
7
|
expect(type.new.is_a?(Attributor::Dumpable)).not_to be(true)
|
8
8
|
end
|
9
9
|
|
10
|
+
its(:json_schema_type){ should eq(:boolean)}
|
11
|
+
|
10
12
|
context '.valid_type?' do
|
11
13
|
context 'for incoming Boolean values' do
|
12
14
|
[false, true].each do |value|
|
@@ -63,4 +65,12 @@ describe Attributor::Boolean do
|
|
63
65
|
end
|
64
66
|
end
|
65
67
|
end
|
68
|
+
context '.as_json_schema' do
|
69
|
+
subject(:js){ type.as_json_schema }
|
70
|
+
it 'adds the right attributes' do
|
71
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
72
|
+
expect(js[:type]).to eq(:boolean)
|
73
|
+
expect(js[:'x-type_name']).to eq('Boolean')
|
74
|
+
end
|
75
|
+
end
|
66
76
|
end
|
@@ -344,4 +344,20 @@ describe Attributor::Collection do
|
|
344
344
|
end.to_not raise_error
|
345
345
|
end
|
346
346
|
end
|
347
|
+
|
348
|
+
context '.as_json_schema' do
|
349
|
+
let(:member_type) { Attributor::String }
|
350
|
+
let(:type) { Attributor::Collection.of(member_type) }
|
351
|
+
let(:attribute_options) do
|
352
|
+
{}
|
353
|
+
end
|
354
|
+
subject(:js){ type.as_json_schema(attribute_options: attribute_options) }
|
355
|
+
|
356
|
+
it 'adds the right attributes' do
|
357
|
+
expect(js.keys).to include(:type, :'x-type_name', :items)
|
358
|
+
expect(js[:type]).to eq(:array)
|
359
|
+
expect(js[:'x-type_name']).to eq('Collection')
|
360
|
+
expect(js[:items]).to eq(member_type.as_json_schema)
|
361
|
+
end
|
362
|
+
end
|
347
363
|
end
|
data/spec/types/date_spec.rb
CHANGED
@@ -92,4 +92,13 @@ describe Attributor::Date do
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
95
|
+
context '.as_json_schema' do
|
96
|
+
subject(:js){ type.as_json_schema }
|
97
|
+
it 'adds the right attributes' do
|
98
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
99
|
+
expect(js[:type]).to eq(:string)
|
100
|
+
expect(js[:format]).to eq(:'date')
|
101
|
+
expect(js[:'x-type_name']).to eq('Date')
|
102
|
+
end
|
103
|
+
end
|
95
104
|
end
|
@@ -92,4 +92,13 @@ describe Attributor::DateTime do
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
95
|
+
context '.as_json_schema' do
|
96
|
+
subject(:js){ type.as_json_schema }
|
97
|
+
it 'adds the right attributes' do
|
98
|
+
expect(js.keys).to include(:type, :'x-type_name', :format)
|
99
|
+
expect(js[:type]).to eq(:string)
|
100
|
+
expect(js[:format]).to eq(:'date-time')
|
101
|
+
expect(js[:'x-type_name']).to eq('DateTime')
|
102
|
+
end
|
103
|
+
end
|
95
104
|
end
|
data/spec/types/float_spec.rb
CHANGED
@@ -76,4 +76,12 @@ describe Attributor::Float do
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
79
|
+
context '.as_json_schema' do
|
80
|
+
subject(:js){ type.as_json_schema }
|
81
|
+
it 'adds the right attributes' do
|
82
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
83
|
+
expect(js[:type]).to eq(:number)
|
84
|
+
expect(js[:'x-type_name']).to eq('Float')
|
85
|
+
end
|
86
|
+
end
|
79
87
|
end
|
data/spec/types/hash_spec.rb
CHANGED
@@ -7,6 +7,7 @@ describe Attributor::Hash do
|
|
7
7
|
its(:key_type) { should be(Attributor::Object) }
|
8
8
|
its(:value_type) { should be(Attributor::Object) }
|
9
9
|
its(:dsl_class) { should be(Attributor::HashDSLCompiler) }
|
10
|
+
its(:json_schema_type) { should be(:object) }
|
10
11
|
|
11
12
|
context 'attributes' do
|
12
13
|
context 'with an exception from the definition block' do
|
@@ -1172,4 +1173,121 @@ describe Attributor::Hash do
|
|
1172
1173
|
|
1173
1174
|
context Attributor::InvalidDefinition do
|
1174
1175
|
end
|
1176
|
+
|
1177
|
+
|
1178
|
+
context '.as_json_hash' do
|
1179
|
+
let(:example){ nil }
|
1180
|
+
subject(:description) { type.as_json_schema(example: example) }
|
1181
|
+
its([:type]){ should eq(:object)}
|
1182
|
+
its([:'x-type_name']){ should eq('Hash')}
|
1183
|
+
|
1184
|
+
context 'for hashes with explicit key and value types' do
|
1185
|
+
let(:key_type){ String }
|
1186
|
+
let(:value_type){ Integer }
|
1187
|
+
|
1188
|
+
subject(:type) { Attributor::Hash.of(key: key_type, value: value_type) }
|
1189
|
+
|
1190
|
+
it 'describes the key type correctly' do
|
1191
|
+
expect(description.keys).to include( :'x-key_type' )
|
1192
|
+
expect(description[:'x-key_type']).to be_kind_of(::Hash)
|
1193
|
+
expect(description[:'x-key_type'][:type]).to eq( :string )
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
it 'describes the value type correctly' do
|
1197
|
+
expect(description.keys).to include( :'x-value_type' )
|
1198
|
+
expect(description[:'x-value_type']).to be_kind_of(::Hash)
|
1199
|
+
expect(description[:'x-value_type'][:type]).to eq( :integer )
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
|
1205
|
+
context 'for hashes with specific keys defined' do
|
1206
|
+
let(:block) do
|
1207
|
+
proc do
|
1208
|
+
key 'a string', String
|
1209
|
+
key '1', Integer, min: 1, max: 20
|
1210
|
+
key 'some_date', DateTime
|
1211
|
+
key 'defaulted', String, default: 'default value'
|
1212
|
+
requires do
|
1213
|
+
all.of '1','some_date'
|
1214
|
+
exclusive 'some_date', 'defaulted'
|
1215
|
+
at_least(1).of 'a string', 'some_date'
|
1216
|
+
at_most(2).of 'a string', 'some_date'
|
1217
|
+
exactly(1).of 'a string', 'some_date'
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
let(:type) { Attributor::Hash.of(key: String).construct(block) }
|
1223
|
+
|
1224
|
+
it 'describes the basic type options correctly' do
|
1225
|
+
expect(description[:type]).to eq(:object)
|
1226
|
+
expect(description[:'x-key_type']).to eq( type: :string , 'x-type_name': 'String')
|
1227
|
+
expect(description).to_not have_key(:'x-value_type')
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
it 'describes the type attributes correctly' do
|
1231
|
+
props = description[:properties]
|
1232
|
+
|
1233
|
+
expect(props['a string']).to eq(type: :string, 'x-type_name': 'String')
|
1234
|
+
expect(props['1']).to eq(type: :integer, 'x-type_name': 'Integer', minimum: 1, maximum: 20)
|
1235
|
+
expect(props['some_date']).to eq(type: :string, 'x-type_name': 'DateTime', format: :'date-time')
|
1236
|
+
expect(props['defaulted']).to eq(type: :string, 'x-type_name': 'String', default: 'default value')
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
it 'describes the attribute requirements correctly' do
|
1240
|
+
reqs = description[:required]
|
1241
|
+
expect(reqs).to be_kind_of(Array)
|
1242
|
+
expect(reqs).to eq( ['1','some_date'] )
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
it 'describes the extended requirements correctly' do
|
1246
|
+
reqs = description[:'x-requirements']
|
1247
|
+
expect(reqs).to be_kind_of(Array)
|
1248
|
+
expect(reqs.size).to be(5)
|
1249
|
+
expect(reqs).to include( type: :all, attributes: ['1','some_date'] )
|
1250
|
+
expect(reqs).to include( type: :exclusive, attributes: ['some_date','defaulted'] )
|
1251
|
+
expect(reqs).to include( type: :at_least, attributes: ['a string','some_date'], count: 1 )
|
1252
|
+
expect(reqs).to include( type: :at_most, attributes: ['a string','some_date'], count: 2 )
|
1253
|
+
expect(reqs).to include( type: :exactly, attributes: ['a string','some_date'], count: 1 )
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
context 'merging requires.all with attribute required: true' do
|
1257
|
+
let(:block) do
|
1258
|
+
proc do
|
1259
|
+
key 'required string', String, required: true
|
1260
|
+
key '1', Integer
|
1261
|
+
key 'some_date', DateTime
|
1262
|
+
requires do
|
1263
|
+
all.of 'some_date'
|
1264
|
+
end
|
1265
|
+
end
|
1266
|
+
end
|
1267
|
+
it 'includes attributes with required: true into :required' do
|
1268
|
+
expect(description[:required].size).to eq(2)
|
1269
|
+
expect(description[:required]).to include( 'required string','some_date' )
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
it 'includes attributes with required: true into the :all requirements' do
|
1273
|
+
req_all = description[:'x-requirements'].select{|r| r[:type] == :all}.first
|
1274
|
+
expect(req_all[:attributes]).to include( 'required string','some_date' )
|
1275
|
+
end
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
|
1279
|
+
context 'with an example' do
|
1280
|
+
let(:example){ type.example }
|
1281
|
+
|
1282
|
+
it 'should have the matching example for each leaf key' do
|
1283
|
+
expect(description[:properties].keys).to include(*type.keys.keys)
|
1284
|
+
description[:properties].each do |name,sub_description|
|
1285
|
+
expect(sub_description).to have_key(:example)
|
1286
|
+
val = type.attributes[name].dump(example[name])
|
1287
|
+
expect(sub_description[:example]).to eq val
|
1288
|
+
end
|
1289
|
+
end
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
end
|
1175
1293
|
end
|
data/spec/types/integer_spec.rb
CHANGED
@@ -146,4 +146,13 @@ describe Attributor::Integer do
|
|
146
146
|
end
|
147
147
|
end
|
148
148
|
end
|
149
|
+
|
150
|
+
context '.as_json_schema' do
|
151
|
+
subject(:js){ type.as_json_schema }
|
152
|
+
it 'adds the right stuff' do
|
153
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
154
|
+
expect(js[:type]).to eq(:integer)
|
155
|
+
expect(js[:'x-type_name']).to eq('Integer')
|
156
|
+
end
|
157
|
+
end
|
149
158
|
end
|
data/spec/types/string_spec.rb
CHANGED
@@ -64,4 +64,14 @@ describe Attributor::String do
|
|
64
64
|
end.to raise_error(Attributor::IncompatibleTypeError)
|
65
65
|
end
|
66
66
|
end
|
67
|
+
|
68
|
+
context '.as_json_schema' do
|
69
|
+
subject(:js){ type.as_json_schema(attribute_options: { regexp: /^Foobar$/ }) }
|
70
|
+
it 'adds the right attributes' do
|
71
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
72
|
+
expect(js[:type]).to eq(:string)
|
73
|
+
expect(js[:'x-type_name']).to eq('String')
|
74
|
+
expect(js[:pattern]).to eq('^Foobar$')
|
75
|
+
end
|
76
|
+
end
|
67
77
|
end
|
data/spec/types/temporal_spec.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Attributor::Temporal do
|
4
|
-
subject(:type)
|
4
|
+
subject(:type) do
|
5
|
+
Class.new do
|
6
|
+
include Attributor::Temporal
|
7
|
+
end
|
8
|
+
end
|
5
9
|
|
6
10
|
it 'raises an exception for native_type' do
|
7
11
|
expect { type.native_type }.to raise_error(NotImplementedError)
|
data/spec/types/time_spec.rb
CHANGED
@@ -90,4 +90,13 @@ describe Attributor::Time do
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
93
|
+
context '.as_json_schema' do
|
94
|
+
subject(:js){ type.as_json_schema }
|
95
|
+
it 'adds the right attributes' do
|
96
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
97
|
+
expect(js[:type]).to eq(:string)
|
98
|
+
expect(js[:format]).to eq(:'time')
|
99
|
+
expect(js[:'x-type_name']).to eq('Time')
|
100
|
+
end
|
101
|
+
end
|
93
102
|
end
|
data/spec/types/uri_spec.rb
CHANGED
@@ -109,4 +109,13 @@ describe Attributor::URI do
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
end
|
112
|
+
context '.as_json_schema' do
|
113
|
+
subject(:js){ type.as_json_schema }
|
114
|
+
it 'adds the right attributes' do
|
115
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
116
|
+
expect(js[:type]).to eq(:string)
|
117
|
+
expect(js[:format]).to eq(:uri)
|
118
|
+
expect(js[:'x-type_name']).to eq('URI')
|
119
|
+
end
|
120
|
+
end
|
112
121
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attributor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '5.
|
4
|
+
version: '5.5'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-08-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hashie
|
@@ -361,6 +361,7 @@ files:
|
|
361
361
|
- spec/smart_attribute_selector_spec.rb
|
362
362
|
- spec/spec_helper.rb
|
363
363
|
- spec/support/hashes.rb
|
364
|
+
- spec/support/integers.rb
|
364
365
|
- spec/support/models.rb
|
365
366
|
- spec/support/polymorphics.rb
|
366
367
|
- spec/type_spec.rb
|
@@ -406,7 +407,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
406
407
|
- !ruby/object:Gem::Version
|
407
408
|
version: '0'
|
408
409
|
requirements: []
|
409
|
-
|
410
|
+
rubyforge_project:
|
411
|
+
rubygems_version: 2.6.14
|
410
412
|
signing_key:
|
411
413
|
specification_version: 4
|
412
414
|
summary: A powerful attribute and type management library for Ruby
|
@@ -422,6 +424,7 @@ test_files:
|
|
422
424
|
- spec/smart_attribute_selector_spec.rb
|
423
425
|
- spec/spec_helper.rb
|
424
426
|
- spec/support/hashes.rb
|
427
|
+
- spec/support/integers.rb
|
425
428
|
- spec/support/models.rb
|
426
429
|
- spec/support/polymorphics.rb
|
427
430
|
- spec/type_spec.rb
|