json_model_rb 0.1.15 → 0.1.17

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: ae63a0cef5ca421b32a3a103aeaaa8c61cfe9a1edd7871567e8c8f5905678f5d
4
- data.tar.gz: b51047217b904c7194deb9c21bcd66a175ae18779360d68a78db5efa3190d12a
3
+ metadata.gz: 601bb6f47fc30b627b972036213a1388f0e844a42476988cbeb19156edf59ea0
4
+ data.tar.gz: 4550f13f61deaf8b455bf7b8437e05e13401aae8d33932460e722574e0bc6ee7
5
5
  SHA512:
6
- metadata.gz: 05f4f40b76349054936c5df92514615c3a86ebb9eaa54d5e9105c59dc33d836fc4718f98d1590a34d7569ed86e14a292ee70a2f7e17b241fea337f81074e4d82
7
- data.tar.gz: b9b97ae062116a998eb894e8a98d486dc8ffa141d2a49c5b4755afe41358b0ff4b8aed7a8283bbe744107980a3206a858b2de6d16deacdbe01102bf5a4aed300
6
+ metadata.gz: 45305cffcc227dd58959f1b5ae64662d378f6d703e85b53da9319e9b6366d075c0c20a70465fc8f1fbb319b83d58d5637682f4a29faf3e8513bf78aa632f2b20
7
+ data.tar.gz: 21de5df72ff9f5b1244e530133d0c6d2e6cddf6123a25146a07cd79db73f1e8083488f7dc37fe32fab72c169f05baba296ba96fa6292f52f188889adb5a48cf4
@@ -39,5 +39,7 @@ module JsonModel
39
39
  end
40
40
 
41
41
  option(:validate_after_instantiation, default: true)
42
+
43
+ option(:schema_version)
42
44
  end
43
45
  end
@@ -17,12 +17,12 @@ module JsonModel
17
17
  def self.inherited(subclass)
18
18
  super
19
19
  subclass.schema_id(JsonModel.config.schema_id_naming_strategy.call(subclass))
20
- subclass.additional_properties(false)
20
+ subclass.schema_version(JsonModel.config.schema_version)
21
21
  subclass.meta_attributes[:$ref] = schema_id
22
22
  end
23
23
 
24
24
  schema_id(JsonModel.config.schema_id_naming_strategy.call(self))
25
- additional_properties(false)
25
+ schema_version(JsonModel.config.schema_version)
26
26
  end
27
27
 
28
28
  class_methods do
@@ -71,6 +71,16 @@ module JsonModel
71
71
  end
72
72
  end
73
73
 
74
+ # @param [Boolean, nil] value
75
+ # @return [Boolean]
76
+ def unevaluated_properties(value = nil)
77
+ if value.nil?
78
+ meta_attributes[:unevaluatedProperties] || false
79
+ else
80
+ meta_attributes[:unevaluatedProperties] = value
81
+ end
82
+ end
83
+
74
84
  # @param [Symbol, nil] version
75
85
  # @return [Boolean]
76
86
  def schema_version(version = nil)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ class TypeSpec
5
+ class Castable < TypeSpec
6
+ # @param [String] format
7
+ # @param [Proc] cast_block
8
+ def initialize(format:, &cast_block)
9
+ super()
10
+ @format = format
11
+ @cast_block = cast_block
12
+ end
13
+
14
+ # @param [Hash] _options
15
+ # @return [Hash]
16
+ def as_schema(**_options)
17
+ {
18
+ type: 'string',
19
+ format: @format,
20
+ }
21
+ end
22
+
23
+ # @param [::Object] json
24
+ # @return [::Object, nil]
25
+ def cast(json)
26
+ if json.nil?
27
+ nil
28
+ else
29
+ @cast_block.call(json)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -62,7 +62,7 @@ module JsonModel
62
62
  end
63
63
  },
64
64
  uuid: lambda { |v|
65
- SecureRandom(v.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i))
65
+ v.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i)
66
66
  },
67
67
  regex: lambda { |v|
68
68
  begin
@@ -79,7 +79,7 @@ module JsonModel
79
79
  # @param [Integer, nil] min_length
80
80
  # @param [Integer, nil] max_length
81
81
  # @param [Regexp, nil] pattern
82
- # @param [String, nil] format
82
+ # @param [Symbol, nil] format
83
83
  def initialize(min_length: nil, max_length: nil, pattern: nil, format: nil)
84
84
  super(types: [::String], schema_type: 'string')
85
85
 
@@ -115,7 +115,6 @@ module JsonModel
115
115
  end
116
116
  if @format
117
117
  if JSON_SCHEMA_FORMATS.key?(@format)
118
-
119
118
  register_format_validation(klass, name)
120
119
  else
121
120
  raise(ArgumentError, "Invalid format: #{@format}")
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative('type_spec/array')
4
+ require_relative('type_spec/castable')
4
5
  require_relative('type_spec/composition')
5
6
  require_relative('type_spec/const')
6
7
  require_relative('type_spec/enum')
@@ -9,6 +10,20 @@ require_relative('type_spec/primitive')
9
10
 
10
11
  module JsonModel
11
12
  class TypeSpec
13
+ TYPE_MAP = {
14
+ Date => Castable.new(format: 'date') { |v| ::DateTime.iso8601(v) },
15
+ DateTime => Castable.new(format: 'date-time') { |v| ::DateTime.iso8601(v) },
16
+ FalseClass => Primitive::Boolean.new,
17
+ Float => Primitive::Number.new,
18
+ Integer => Primitive::Integer.new,
19
+ NilClass => Primitive::Null.new,
20
+ Regexp => Castable.new(format: 'regex') { |v| Regexp.new(v) },
21
+ String => Primitive::String.new,
22
+ Time => Castable.new(format: 'time') { |v| ::Time.iso8601(v) },
23
+ TrueClass => Primitive::Boolean.new,
24
+ URI => Castable.new(format: 'uri') { |v| URI.parse(v) },
25
+ }.freeze
26
+
12
27
  # @param [Hash] options
13
28
  # @return [Hash]
14
29
  def as_schema(**options)
@@ -53,16 +68,8 @@ module JsonModel
53
68
  # @param [Object, Class] type
54
69
  # @return [TypeSpec]
55
70
  def resolve_type_from_class(type)
56
- if type == String
57
- Primitive::String.new
58
- elsif type == Integer
59
- Primitive::Integer.new
60
- elsif type == Float
61
- Primitive::Number.new
62
- elsif [TrueClass, FalseClass].include?(type)
63
- Primitive::Boolean.new
64
- elsif type == NilClass
65
- Primitive::Null.new
71
+ if TYPE_MAP.key?(type)
72
+ TYPE_MAP[type]
66
73
  elsif type < Schema
67
74
  TypeSpec::Object.new(type)
68
75
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JsonModel
4
- VERSION = '0.1.15'
4
+ VERSION = '0.1.17'
5
5
  end
@@ -98,7 +98,6 @@ RSpec.describe('File system schema') do
98
98
  type: 'boolean',
99
99
  },
100
100
  },
101
- additionalProperties: false,
102
101
  '$defs': {
103
102
  DiskDevice: {
104
103
  properties: {
@@ -111,7 +110,6 @@ RSpec.describe('File system schema') do
111
110
  },
112
111
  },
113
112
  required: %i(device type),
114
- additionalProperties: false,
115
113
  type: 'object',
116
114
  },
117
115
  DiskUuid: {
@@ -123,7 +121,6 @@ RSpec.describe('File system schema') do
123
121
  },
124
122
  },
125
123
  required: %i(label type),
126
- additionalProperties: false,
127
124
  type: 'object',
128
125
  },
129
126
  Nfs: {
@@ -139,7 +136,6 @@ RSpec.describe('File system schema') do
139
136
  },
140
137
  },
141
138
  required: %i(remotePath server type),
142
- additionalProperties: false,
143
139
  type: 'object',
144
140
  },
145
141
  Tmpfs: {
@@ -148,7 +144,6 @@ RSpec.describe('File system schema') do
148
144
  sizeInMB: { type: 'integer', minimum: 16, maximum: 512 },
149
145
  },
150
146
  required: %i(sizeInMB type),
151
- additionalProperties: false,
152
147
  type: 'object',
153
148
  },
154
149
  },
@@ -33,6 +33,7 @@ RSpec.describe('User schema') do
33
33
  property(:active, type: T::Boolean, default: true, optional: true)
34
34
  property(:addresses, type: T::Array[Address], ref_mode: JsonModel::RefMode::LOCAL)
35
35
  property(:tags, type: T::Array[String], optional: true)
36
+ property(:birthday, type: Date, optional: true)
36
37
  end
37
38
 
38
39
  stub_const('User', user_class)
@@ -54,8 +55,8 @@ RSpec.describe('User schema') do
54
55
  items: { '$ref': '#/$defs/Address' },
55
56
  },
56
57
  tags: { type: 'array', items: { type: 'string' } },
58
+ birthday: { type: 'string', format: 'date' },
57
59
  },
58
- additionalProperties: false,
59
60
  required: %i(addresses email name),
60
61
  '$defs': {
61
62
  Address: {
@@ -68,7 +69,6 @@ RSpec.describe('User schema') do
68
69
  street: { type: 'string' },
69
70
  },
70
71
  required: %i(city country street),
71
- additionalProperties: false,
72
72
  },
73
73
  },
74
74
  },
@@ -77,7 +77,12 @@ RSpec.describe('User schema') do
77
77
  end
78
78
 
79
79
  it('can instantiate a model') do
80
- user = User.new(name: 'Foo', email: 'foo@example.com', addresses: [{ street: '123 Main St', city: 'Anytown' }])
80
+ user = User.new(
81
+ name: 'Foo',
82
+ email: 'foo@example.com',
83
+ addresses: [{ street: '123 Main St', city: 'Anytown' }],
84
+ birthday: '2000-01-01',
85
+ )
81
86
 
82
87
  expect(user.name).to(eq('Foo'))
83
88
  expect(user.email).to(eq('foo@example.com'))
@@ -88,5 +93,6 @@ RSpec.describe('User schema') do
88
93
  expect(user.addresses.first.postal_code).to(be_nil)
89
94
  expect(user.addresses.first.state).to(be_nil)
90
95
  expect(user.addresses.first.city).to(eq('Anytown'))
96
+ expect(user.birthday).to(eq(Date.new(2000, 1, 1)))
91
97
  end
92
98
  end
@@ -103,9 +103,9 @@ RSpec.describe(JsonModel::SchemaMeta) do
103
103
  end
104
104
  end
105
105
 
106
- it('is false by default') do
106
+ it('is missing by default') do
107
107
  expect(klass.meta_attributes)
108
- .to(eq({ additionalProperties: false }))
108
+ .to(eq({}))
109
109
  end
110
110
 
111
111
  it('can be changed') do
@@ -115,4 +115,24 @@ RSpec.describe(JsonModel::SchemaMeta) do
115
115
  .to(eq({ additionalProperties: true }))
116
116
  end
117
117
  end
118
+
119
+ describe('.additional_properties') do
120
+ let(:klass) do
121
+ Class.new do
122
+ include(JsonModel::SchemaMeta)
123
+ end
124
+ end
125
+
126
+ it('is missing by default') do
127
+ expect(klass.meta_attributes)
128
+ .to(eq({}))
129
+ end
130
+
131
+ it('can be changed') do
132
+ klass.unevaluated_properties(true)
133
+
134
+ expect(klass.meta_attributes)
135
+ .to(eq({ unevaluatedProperties: true }))
136
+ end
137
+ end
118
138
  end
data/spec/schema_spec.rb CHANGED
@@ -100,7 +100,7 @@ RSpec.describe(JsonModel::Schema) do
100
100
 
101
101
  it('returns an empty schema') do
102
102
  expect(klass.as_schema)
103
- .to(eq({ type: 'object', additionalProperties: false }))
103
+ .to(eq({ type: 'object' }))
104
104
  end
105
105
 
106
106
  it('includes the schema id') do
@@ -112,7 +112,6 @@ RSpec.describe(JsonModel::Schema) do
112
112
  {
113
113
  '$id': 'https://example.com/schemas/example.json',
114
114
  type: 'object',
115
- additionalProperties: false,
116
115
  },
117
116
  ),
118
117
  )
@@ -145,7 +144,6 @@ RSpec.describe(JsonModel::Schema) do
145
144
  foo: { type: 'string' },
146
145
  },
147
146
  required: %i(bam baz foo),
148
- additionalProperties: false,
149
147
  },
150
148
  ),
151
149
  )
@@ -206,16 +204,13 @@ RSpec.describe(JsonModel::Schema) do
206
204
  type: 'object',
207
205
  properties: { foo: { type: 'string' } },
208
206
  required: %i(foo),
209
- additionalProperties: false,
210
207
  },
211
208
  },
212
- additionalProperties: false,
213
209
  '$defs': {
214
210
  Bar: {
215
211
  type: 'object',
216
212
  properties: { bar: { type: 'string' } },
217
213
  required: %i(bar),
218
- additionalProperties: false,
219
214
  },
220
215
  },
221
216
  required: %i(bam bar foo),
@@ -250,7 +245,6 @@ RSpec.describe(JsonModel::Schema) do
250
245
  '$id': 'https://example.com/schemas/child.json',
251
246
  '$ref': 'https://example.com/schemas/example.json',
252
247
  type: 'object',
253
- additionalProperties: false,
254
248
  properties: { baz: { type: 'string' } },
255
249
  required: %i(baz),
256
250
  },
@@ -265,7 +259,6 @@ RSpec.describe(JsonModel::Schema) do
265
259
  '$ref': 'https://example.com/schemas/example.json',
266
260
  title: 'SecondChild',
267
261
  type: 'object',
268
- additionalProperties: false,
269
262
  properties: { bar: { type: 'string' } },
270
263
  required: %i(bar),
271
264
  },
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('spec_helper')
4
+
5
+ RSpec.describe(JsonModel::TypeSpec::Castable) do
6
+ describe('#as_schema') do
7
+ it('returns a string schema') do
8
+ expect(described_class.new(format: 'date') { |v| v }.as_schema)
9
+ .to(eq({ type: 'string', format: 'date' }))
10
+ end
11
+ end
12
+
13
+ describe('#cast') do
14
+ it('casts a string') do
15
+ expect(described_class.new(format: 'date') { |v| Date.parse(v) }.cast('2020-01-01'))
16
+ .to(eq(Date.new(2020, 1, 1)))
17
+ end
18
+ end
19
+ end
@@ -27,7 +27,6 @@ RSpec.describe(JsonModel::TypeSpec::Composition) do
27
27
  type: 'object',
28
28
  properties: { foo: { type: 'string' } },
29
29
  required: %i(foo),
30
- additionalProperties: false,
31
30
  },
32
31
  ],
33
32
  },
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_model_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Gillesberger
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-12-30 00:00:00.000000000 Z
10
+ date: 2026-01-05 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake
@@ -117,6 +117,7 @@ files:
117
117
  - lib/json_model/schema_meta.rb
118
118
  - lib/json_model/type_spec.rb
119
119
  - lib/json_model/type_spec/array.rb
120
+ - lib/json_model/type_spec/castable.rb
120
121
  - lib/json_model/type_spec/composition.rb
121
122
  - lib/json_model/type_spec/composition/all_of.rb
122
123
  - lib/json_model/type_spec/composition/any_of.rb
@@ -145,7 +146,6 @@ files:
145
146
  - lib/json_model/types/string.rb
146
147
  - lib/json_model/version.rb
147
148
  - spec/config_spec.rb
148
- - spec/examples/component_spec.rb
149
149
  - spec/examples/file_system_spec.rb
150
150
  - spec/examples/user_spec.rb
151
151
  - spec/json_model_spec.rb
@@ -155,6 +155,7 @@ files:
155
155
  - spec/schema_spec.rb
156
156
  - spec/spec_helper.rb
157
157
  - spec/type_spec/array_spec.rb
158
+ - spec/type_spec/castable_spec.rb
158
159
  - spec/type_spec/composition/all_of_spec.rb
159
160
  - spec/type_spec/composition/any_of_spec.rb
160
161
  - spec/type_spec/composition/one_of_spec.rb
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require('spec_helper')
4
-
5
- RSpec.describe('User schema') do
6
- it('todo') do
7
- stub_const(
8
- 'BaseComponent',
9
- Class.new do
10
- include(JsonModel::Schema)
11
-
12
- property(:type, type: String)
13
- property(:styles, type: T::Array[String], optional: true)
14
- end,
15
- )
16
- stub_const(
17
- 'TextComponent',
18
- Class.new(BaseComponent) do
19
- include(JsonModel::Schema)
20
-
21
- property(:type, type: T::Enum['text'])
22
- property(:value, type: String)
23
- end,
24
- )
25
- stub_const(
26
- 'NumberComponent',
27
- Class.new(BaseComponent) do
28
- include(JsonModel::Schema)
29
-
30
- property(:type, type: T::Const['number'])
31
- property(:value, type: Integer)
32
- end,
33
- )
34
-
35
- all_component_types = ObjectSpace
36
- .each_object(Class)
37
- .select { |klass| klass.ancestors.include?(BaseComponent) && klass != BaseComponent }
38
- stub_const(
39
- 'GridComponent',
40
- Class.new(BaseComponent) do
41
- include(JsonModel::Schema)
42
-
43
- property(:type, type: T::Const['grid'])
44
- property(:components, type: T::Array[T::OneOf[*all_component_types]], optional: true, ref_mode: JsonModel::RefMode::EXTERNAL)
45
- end,
46
- )
47
-
48
-
49
- instance = GridComponent.new(
50
- type: 'grid',
51
- components: [{ type: 'text', value: 'foo' }, { type: 'number', value: 1 }],
52
- )
53
-
54
- expect(instance.components.first).to(be_instance_of(TextComponent))
55
- expect(instance.components.last).to(be_instance_of(NumberComponent))
56
- end
57
- end