json_model_rb 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6055ccf369735b5c3461df4c1e1c84fb376bbfb56db264c4b2cff411a0408f7b
4
- data.tar.gz: 269014c71581e5fa7053b00e37cd0e9faa854b2af11b243aab322da3673cc758
3
+ metadata.gz: 9599ecfecdb61edf6173927d8e33bf9a96686fe765aa94274611788eff07312a
4
+ data.tar.gz: 160000519dda11b0ce29648dd17d12436e86514acbc013de01da96f23949effe
5
5
  SHA512:
6
- metadata.gz: 9e9145acb6866d32cd0a34a507bf9b30658c7197e5098e630f679bd874697639d66cf17c60fd9b8534418c6f01c7470508193077e260d935845b6275ee5f9636
7
- data.tar.gz: 1d5c2c8cd76cb864be16eb30787a53349ccc5c28f08d159f6e1984e87264c91dea759184ed7f67ff61720bf912e92deeaa1a014c560338ca86694ab5288585c4
6
+ metadata.gz: 12a4b646df7225003ec60e07dccbbb6c5115e405736af120712d0659fc14fea268373701e30f7f3e2b07f3f095b3f0a797946e97794d07b60b15ea368d48b7cf
7
+ data.tar.gz: dee7c155478f1e4532348e481587d00045b325744e0014638acfe4c28074f0569f19e1210a01f54de848f4eea3ce4b42d3d2830a0f81d2f1f9abae7aa1faeca6
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ module Errors
5
+ class TypeError < Error
6
+ end
7
+ end
8
+ end
@@ -2,4 +2,5 @@
2
2
 
3
3
  require_relative('errors/error')
4
4
  require_relative('errors/invalid_ref_mode_error')
5
+ require_relative('errors/type_error')
5
6
  require_relative('errors/unknown_attribute_error')
@@ -17,8 +17,8 @@ module JsonModel
17
17
  # @param [Object] type
18
18
  # @param [Hash] options
19
19
  def property(name, type:, **options)
20
- property_options = options.slice(:default, :optional, :ref_mode)
21
- resolved_type = TypeSpec.resolve(type, **options.except(:default, :optional, :ref_mode))
20
+ property_options = options.slice(:default, :optional, :ref_mode, :as)
21
+ resolved_type = TypeSpec.resolve(type, **options.except(:default, :optional, :ref_mode, :as))
22
22
  add_property(name, type: resolved_type, **property_options)
23
23
  descendants.each { |subclass| subclass.add_property(name, type: resolved_type, **property_options) }
24
24
  end
@@ -54,7 +54,7 @@ module JsonModel
54
54
 
55
55
  # @param [Property] property
56
56
  def define_setter(property)
57
- define_method("#{property.name}=") { |value| attributes[property.name] = value }
57
+ define_method("#{property.name}=") { |value| attributes[property.name] = property.type.cast(value) }
58
58
  end
59
59
 
60
60
  # @param [Property] property
@@ -23,9 +23,20 @@ module JsonModel
23
23
  end
24
24
 
25
25
  schema_id(name)
26
+ additional_properties(false)
26
27
  end
27
28
 
28
29
  class_methods do
30
+ # @param [String, nil] description
31
+ # @return [String, nil]
32
+ def description(description = nil)
33
+ if description
34
+ meta_attributes[:description] = description
35
+ else
36
+ meta_attributes[:description]
37
+ end
38
+ end
39
+
29
40
  # @param [String, nil] id
30
41
  # @return [String, nil]
31
42
  def schema_id(id = nil)
@@ -45,6 +45,12 @@ module JsonModel
45
45
  @type.referenced_schemas
46
46
  end
47
47
 
48
+ # @param [::Object] json
49
+ # @return [::Object]
50
+ def cast(json)
51
+ json.map { |item| @type.cast(item) }
52
+ end
53
+
48
54
  private
49
55
 
50
56
  # @param [Symbol] name
@@ -9,6 +9,20 @@ module JsonModel
9
9
  def initialize(*types, **options)
10
10
  super(:allOf, *types, **options)
11
11
  end
12
+
13
+ # @param [::Object] json
14
+ # @return [::Object]
15
+ def cast(json)
16
+ if json.nil?
17
+ return nil
18
+ end
19
+
20
+ @types.map do |type|
21
+ type.cast(json)
22
+ rescue StandardError
23
+ raise(Errors::TypeError, "Value #{json.inspect} cannot be cast to allOf type #{self}")
24
+ end
25
+ end
12
26
  end
13
27
  end
14
28
  end
@@ -9,6 +9,25 @@ module JsonModel
9
9
  def initialize(*types, **options)
10
10
  super(:anyOf, *types, **options)
11
11
  end
12
+
13
+ # @param [::Object] json
14
+ # @return [::Object]
15
+ def cast(json)
16
+ if json.nil?
17
+ return nil
18
+ end
19
+
20
+ type = @types.detect do |type|
21
+ type.cast(json)
22
+ rescue StandardError
23
+ false
24
+ end
25
+ if type.nil?
26
+ raise(Errors::TypeError, "No matching type found in AnyOf for value: #{json.inspect}")
27
+ else
28
+ type.cast(json)
29
+ end
30
+ end
12
31
  end
13
32
  end
14
33
  end
@@ -9,6 +9,29 @@ module JsonModel
9
9
  def initialize(*types, **options)
10
10
  super(:oneOf, *types, **options)
11
11
  end
12
+
13
+ # @param [::Object] json
14
+ # @return [::Object]
15
+ def cast(json)
16
+ if json.nil?
17
+ return nil
18
+ end
19
+
20
+ types = @types.select do |type|
21
+ type.cast(json)
22
+ rescue StandardError
23
+ false
24
+ end
25
+
26
+ case types.size
27
+ when 0
28
+ raise(Errors::TypeError, "No matching type found in OneOf for value: #{json.inspect}")
29
+ when 1
30
+ types.first.cast(json)
31
+ else
32
+ raise(Errors::TypeError, "Multiple matching types found in OneOf for value: #{json.inspect}")
33
+ end
34
+ end
12
35
  end
13
36
  end
14
37
  end
@@ -23,6 +23,12 @@ module JsonModel
23
23
  def referenced_schemas
24
24
  @types.flat_map(&:referenced_schemas)
25
25
  end
26
+
27
+ # @param [::Object] json
28
+ # @return [::Object]
29
+ def cast(json)
30
+ raise(NotImplementedError)
31
+ end
26
32
  end
27
33
  end
28
34
  end
@@ -19,6 +19,12 @@ module JsonModel
19
19
  def referenced_schemas
20
20
  [@type]
21
21
  end
22
+
23
+ # @param [::Object] json
24
+ # @return [::Object]
25
+ def cast(json)
26
+ @type.new(**json)
27
+ end
22
28
  end
23
29
  end
24
30
  end
@@ -5,7 +5,7 @@ module JsonModel
5
5
  class Primitive
6
6
  class Boolean < Primitive
7
7
  def initialize
8
- super('boolean')
8
+ super(types: [TrueClass, FalseClass], schema_type: 'boolean')
9
9
  end
10
10
  end
11
11
  end
@@ -6,7 +6,7 @@ module JsonModel
6
6
  class Integer < Numeric
7
7
  # @param [Hash] options
8
8
  def initialize(**options)
9
- super('integer', **options)
9
+ super(types: [::Integer], schema_type: 'integer', **options)
10
10
  end
11
11
 
12
12
  # @param [Symbol] name
@@ -5,7 +5,7 @@ module JsonModel
5
5
  class Primitive
6
6
  class Null < Primitive
7
7
  def initialize
8
- super('null')
8
+ super(types: [NilClass], schema_type: 'null')
9
9
  end
10
10
  end
11
11
  end
@@ -6,7 +6,7 @@ module JsonModel
6
6
  class Number < Numeric
7
7
  # @param [Hash] options
8
8
  def initialize(**options)
9
- super('number', **options)
9
+ super(types: [Float], schema_type: 'number', **options)
10
10
  end
11
11
  end
12
12
  end
@@ -4,21 +4,23 @@ module JsonModel
4
4
  class TypeSpec
5
5
  class Primitive
6
6
  class Numeric < Primitive
7
- # @param [String] type
7
+ # @param [::Array<Class>] types
8
+ # @param [String] schema_type
8
9
  # @param [Integer, nil] multiple_of
9
10
  # @param [Integer, nil] minimum
10
11
  # @param [Integer, nil] maximum
11
12
  # @param [Integer, nil] exclusive_minimum
12
13
  # @param [Integer, nil] exclusive_maximum
13
14
  def initialize(
14
- type,
15
+ types:,
16
+ schema_type:,
15
17
  multiple_of: nil,
16
18
  minimum: nil,
17
19
  maximum: nil,
18
20
  exclusive_minimum: nil,
19
21
  exclusive_maximum: nil
20
22
  )
21
- super(type)
23
+ super(types: types, schema_type: schema_type)
22
24
  @multiple_of = multiple_of
23
25
  @minimum = minimum
24
26
  @maximum = maximum
@@ -82,7 +82,7 @@ module JsonModel
82
82
  # @param [Regexp, nil] pattern
83
83
  # @param [String, nil] format
84
84
  def initialize(min_length: nil, max_length: nil, pattern: nil, format: nil)
85
- super('string')
85
+ super(types: [::String], schema_type: 'string')
86
86
 
87
87
  @min_length = min_length
88
88
  @max_length = max_length
@@ -126,6 +126,8 @@ module JsonModel
126
126
 
127
127
  private
128
128
 
129
+ # @param [Class] klass
130
+ # @param [Symbol] name
129
131
  def register_format_validation(klass, name)
130
132
  if @format.nil?
131
133
  return
@@ -3,16 +3,30 @@
3
3
  module JsonModel
4
4
  class TypeSpec
5
5
  class Primitive < TypeSpec
6
- # @param [String] type
7
- def initialize(type)
6
+ # @param [::Array<Class>] types
7
+ # @param [String] schema_type
8
+ def initialize(types:, schema_type:)
8
9
  super()
9
- @type = type
10
+ @types = types
11
+ @schema_type = schema_type
10
12
  end
11
13
 
12
14
  # @param [Hash] _options
13
15
  # @return [Hash]
14
16
  def as_schema(**_options)
15
- { type: @type }
17
+ { type: @schema_type }
18
+ end
19
+
20
+ # @param [::Object] json
21
+ # @return [::Object]
22
+ def cast(json)
23
+ if json.nil?
24
+ nil
25
+ elsif @types.any? { |type| json.is_a?(type) }
26
+ json
27
+ else
28
+ raise(Errors::TypeError, "Expected #{@type}, got #{json.class} (#{json.inspect})")
29
+ end
16
30
  end
17
31
  end
18
32
  end
@@ -23,6 +23,12 @@ module JsonModel
23
23
  []
24
24
  end
25
25
 
26
+ # @param [::Object] json
27
+ # @return [::Object]
28
+ def cast(json)
29
+ json
30
+ end
31
+
26
32
  class << self
27
33
  # @param [Object] type
28
34
  # @param [Hash] options
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JsonModel
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('spec_helper')
4
+
5
+ RSpec.describe('File system schema') do
6
+ before do
7
+ stub_const(
8
+ 'DiskDevice',
9
+ Class.new do
10
+ include(JsonModel::Schema)
11
+
12
+ property(:type, type: T::Enum['disk'])
13
+ property(:device, type: String, pattern: %r{\A/dev/[^/]+(/[^/]+)*\z})
14
+ end,
15
+ )
16
+
17
+ stub_const(
18
+ 'DiskUuid',
19
+ Class.new do
20
+ include(JsonModel::Schema)
21
+
22
+ property(:type, type: T::Enum['disk'])
23
+ property(
24
+ :label,
25
+ type: String,
26
+ pattern: /\A[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\z/,
27
+ )
28
+ end,
29
+ )
30
+
31
+ stub_const(
32
+ 'Nfs',
33
+ Class.new do
34
+ include(JsonModel::Schema)
35
+
36
+ property(:type, type: T::Enum['nfs'])
37
+ property(:remote_path, type: String, pattern: %r{\A(/[^/]+)+\z}, as: :remotePath)
38
+ property(:server, type: String, format: :ipv4)
39
+ end,
40
+ )
41
+
42
+ stub_const(
43
+ 'Tmpfs',
44
+ Class.new do
45
+ include(JsonModel::Schema)
46
+
47
+ property(:type, type: T::Enum['tmpfs'])
48
+ property(:size_in_mb, type: Integer, minimum: 16, maximum: 512, as: :sizeInMB)
49
+ end,
50
+ )
51
+
52
+ stub_const(
53
+ 'Fstab',
54
+ Class.new do
55
+ include(JsonModel::Schema)
56
+
57
+ description('JSON Schema for an fstab entry')
58
+ property(:storage, type: T::OneOf[DiskDevice, DiskUuid, Nfs, Tmpfs], ref_mode: JsonModel::RefMode::LOCAL)
59
+ property(:fstype, type: T::Enum['ext3', 'ext4', 'btrfs'], optional: true)
60
+ property(:options, type: T::Array[String], min_items: 1, unique_items: true, optional: true)
61
+ property(:readonly, type: T::Boolean, optional: true)
62
+ end,
63
+ )
64
+ end
65
+
66
+ it('renders the schema') do
67
+ expect(Fstab.as_schema)
68
+ .to(
69
+ eq(
70
+ {
71
+ description: 'JSON Schema for an fstab entry',
72
+ type: 'object',
73
+ required: %i(storage),
74
+ properties: {
75
+ storage: {
76
+ oneOf: [
77
+ { '$ref': '#/$defs/DiskDevice' },
78
+ { '$ref': '#/$defs/DiskUuid' },
79
+ { '$ref': '#/$defs/Nfs' },
80
+ { '$ref': '#/$defs/Tmpfs' },
81
+ ],
82
+ },
83
+ fstype: {
84
+ enum: %w(ext3 ext4 btrfs),
85
+ },
86
+ options: {
87
+ type: 'array',
88
+ minItems: 1,
89
+ items: {
90
+ type: 'string',
91
+ },
92
+ uniqueItems: true,
93
+ },
94
+ readonly: {
95
+ type: 'boolean',
96
+ },
97
+ },
98
+ additionalProperties: false,
99
+ '$defs': {
100
+ DiskDevice: {
101
+ properties: {
102
+ type: {
103
+ enum: ['disk'],
104
+ },
105
+ device: {
106
+ type: 'string',
107
+ pattern: '\\A/dev/[^/]+(/[^/]+)*\\z',
108
+ },
109
+ },
110
+ required: %i(device type),
111
+ additionalProperties: false,
112
+ type: 'object',
113
+ },
114
+ DiskUuid: {
115
+ properties: {
116
+ type: { enum: ['disk'] },
117
+ label: {
118
+ type: 'string',
119
+ pattern: '\\A[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\z',
120
+ },
121
+ },
122
+ required: %i(label type),
123
+ additionalProperties: false,
124
+ type: 'object',
125
+ },
126
+ Nfs: {
127
+ properties: {
128
+ type: { enum: ['nfs'] },
129
+ remotePath: {
130
+ type: 'string',
131
+ pattern: '\\A(/[^/]+)+\\z',
132
+ },
133
+ server: {
134
+ type: 'string',
135
+ format: 'ipv4',
136
+ },
137
+ },
138
+ required: %i(remotePath server type),
139
+ additionalProperties: false,
140
+ type: 'object',
141
+ },
142
+ Tmpfs: {
143
+ properties: {
144
+ type: { enum: ['tmpfs'] },
145
+ sizeInMB: { type: 'integer', minimum: 16, maximum: 512 },
146
+ },
147
+ required: %i(sizeInMB type),
148
+ additionalProperties: false,
149
+ type: 'object',
150
+ },
151
+ },
152
+ },
153
+ ),
154
+ )
155
+ end
156
+
157
+ it('can instantiate a model') do
158
+ instance = Fstab.new(
159
+ storage: { device: '/dev/sda1', type: 'disk' },
160
+ fstype: 'ext4',
161
+ options: %w(rw noatime),
162
+ )
163
+
164
+ expect(instance.storage).to(be_a(DiskDevice))
165
+ expect(instance.storage.device).to(eq('/dev/sda1'))
166
+ expect(instance.fstype).to(eq('ext4'))
167
+ expect(instance.options).to(eq(%w(rw noatime)))
168
+ expect(instance.readonly).to(be_nil)
169
+ end
170
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('spec_helper')
4
+
5
+ RSpec.describe('User schema') do
6
+ before do
7
+ address_class = Class.new do
8
+ include(JsonModel::Schema)
9
+
10
+ def self.name
11
+ 'Address'
12
+ end
13
+
14
+ property(:street, type: String)
15
+ property(:city, type: String)
16
+ property(:state, type: String, optional: true)
17
+ property(:postal_code, type: String, pattern: /\A\d{5}(-\d{4})?\z/, optional: true)
18
+ property(:country, type: String, default: 'USA')
19
+ end
20
+
21
+ stub_const('Address', address_class)
22
+
23
+ user_class = Class.new do
24
+ include(JsonModel::Schema)
25
+
26
+ def self.name
27
+ 'User'
28
+ end
29
+
30
+ property(:name, type: String)
31
+ property(:email, type: String, format: :email)
32
+ property(:age, type: Integer, minimum: 0, maximum: 120, optional: true)
33
+ property(:active, type: T::Boolean, default: true, optional: true)
34
+ property(:addresses, type: T::Array[Address], ref_mode: JsonModel::RefMode::LOCAL)
35
+ property(:tags, type: T::Array[String], optional: true)
36
+ end
37
+
38
+ stub_const('User', user_class)
39
+ end
40
+
41
+ it('renders the schema') do
42
+ expect(User.as_schema)
43
+ .to(
44
+ eq(
45
+ {
46
+ type: 'object',
47
+ properties: {
48
+ name: { type: 'string' },
49
+ email: { type: 'string', format: 'email' },
50
+ age: { type: 'integer', minimum: 0, maximum: 120 },
51
+ active: { type: 'boolean', default: true },
52
+ addresses: {
53
+ type: 'array',
54
+ items: { '$ref': '#/$defs/Address' },
55
+ },
56
+ tags: { type: 'array', items: { type: 'string' } },
57
+ },
58
+ additionalProperties: false,
59
+ required: %i(addresses email name),
60
+ '$defs': {
61
+ Address: {
62
+ type: 'object',
63
+ properties: {
64
+ city: { type: 'string' },
65
+ country: { type: 'string', default: 'USA' },
66
+ postal_code: { type: 'string', pattern: '\A\d{5}(-\d{4})?\z' },
67
+ state: { type: 'string' },
68
+ street: { type: 'string' },
69
+ },
70
+ required: %i(city country street),
71
+ additionalProperties: false,
72
+ },
73
+ },
74
+ },
75
+ ),
76
+ )
77
+ end
78
+
79
+ it('can instantiate a model') do
80
+ user = User.new(name: 'Foo', email: 'foo@example.com', addresses: [{ street: '123 Main St', city: 'Anytown' }])
81
+
82
+ expect(user.name).to(eq('Foo'))
83
+ expect(user.email).to(eq('foo@example.com'))
84
+ expect(user.active).to(eq(true))
85
+ expect(user.age).to(be_nil)
86
+ expect(user.addresses.first.street).to(eq('123 Main St'))
87
+ expect(user.addresses.first.country).to(eq('USA'))
88
+ expect(user.addresses.first.postal_code).to(be_nil)
89
+ expect(user.addresses.first.state).to(be_nil)
90
+ expect(user.addresses.first.city).to(eq('Anytown'))
91
+ end
92
+ end
@@ -56,6 +56,26 @@ RSpec.describe(JsonModel::SchemaMeta) do
56
56
  end
57
57
  end
58
58
 
59
+ describe('.description') do
60
+ let(:klass) do
61
+ Class.new do
62
+ include(JsonModel::SchemaMeta)
63
+ end
64
+ end
65
+
66
+ it('is nil by default') do
67
+ expect(klass.description)
68
+ .to(be_nil)
69
+ end
70
+
71
+ it('can be changed') do
72
+ klass.description('This is a test description.')
73
+
74
+ expect(klass.description)
75
+ .to(eq('This is a test description.'))
76
+ end
77
+ end
78
+
59
79
  describe('.additional_properties') do
60
80
  let(:klass) do
61
81
  Class.new do
@@ -63,9 +83,9 @@ RSpec.describe(JsonModel::SchemaMeta) do
63
83
  end
64
84
  end
65
85
 
66
- it('is missing by default') do
86
+ it('is false by default') do
67
87
  expect(klass.meta_attributes)
68
- .to(eq({}))
88
+ .to(eq({ additionalProperties: false }))
69
89
  end
70
90
 
71
91
  it('can be changed') do
data/spec/schema_spec.rb CHANGED
@@ -72,7 +72,7 @@ RSpec.describe(JsonModel::Schema) do
72
72
 
73
73
  it('returns an empty schema') do
74
74
  expect(klass.as_schema)
75
- .to(eq({ type: 'object' }))
75
+ .to(eq({ type: 'object', additionalProperties: false }))
76
76
  end
77
77
 
78
78
  it('includes the schema id') do
@@ -84,6 +84,7 @@ RSpec.describe(JsonModel::Schema) do
84
84
  {
85
85
  '$id': 'https://example.com/schemas/example.json',
86
86
  type: 'object',
87
+ additionalProperties: false,
87
88
  },
88
89
  ),
89
90
  )
@@ -116,6 +117,7 @@ RSpec.describe(JsonModel::Schema) do
116
117
  foo: { type: 'string' },
117
118
  },
118
119
  required: %i(bam baz foo),
120
+ additionalProperties: false,
119
121
  },
120
122
  ),
121
123
  )
@@ -176,13 +178,16 @@ RSpec.describe(JsonModel::Schema) do
176
178
  type: 'object',
177
179
  properties: { foo: { type: 'string' } },
178
180
  required: %i(foo),
181
+ additionalProperties: false,
179
182
  },
180
183
  },
184
+ additionalProperties: false,
181
185
  '$defs': {
182
186
  Bar: {
183
187
  type: 'object',
184
188
  properties: { bar: { type: 'string' } },
185
189
  required: %i(bar),
190
+ additionalProperties: false,
186
191
  },
187
192
  },
188
193
  required: %i(bam bar foo),
@@ -209,6 +214,7 @@ RSpec.describe(JsonModel::Schema) do
209
214
  '$id': 'https://example.com/schemas/child.json',
210
215
  '$ref': 'https://example.com/schemas/example.json',
211
216
  type: 'object',
217
+ additionalProperties: false,
212
218
  },
213
219
  ),
214
220
  )
@@ -17,4 +17,41 @@ RSpec.describe(JsonModel::TypeSpec::Composition::AllOf) do
17
17
  )
18
18
  end
19
19
  end
20
+
21
+ describe('#cast') do
22
+ let(:foo) do
23
+ Class.new do
24
+ include(JsonModel::Schema)
25
+
26
+ additional_properties(true)
27
+ property(:foo, type: String)
28
+ end
29
+ end
30
+
31
+ let(:bar) do
32
+ Class.new do
33
+ include(JsonModel::Schema)
34
+
35
+ additional_properties(true)
36
+ property(:bar, type: String)
37
+ end
38
+ end
39
+
40
+ subject { described_class.new(JsonModel::TypeSpec::Object.new(foo), JsonModel::TypeSpec::Object.new(bar)) }
41
+
42
+ it('raises if one of the types fails') do
43
+ expect { subject.cast({ foo: 'foo' }) }
44
+ .to(raise_error(JsonModel::Errors::TypeError))
45
+ end
46
+
47
+ it('returns the value if all types pass') do
48
+ foo_instance, bar_instance = subject.cast({ foo: 'foo', bar: 'bar' })
49
+
50
+ expect(foo_instance).to(be_instance_of(foo))
51
+ expect(foo_instance.foo).to(eq('foo'))
52
+
53
+ expect(bar_instance).to(be_instance_of(bar))
54
+ expect(bar_instance.bar).to(eq('bar'))
55
+ end
56
+ end
20
57
  end
@@ -17,4 +17,38 @@ RSpec.describe(JsonModel::TypeSpec::Composition::AnyOf) do
17
17
  )
18
18
  end
19
19
  end
20
+
21
+ describe('#cast') do
22
+ let(:foo) do
23
+ Class.new do
24
+ include(JsonModel::Schema)
25
+
26
+ additional_properties(true)
27
+ property(:foo, type: String)
28
+ end
29
+ end
30
+
31
+ let(:bar) do
32
+ Class.new do
33
+ include(JsonModel::Schema)
34
+
35
+ additional_properties(true)
36
+ property(:bar, type: String)
37
+ end
38
+ end
39
+
40
+ subject { described_class.new(JsonModel::TypeSpec::Object.new(foo), JsonModel::TypeSpec::Object.new(bar)) }
41
+
42
+ it('raises if all of the types fail') do
43
+ expect { subject.cast({ bam: 'bam' }) }
44
+ .to(raise_error(JsonModel::Errors::TypeError))
45
+ end
46
+
47
+ it('returns the value if any type passes') do
48
+ foo_instance = subject.cast({ foo: 'foo' })
49
+
50
+ expect(foo_instance).to(be_instance_of(foo))
51
+ expect(foo_instance.foo).to(eq('foo'))
52
+ end
53
+ end
20
54
  end
@@ -17,4 +17,43 @@ RSpec.describe(JsonModel::TypeSpec::Composition::OneOf) do
17
17
  )
18
18
  end
19
19
  end
20
+
21
+ describe('#cast') do
22
+ let(:foo) do
23
+ Class.new do
24
+ include(JsonModel::Schema)
25
+
26
+ additional_properties(true)
27
+ property(:foo, type: String)
28
+ end
29
+ end
30
+
31
+ let(:bar) do
32
+ Class.new do
33
+ include(JsonModel::Schema)
34
+
35
+ additional_properties(true)
36
+ property(:bar, type: String)
37
+ end
38
+ end
39
+
40
+ subject { described_class.new(JsonModel::TypeSpec::Object.new(foo), JsonModel::TypeSpec::Object.new(bar)) }
41
+
42
+ it('raises if all of the types fail') do
43
+ expect { subject.cast({ bam: 'bam' }) }
44
+ .to(raise_error(JsonModel::Errors::TypeError))
45
+ end
46
+
47
+ it('raises if more than one type passes') do
48
+ expect { subject.cast({ foo: 'foo', bar: 'bar' }) }
49
+ .to(raise_error(JsonModel::Errors::TypeError))
50
+ end
51
+
52
+ it('returns the value if one type passes') do
53
+ foo_instance = subject.cast({ foo: 'foo' })
54
+
55
+ expect(foo_instance).to(be_instance_of(foo))
56
+ expect(foo_instance.foo).to(eq('foo'))
57
+ end
58
+ end
20
59
  end
@@ -27,6 +27,7 @@ RSpec.describe(JsonModel::TypeSpec::Composition) do
27
27
  type: 'object',
28
28
  properties: { foo: { type: 'string' } },
29
29
  required: %i(foo),
30
+ additionalProperties: false,
30
31
  },
31
32
  ],
32
33
  },
@@ -5,27 +5,27 @@ require('spec_helper')
5
5
  RSpec.describe(JsonModel::TypeSpec::Primitive::Numeric) do
6
6
  describe('#as_schema') do
7
7
  it('includes the multipleOf attribute') do
8
- expect(described_class.new('number', multiple_of: 5).as_schema)
8
+ expect(described_class.new(types: [Integer, Float], schema_type: 'number', multiple_of: 5).as_schema)
9
9
  .to(eq({ type: 'number', multipleOf: 5 }))
10
10
  end
11
11
 
12
12
  it('includes the minimum attribute') do
13
- expect(described_class.new('number', minimum: 5).as_schema)
13
+ expect(described_class.new(types: [Integer, Float], schema_type: 'number', minimum: 5).as_schema)
14
14
  .to(eq({ type: 'number', minimum: 5 }))
15
15
  end
16
16
 
17
17
  it('includes the maximum attribute') do
18
- expect(described_class.new('number', maximum: 5).as_schema)
18
+ expect(described_class.new(types: [Integer, Float], schema_type: 'number', maximum: 5).as_schema)
19
19
  .to(eq({ type: 'number', maximum: 5 }))
20
20
  end
21
21
 
22
22
  it('includes the exclusiveMinimum attribute') do
23
- expect(described_class.new('number', exclusive_minimum: 5).as_schema)
23
+ expect(described_class.new(types: [Integer, Float], schema_type: 'number', exclusive_minimum: 5).as_schema)
24
24
  .to(eq({ type: 'number', exclusiveMinimum: 5 }))
25
25
  end
26
26
 
27
27
  it('includes the exclusiveMaximum attribute') do
28
- expect(described_class.new('number', exclusive_maximum: 5).as_schema)
28
+ expect(described_class.new(types: [Integer, Float], schema_type: 'number', exclusive_maximum: 5).as_schema)
29
29
  .to(eq({ type: 'number', exclusiveMaximum: 5 }))
30
30
  end
31
31
  end
@@ -46,7 +46,7 @@ RSpec.describe(JsonModel::TypeSpec::Primitive::Numeric) do
46
46
  context('for multiple of') do
47
47
  before do
48
48
  described_class
49
- .new('number', multiple_of: 5)
49
+ .new(types: [Integer, Float], schema_type: 'number', multiple_of: 5)
50
50
  .register_validations(:foo, klass)
51
51
  end
52
52
 
@@ -72,7 +72,7 @@ RSpec.describe(JsonModel::TypeSpec::Primitive::Numeric) do
72
72
  context('for minimum') do
73
73
  before do
74
74
  described_class
75
- .new('number', minimum: 5)
75
+ .new(types: [Integer, Float], schema_type: 'number', minimum: 5)
76
76
  .register_validations(:foo, klass)
77
77
  end
78
78
 
@@ -98,7 +98,7 @@ RSpec.describe(JsonModel::TypeSpec::Primitive::Numeric) do
98
98
  context('for maximum') do
99
99
  before do
100
100
  described_class
101
- .new('number', maximum: 5)
101
+ .new(types: [Integer, Float], schema_type: 'number', maximum: 5)
102
102
  .register_validations(:foo, klass)
103
103
  end
104
104
 
@@ -124,7 +124,7 @@ RSpec.describe(JsonModel::TypeSpec::Primitive::Numeric) do
124
124
  context('for exclusive_minimum') do
125
125
  before do
126
126
  described_class
127
- .new('number', exclusive_minimum: 5)
127
+ .new(types: [Integer, Float], schema_type: 'number', exclusive_minimum: 5)
128
128
  .register_validations(:foo, klass)
129
129
  end
130
130
 
@@ -150,7 +150,7 @@ RSpec.describe(JsonModel::TypeSpec::Primitive::Numeric) do
150
150
  context('for exclusive_maximum') do
151
151
  before do
152
152
  described_class
153
- .new('number', exclusive_maximum: 5)
153
+ .new(types: [Integer, Float], schema_type: 'number', exclusive_maximum: 5)
154
154
  .register_validations(:foo, klass)
155
155
  end
156
156
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_model_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Gillesberger
@@ -108,6 +108,7 @@ files:
108
108
  - lib/json_model/errors.rb
109
109
  - lib/json_model/errors/error.rb
110
110
  - lib/json_model/errors/invalid_ref_mode_error.rb
111
+ - lib/json_model/errors/type_error.rb
111
112
  - lib/json_model/errors/unknown_attribute_error.rb
112
113
  - lib/json_model/properties.rb
113
114
  - lib/json_model/property.rb
@@ -138,6 +139,8 @@ files:
138
139
  - lib/json_model/types/one_of.rb
139
140
  - lib/json_model/version.rb
140
141
  - spec/config_spec.rb
142
+ - spec/examples/file_system_spec.rb
143
+ - spec/examples/user_spec.rb
141
144
  - spec/json_model_spec.rb
142
145
  - spec/properties_spec.rb
143
146
  - spec/property_spec.rb