attributor 5.4 → 5.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|