json_model_rb 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +686 -0
- data/lib/json_model/type_spec.rb +1 -1
- data/lib/json_model/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae375731299ec26a890c6325c485b794599871c645e9345bb92492908f9bf3ae
|
|
4
|
+
data.tar.gz: bfad9ced0dd6ecc9720b85bdd029b7e3078b84581aad4c2273866137459e1442
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78c339093d60f4bb6d9d13f48ed21fc6d3f303f7821ac3b66c0ff90593c70fe962582db2fb5481b7399c66c607cd7af7072d9acbcc92dd706f9b4ec768b4b064
|
|
7
|
+
data.tar.gz: 686ff812df589d267d6318b183c47c815184eb17cbd32d0d79b278216af4fdee70d8d04f39734d9545cf0fa89d8e5cf04b985f0874814d2dc9a3d18cdb2cf5cc
|
data/README.md
CHANGED
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
# JSON Model
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/json_model)
|
|
4
|
+
[](https://github.com/gillesbergerp/json_model_rb/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
A Ruby DSL for building JSON Schema definitions with a clean, declarative syntax. Define your schemas in Ruby and generate complete, standards-compliant JSON Schema documents.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add this line to your application's Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'json_model'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
And then execute:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install it yourself as:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem install json_model
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require 'json_model'
|
|
33
|
+
|
|
34
|
+
class User
|
|
35
|
+
include JsonModel::Schema
|
|
36
|
+
|
|
37
|
+
schema_id "https://example.com/schemas/user.json"
|
|
38
|
+
title "User"
|
|
39
|
+
description "A registered user in the system"
|
|
40
|
+
|
|
41
|
+
property :name, type: String
|
|
42
|
+
property :email, type: String, format: :email
|
|
43
|
+
property :age, type: Integer, minimum: 0, maximum: 120, optional: true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Generate the JSON Schema
|
|
47
|
+
puts JSON.pretty_generate(User.as_schema)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Output:**
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"$id": "https://example.com/schemas/user.json",
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"title": "User",
|
|
56
|
+
"description": "A registered user in the system",
|
|
57
|
+
"properties": {
|
|
58
|
+
"age": {
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"minimum": 0,
|
|
61
|
+
"maximum": 120
|
|
62
|
+
},
|
|
63
|
+
"email": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"format": "email"
|
|
66
|
+
},
|
|
67
|
+
"name": {
|
|
68
|
+
"type": "string"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"required": [
|
|
72
|
+
{
|
|
73
|
+
"json_class": "Symbol",
|
|
74
|
+
"s": "email"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"json_class": "Symbol",
|
|
78
|
+
"s": "name"
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"type": "object"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Schema Metadata
|
|
86
|
+
|
|
87
|
+
You can set top-level schema metadata properties directly in your schema class:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
class Product
|
|
91
|
+
include JsonModel::Schema
|
|
92
|
+
|
|
93
|
+
# Schema metadata
|
|
94
|
+
schema_id "https://api.example.com/schemas/product.json"
|
|
95
|
+
schema_version :draft_2020_12
|
|
96
|
+
title "Product"
|
|
97
|
+
description "A product available in the catalog"
|
|
98
|
+
|
|
99
|
+
# Properties
|
|
100
|
+
property :id, type: String
|
|
101
|
+
property :name, type: String
|
|
102
|
+
property :price, type: Float, minimum: 0
|
|
103
|
+
property :available, type: T::Boolean, default: true, optional: true
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Available Metadata Keywords
|
|
108
|
+
|
|
109
|
+
- **`schema_id`** - Sets the `$id` (unique URI identifier for the schema)
|
|
110
|
+
- **`schema_version`** - Sets the `$schema` (JSON Schema version)
|
|
111
|
+
- **`title`** - Human-readable title for the schema
|
|
112
|
+
- **`description`** - Detailed explanation of the schema's purpose
|
|
113
|
+
- **`additional_properties`** - Whether additional properties are allowed (default: `false`)
|
|
114
|
+
|
|
115
|
+
## Data Types
|
|
116
|
+
|
|
117
|
+
### String Type
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
class StringExample
|
|
121
|
+
include JsonModel::Schema
|
|
122
|
+
|
|
123
|
+
# Basic string
|
|
124
|
+
property :simple_string, type: String
|
|
125
|
+
|
|
126
|
+
# String with length constraints
|
|
127
|
+
property :username, type: String,
|
|
128
|
+
min_length: 3,
|
|
129
|
+
max_length: 20
|
|
130
|
+
|
|
131
|
+
# String with pattern (regex)
|
|
132
|
+
property :product_code, type: String, pattern: /\A[A-Z]{3}-\d{4}\z/
|
|
133
|
+
|
|
134
|
+
# String with format
|
|
135
|
+
property :email, type: String, format: :email
|
|
136
|
+
property :uri, type: String, format: :uri
|
|
137
|
+
property :hostname, type: String, format: :hostname
|
|
138
|
+
property :ipv4, type: String, format: :ipv4
|
|
139
|
+
property :ipv6, type: String, format: :ipv6
|
|
140
|
+
property :uuid, type: String, format: :uuid
|
|
141
|
+
property :date, type: String, format: :date
|
|
142
|
+
property :time, type: String, format: :time
|
|
143
|
+
property :datetime, type: String, format: :date_time
|
|
144
|
+
property :duration, type: String, format: :duration
|
|
145
|
+
|
|
146
|
+
# String with enum
|
|
147
|
+
property :status, type: String, enum: ["draft", "published", "archived"]
|
|
148
|
+
|
|
149
|
+
# String with const
|
|
150
|
+
property :api_version, type: String, const: "v1"
|
|
151
|
+
|
|
152
|
+
# Optional string
|
|
153
|
+
property :nickname, type: String, optional: true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Generate the JSON Schema
|
|
157
|
+
puts JSON.pretty_generate(StringExample.as_schema)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Output:**
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"additionalProperties": false,
|
|
164
|
+
"properties": {
|
|
165
|
+
"date": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"format": "date"
|
|
168
|
+
},
|
|
169
|
+
"datetime": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"format": "date-time"
|
|
172
|
+
},
|
|
173
|
+
"duration": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"format": "duration"
|
|
176
|
+
},
|
|
177
|
+
"email": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"format": "email"
|
|
180
|
+
},
|
|
181
|
+
"hostname": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"format": "hostname"
|
|
184
|
+
},
|
|
185
|
+
"ipv4": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"format": "ipv4"
|
|
188
|
+
},
|
|
189
|
+
"ipv6": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"format": "ipv6"
|
|
192
|
+
},
|
|
193
|
+
"product_code": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"pattern": "\\A[A-Z]{3}-\\d{4}\\z"
|
|
196
|
+
},
|
|
197
|
+
"simple_string": {
|
|
198
|
+
"type": "string"
|
|
199
|
+
},
|
|
200
|
+
"time": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"format": "time"
|
|
203
|
+
},
|
|
204
|
+
"uri": {
|
|
205
|
+
"type": "string",
|
|
206
|
+
"format": "uri"
|
|
207
|
+
},
|
|
208
|
+
"username": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"minLength": 3,
|
|
211
|
+
"maxLength": 20
|
|
212
|
+
},
|
|
213
|
+
"uuid": {
|
|
214
|
+
"type": "string",
|
|
215
|
+
"format": "uuid"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"required": [
|
|
219
|
+
"date",
|
|
220
|
+
"datetime",
|
|
221
|
+
"duration",
|
|
222
|
+
"email",
|
|
223
|
+
"hostname",
|
|
224
|
+
"ipv4",
|
|
225
|
+
"ipv6",
|
|
226
|
+
"product_code",
|
|
227
|
+
"simple_string",
|
|
228
|
+
"time",
|
|
229
|
+
"uri",
|
|
230
|
+
"username",
|
|
231
|
+
"uuid"
|
|
232
|
+
],
|
|
233
|
+
"type": "object"
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Number and Integer Types
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
class NumericExample
|
|
241
|
+
include JsonModel::Schema
|
|
242
|
+
|
|
243
|
+
# Integer
|
|
244
|
+
property :count, type: Integer
|
|
245
|
+
|
|
246
|
+
# Integer with range
|
|
247
|
+
property :port, type: Integer, minimum: 1024, maximum: 65535
|
|
248
|
+
|
|
249
|
+
# Integer with exclusive bounds
|
|
250
|
+
property :positive_int, type: Integer, exclusive_minimum: 0
|
|
251
|
+
|
|
252
|
+
# Number (float/double)
|
|
253
|
+
property :price, type: Float, minimum: 0
|
|
254
|
+
|
|
255
|
+
# Number with multiple_of
|
|
256
|
+
property :quantity, type: Integer, multiple_of: 10
|
|
257
|
+
|
|
258
|
+
# Number with precision
|
|
259
|
+
property :temperature, type: Float, minimum: -273.15, maximum: 1000.0
|
|
260
|
+
|
|
261
|
+
# Optional number
|
|
262
|
+
property :discount, type: Float, optional: true
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Generate the JSON Schema
|
|
266
|
+
puts JSON.pretty_generate(NumericExample.as_schema)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Output:**
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"additionalProperties": false,
|
|
273
|
+
"properties": {
|
|
274
|
+
"count": {
|
|
275
|
+
"type": "integer"
|
|
276
|
+
},
|
|
277
|
+
"discount": {
|
|
278
|
+
"type": "number"
|
|
279
|
+
},
|
|
280
|
+
"port": {
|
|
281
|
+
"type": "integer",
|
|
282
|
+
"minimum": 1024,
|
|
283
|
+
"maximum": 65535
|
|
284
|
+
},
|
|
285
|
+
"positive_int": {
|
|
286
|
+
"type": "integer",
|
|
287
|
+
"exclusiveMinimum": 0
|
|
288
|
+
},
|
|
289
|
+
"price": {
|
|
290
|
+
"type": "number",
|
|
291
|
+
"minimum": 0
|
|
292
|
+
},
|
|
293
|
+
"quantity": {
|
|
294
|
+
"type": "integer",
|
|
295
|
+
"multipleOf": 10
|
|
296
|
+
},
|
|
297
|
+
"temperature": {
|
|
298
|
+
"type": "number",
|
|
299
|
+
"minimum": -273.15,
|
|
300
|
+
"maximum": 1000.0
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
"required": [
|
|
304
|
+
"count",
|
|
305
|
+
"port",
|
|
306
|
+
"positive_int",
|
|
307
|
+
"price",
|
|
308
|
+
"quantity",
|
|
309
|
+
"temperature"
|
|
310
|
+
],
|
|
311
|
+
"type": "object"
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Boolean Type
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
class BooleanExample
|
|
319
|
+
include JsonModel::Schema
|
|
320
|
+
|
|
321
|
+
property :is_active, type: T::Boolean
|
|
322
|
+
property :has_agreed, type: T::Boolean, default: false
|
|
323
|
+
property :enabled, type: T::Boolean, optional: true
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Generate the JSON Schema
|
|
327
|
+
puts JSON.pretty_generate(BooleanExample.as_schema)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Output:**
|
|
331
|
+
```json
|
|
332
|
+
{
|
|
333
|
+
"additionalProperties": false,
|
|
334
|
+
"properties": {
|
|
335
|
+
"enabled": {
|
|
336
|
+
"type": "boolean"
|
|
337
|
+
},
|
|
338
|
+
"has_agreed": {
|
|
339
|
+
"type": "boolean",
|
|
340
|
+
"default": false
|
|
341
|
+
},
|
|
342
|
+
"is_active": {
|
|
343
|
+
"type": "boolean"
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
"required": [
|
|
347
|
+
"has_agreed",
|
|
348
|
+
"is_active"
|
|
349
|
+
],
|
|
350
|
+
"type": "object"
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Array Type
|
|
355
|
+
|
|
356
|
+
```ruby
|
|
357
|
+
class ArrayExample
|
|
358
|
+
include JsonModel::Schema
|
|
359
|
+
|
|
360
|
+
# Simple array
|
|
361
|
+
property :tags, type: T::Array[String]
|
|
362
|
+
|
|
363
|
+
# Array with constraints
|
|
364
|
+
property :numbers, type: T::Array[Integer], min_items: 1, max_items: 10, unique_items: true
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Generate the JSON Schema
|
|
368
|
+
puts JSON.pretty_generate(ArrayExample.as_schema)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Output:**
|
|
372
|
+
```json
|
|
373
|
+
{
|
|
374
|
+
"additionalProperties": false,
|
|
375
|
+
"properties": {
|
|
376
|
+
"numbers": {
|
|
377
|
+
"type": "array",
|
|
378
|
+
"items": {
|
|
379
|
+
"type": "integer"
|
|
380
|
+
},
|
|
381
|
+
"minItems": 1,
|
|
382
|
+
"maxItems": 10,
|
|
383
|
+
"uniqueItems": true
|
|
384
|
+
},
|
|
385
|
+
"tags": {
|
|
386
|
+
"type": "array",
|
|
387
|
+
"items": {
|
|
388
|
+
"type": "string"
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
"required": [
|
|
393
|
+
"numbers",
|
|
394
|
+
"tags"
|
|
395
|
+
],
|
|
396
|
+
"type": "object"
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Schema Composition
|
|
401
|
+
|
|
402
|
+
JSON Model supports powerful schema composition using `T::AllOf`, `T::AnyOf`, and `T::OneOf`:
|
|
403
|
+
|
|
404
|
+
### AllOf - Must Match All Schemas
|
|
405
|
+
|
|
406
|
+
Use `T::AllOf` when a value must validate against all provided schemas (intersection/combining schemas):
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
class PersonBase
|
|
410
|
+
include JsonModel::Schema
|
|
411
|
+
|
|
412
|
+
property :name, type: String
|
|
413
|
+
property :age, type: Integer, minimum: 0, optional: true
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
class EmployeeDetails
|
|
417
|
+
include JsonModel::Schema
|
|
418
|
+
|
|
419
|
+
property :employee_id, type: String, pattern: /\AE-\d{4}\z/
|
|
420
|
+
property :department, type: String
|
|
421
|
+
property :salary, type: Float, minimum: 0, optional: true
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
class Employee
|
|
425
|
+
include JsonModel::Schema
|
|
426
|
+
|
|
427
|
+
title "Employee"
|
|
428
|
+
description "Combines person and employee-specific properties"
|
|
429
|
+
|
|
430
|
+
property :employee, type: T::AllOf[PersonBase, EmployeeDetails]
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Generate the JSON Schema
|
|
434
|
+
puts JSON.pretty_generate(Employee.as_schema)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Output:**
|
|
438
|
+
```json
|
|
439
|
+
{
|
|
440
|
+
"additionalProperties": false,
|
|
441
|
+
"title": "Employee",
|
|
442
|
+
"description": "Combines person and employee-specific properties",
|
|
443
|
+
"properties": {
|
|
444
|
+
"employee": {
|
|
445
|
+
"allOf": [
|
|
446
|
+
{
|
|
447
|
+
"additionalProperties": false,
|
|
448
|
+
"properties": {
|
|
449
|
+
"age": {
|
|
450
|
+
"type": "integer",
|
|
451
|
+
"minimum": 0
|
|
452
|
+
},
|
|
453
|
+
"name": {
|
|
454
|
+
"type": "string"
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
"required": [
|
|
458
|
+
"name"
|
|
459
|
+
],
|
|
460
|
+
"type": "object"
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"additionalProperties": false,
|
|
464
|
+
"properties": {
|
|
465
|
+
"department": {
|
|
466
|
+
"type": "string"
|
|
467
|
+
},
|
|
468
|
+
"employee_id": {
|
|
469
|
+
"type": "string",
|
|
470
|
+
"pattern": "\\AE-\\d{4}\\z"
|
|
471
|
+
},
|
|
472
|
+
"salary": {
|
|
473
|
+
"type": "number",
|
|
474
|
+
"minimum": 0
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
"required": [
|
|
478
|
+
"department",
|
|
479
|
+
"employee_id"
|
|
480
|
+
],
|
|
481
|
+
"type": "object"
|
|
482
|
+
}
|
|
483
|
+
]
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
"required": [
|
|
487
|
+
"employee"
|
|
488
|
+
],
|
|
489
|
+
"type": "object"
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### AnyOf - Must Match At Least One Schema
|
|
494
|
+
|
|
495
|
+
Use `T::AnyOf` when a value must validate against one or more schemas (union/alternatives):
|
|
496
|
+
|
|
497
|
+
```ruby
|
|
498
|
+
class EmailContact
|
|
499
|
+
include JsonModel::Schema
|
|
500
|
+
|
|
501
|
+
property :email, type: String, format: :email
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
class PhoneContact
|
|
505
|
+
include JsonModel::Schema
|
|
506
|
+
|
|
507
|
+
property :phone, type: String, pattern: /\A\+?[1-9]\\d{1,14}\z/
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
class AddressContact
|
|
511
|
+
include JsonModel::Schema
|
|
512
|
+
|
|
513
|
+
property :street, type: String
|
|
514
|
+
property :city, type: String
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
class Contact
|
|
518
|
+
include JsonModel::Schema
|
|
519
|
+
|
|
520
|
+
title "Contact Method"
|
|
521
|
+
description "Must provide at least one contact method"
|
|
522
|
+
|
|
523
|
+
property :contact, type: T::AnyOf[EmailContact, PhoneContact, AddressContact]
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Generate the JSON Schema
|
|
527
|
+
puts JSON.pretty_generate(Contact.as_schema)
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Output:**
|
|
531
|
+
```json
|
|
532
|
+
{
|
|
533
|
+
"additionalProperties": false,
|
|
534
|
+
"title": "Contact Method",
|
|
535
|
+
"description": "Must provide at least one contact method",
|
|
536
|
+
"properties": {
|
|
537
|
+
"contact": {
|
|
538
|
+
"anyOf": [
|
|
539
|
+
{
|
|
540
|
+
"additionalProperties": false,
|
|
541
|
+
"properties": {
|
|
542
|
+
"email": {
|
|
543
|
+
"type": "string",
|
|
544
|
+
"format": "email"
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
"required": [
|
|
548
|
+
"email"
|
|
549
|
+
],
|
|
550
|
+
"type": "object"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"additionalProperties": false,
|
|
554
|
+
"properties": {
|
|
555
|
+
"phone": {
|
|
556
|
+
"type": "string",
|
|
557
|
+
"pattern": "\\A\\+?[1-9]\\\\d{1,14}\\z"
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
"required": [
|
|
561
|
+
"phone"
|
|
562
|
+
],
|
|
563
|
+
"type": "object"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
"additionalProperties": false,
|
|
567
|
+
"properties": {
|
|
568
|
+
"city": {
|
|
569
|
+
"type": "string"
|
|
570
|
+
},
|
|
571
|
+
"street": {
|
|
572
|
+
"type": "string"
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
"required": [
|
|
576
|
+
"city",
|
|
577
|
+
"street"
|
|
578
|
+
],
|
|
579
|
+
"type": "object"
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
"required": [
|
|
585
|
+
"contact"
|
|
586
|
+
],
|
|
587
|
+
"type": "object"
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### OneOf - Must Match Exactly One Schema
|
|
592
|
+
|
|
593
|
+
Use `T::OneOf` when a value must validate against exactly one schema (exclusive alternatives):
|
|
594
|
+
|
|
595
|
+
```ruby
|
|
596
|
+
class CreditCardPayment
|
|
597
|
+
include JsonModel::Schema
|
|
598
|
+
|
|
599
|
+
property :payment_type, type: String, const: "credit_card"
|
|
600
|
+
property :card_number, type: String, pattern: /\A\d{16}\z/
|
|
601
|
+
property :cvv, type: String, pattern: /\A\d{3,4}\z/
|
|
602
|
+
property :expiry, type: String, pattern: /\A\d{2}\/\d{2}\z/
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
class PayPalPayment
|
|
606
|
+
include JsonModel::Schema
|
|
607
|
+
|
|
608
|
+
property :payment_type, type: String, const: "paypal"
|
|
609
|
+
property :paypal_email, type: String, format: :email
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
class BankTransferPayment
|
|
613
|
+
include JsonModel::Schema
|
|
614
|
+
|
|
615
|
+
property :payment_type, type: String, const: "bank_transfer"
|
|
616
|
+
property :iban, type: String, pattern: "^[A-Z]{2}\\d{2}[A-Z0-9]+$"
|
|
617
|
+
property :swift, type: String, optional: true
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
class PaymentMethod
|
|
621
|
+
include JsonModel::Schema
|
|
622
|
+
|
|
623
|
+
title "Payment Method"
|
|
624
|
+
description "Must specify exactly one payment method"
|
|
625
|
+
|
|
626
|
+
property :payment, type: T::OneOf[CreditCardPayment, PayPalPayment, BankTransferPayment]
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Generate the JSON Schema
|
|
630
|
+
puts JSON.pretty_generate(PaymentMethod.as_schema)
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**Output:**
|
|
634
|
+
```json
|
|
635
|
+
{
|
|
636
|
+
"additionalProperties": false,
|
|
637
|
+
"title": "Payment Method",
|
|
638
|
+
"description": "Must specify exactly one payment method",
|
|
639
|
+
"properties": {
|
|
640
|
+
"payment": {
|
|
641
|
+
"oneOf": [
|
|
642
|
+
{
|
|
643
|
+
"additionalProperties": false,
|
|
644
|
+
"type": "object"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
"additionalProperties": false,
|
|
648
|
+
"type": "object"
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
"additionalProperties": false,
|
|
652
|
+
"type": "object"
|
|
653
|
+
}
|
|
654
|
+
]
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
"required": [
|
|
658
|
+
"payment"
|
|
659
|
+
],
|
|
660
|
+
"type": "object"
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
## Use Cases
|
|
665
|
+
|
|
666
|
+
- **API Documentation**: Generate JSON schemas for API request/response validation
|
|
667
|
+
- **Configuration Files**: Define and validate application configuration schemas
|
|
668
|
+
- **Data Validation**: Validate incoming data against defined schemas
|
|
669
|
+
- **Code Generation**: Use schemas to generate code in other languages
|
|
670
|
+
- **OpenAPI/Swagger**: Generate OpenAPI schema definitions for your APIs
|
|
671
|
+
- **Form Generation**: Generate forms from schema definitions
|
|
672
|
+
|
|
673
|
+
## Resources
|
|
674
|
+
|
|
675
|
+
- [JSON Schema Specification](https://json-schema.org/)
|
|
676
|
+
- [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/)
|
|
677
|
+
- [JSON Schema Validator](https://www.jsonschemavalidator.net/)
|
|
678
|
+
- [Draft 7 Reference](https://json-schema.org/draft-07/json-schema-release-notes.html)
|
|
679
|
+
|
|
680
|
+
## License
|
|
681
|
+
|
|
682
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
683
|
+
|
|
684
|
+
## Credits
|
|
685
|
+
|
|
686
|
+
Developed and maintained by [gillesbergerp](https://github.com/gillesbergerp).
|
data/lib/json_model/type_spec.rb
CHANGED
|
@@ -40,7 +40,7 @@ module JsonModel
|
|
|
40
40
|
type
|
|
41
41
|
when Class
|
|
42
42
|
resolve_type_from_class(type, **options)
|
|
43
|
-
when T::AllOf, T::AnyOf, T::Boolean, T::OneOf, T::Array, T::Enum
|
|
43
|
+
when T::AllOf, T::AnyOf, T::Boolean, T::OneOf, T::Array, T::Enum, T::Const
|
|
44
44
|
type.to_type_spec(**options)
|
|
45
45
|
else
|
|
46
46
|
raise(ArgumentError, "Unsupported type: #{type}")
|
data/lib/json_model/version.rb
CHANGED