easy_talk 2.0.0 → 3.1.0
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/CHANGELOG.md +56 -0
- data/README.md +663 -165
- data/easy_talk.gemspec +5 -4
- data/lib/easy_talk/builders/integer_builder.rb +1 -0
- data/lib/easy_talk/builders/object_builder.rb +95 -1
- data/lib/easy_talk/builders/string_builder.rb +9 -0
- data/lib/easy_talk/builders/typed_array_builder.rb +5 -2
- data/lib/easy_talk/configuration.rb +22 -9
- data/lib/easy_talk/keywords.rb +2 -0
- data/lib/easy_talk/model.rb +66 -49
- data/lib/easy_talk/property.rb +69 -3
- data/lib/easy_talk/validation_builder.rb +37 -49
- data/lib/easy_talk/version.rb +1 -1
- metadata +36 -10
- data/lib/easy_talk/active_record_schema_builder.rb +0 -299
data/README.md
CHANGED
|
@@ -11,11 +11,14 @@ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema.
|
|
|
11
11
|
### Key Features
|
|
12
12
|
* **Intuitive Schema Definition**: Use Ruby classes and methods to define JSON Schema documents easily.
|
|
13
13
|
* **Automatic ActiveModel Validations**: Schema constraints automatically generate corresponding ActiveModel validations (configurable).
|
|
14
|
-
* **Works for plain Ruby classes and
|
|
14
|
+
* **Works for plain Ruby classes and ActiveModel classes**: Integrate with existing code or build from scratch.
|
|
15
15
|
* **LLM Function Support**: Ideal for integrating with Large Language Models (LLMs) such as OpenAI's GPT series. EasyTalk enables you to effortlessly create JSON Schema documents describing the inputs and outputs of LLM function calls.
|
|
16
16
|
* **Schema Composition**: Define EasyTalk models and reference them in other EasyTalk models to create complex schemas.
|
|
17
17
|
* **Enhanced Model Integration**: Automatic instantiation of nested EasyTalk models from hash attributes.
|
|
18
18
|
* **Flexible Configuration**: Global and per-model configuration options for fine-tuned control.
|
|
19
|
+
* **JSON Schema Version Support**: Configure the `$schema` keyword to declare which JSON Schema draft version your schemas conform to (Draft-04 through Draft 2020-12).
|
|
20
|
+
* **Schema Identification**: Configure the `$id` keyword to provide a unique identifier URI for your schemas.
|
|
21
|
+
* **Schema References**: Use `$ref` and `$defs` for reusable schema definitions, reducing duplication when models are used in multiple places.
|
|
19
22
|
|
|
20
23
|
### Use Cases
|
|
21
24
|
- API request/response validation
|
|
@@ -518,119 +521,6 @@ user.address.class # => Address (automatically instantiated)
|
|
|
518
521
|
user.address.street # => "123 Main St"
|
|
519
522
|
```
|
|
520
523
|
|
|
521
|
-
## ActiveRecord Integration
|
|
522
|
-
|
|
523
|
-
### Automatic Schema Generation
|
|
524
|
-
For ActiveRecord models, EasyTalk automatically generates a schema based on the database columns:
|
|
525
|
-
|
|
526
|
-
```ruby
|
|
527
|
-
class Product < ActiveRecord::Base
|
|
528
|
-
include EasyTalk::Model
|
|
529
|
-
end
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
This will create a schema with properties for each column in the `products` table.
|
|
533
|
-
|
|
534
|
-
### Enhancing Generated Schemas
|
|
535
|
-
You can enhance the auto-generated schema with the `enhance_schema` method:
|
|
536
|
-
|
|
537
|
-
```ruby
|
|
538
|
-
class Product < ActiveRecord::Base
|
|
539
|
-
include EasyTalk::Model
|
|
540
|
-
|
|
541
|
-
enhance_schema({
|
|
542
|
-
title: "Retail Product",
|
|
543
|
-
description: "A product available for purchase",
|
|
544
|
-
properties: {
|
|
545
|
-
name: {
|
|
546
|
-
description: "Product display name",
|
|
547
|
-
title: "Product Name"
|
|
548
|
-
},
|
|
549
|
-
price: {
|
|
550
|
-
description: "Retail price in USD"
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
})
|
|
554
|
-
end
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
### Column Exclusion Options
|
|
558
|
-
EasyTalk provides several ways to exclude columns from your JSON schema:
|
|
559
|
-
|
|
560
|
-
#### 1. Global Configuration
|
|
561
|
-
|
|
562
|
-
```ruby
|
|
563
|
-
EasyTalk.configure do |config|
|
|
564
|
-
# Exclude specific columns by name from all models
|
|
565
|
-
config.excluded_columns = [:created_at, :updated_at, :deleted_at]
|
|
566
|
-
|
|
567
|
-
# Exclude all foreign key columns (columns ending with '_id')
|
|
568
|
-
config.exclude_foreign_keys = true # Default: false
|
|
569
|
-
|
|
570
|
-
# Exclude all primary key columns ('id')
|
|
571
|
-
config.exclude_primary_key = true # Default: true
|
|
572
|
-
|
|
573
|
-
# Exclude timestamp columns ('created_at', 'updated_at')
|
|
574
|
-
config.exclude_timestamps = true # Default: true
|
|
575
|
-
|
|
576
|
-
# Exclude all association properties
|
|
577
|
-
config.exclude_associations = true # Default: false
|
|
578
|
-
end
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
#### 2. Model-Specific Column Ignoring
|
|
582
|
-
|
|
583
|
-
```ruby
|
|
584
|
-
class Product < ActiveRecord::Base
|
|
585
|
-
include EasyTalk::Model
|
|
586
|
-
|
|
587
|
-
enhance_schema({
|
|
588
|
-
ignore: [:internal_ref_id, :legacy_code] # Model-specific exclusions
|
|
589
|
-
})
|
|
590
|
-
end
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
### Virtual Properties
|
|
594
|
-
You can add properties that don't exist as database columns:
|
|
595
|
-
|
|
596
|
-
```ruby
|
|
597
|
-
class Product < ActiveRecord::Base
|
|
598
|
-
include EasyTalk::Model
|
|
599
|
-
|
|
600
|
-
enhance_schema({
|
|
601
|
-
properties: {
|
|
602
|
-
full_details: {
|
|
603
|
-
virtual: true,
|
|
604
|
-
type: :string,
|
|
605
|
-
description: "Complete product information"
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
})
|
|
609
|
-
end
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
### Associations and Foreign Keys
|
|
613
|
-
By default, EasyTalk includes your model's associations in the schema:
|
|
614
|
-
|
|
615
|
-
```ruby
|
|
616
|
-
class Product < ActiveRecord::Base
|
|
617
|
-
include EasyTalk::Model
|
|
618
|
-
belongs_to :category
|
|
619
|
-
has_many :reviews
|
|
620
|
-
end
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
This will include `category` (as an object) and `reviews` (as an array) in the schema.
|
|
624
|
-
|
|
625
|
-
You can control this behavior with configuration:
|
|
626
|
-
|
|
627
|
-
```ruby
|
|
628
|
-
EasyTalk.configure do |config|
|
|
629
|
-
config.exclude_associations = true # Don't include associations
|
|
630
|
-
config.exclude_foreign_keys = true # Don't include foreign key columns
|
|
631
|
-
end
|
|
632
|
-
```
|
|
633
|
-
|
|
634
524
|
## Advanced Features
|
|
635
525
|
|
|
636
526
|
### LLM Function Generation
|
|
@@ -698,19 +588,14 @@ You can configure EasyTalk globally:
|
|
|
698
588
|
|
|
699
589
|
```ruby
|
|
700
590
|
EasyTalk.configure do |config|
|
|
701
|
-
# ActiveRecord integration options
|
|
702
|
-
config.excluded_columns = [:created_at, :updated_at, :deleted_at]
|
|
703
|
-
config.exclude_foreign_keys = true
|
|
704
|
-
config.exclude_primary_key = true
|
|
705
|
-
config.exclude_timestamps = true
|
|
706
|
-
config.exclude_associations = false
|
|
707
|
-
|
|
708
591
|
# Schema behavior options
|
|
709
|
-
config.default_additional_properties = false
|
|
710
|
-
config.nilable_is_optional = false
|
|
711
|
-
|
|
712
|
-
#
|
|
713
|
-
|
|
592
|
+
config.default_additional_properties = false # Control additional properties on all models
|
|
593
|
+
config.nilable_is_optional = false # Makes T.nilable properties also optional
|
|
594
|
+
config.auto_validations = true # Automatically generate ActiveModel validations
|
|
595
|
+
config.schema_version = :none # JSON Schema version for $schema keyword
|
|
596
|
+
# Options: :none, :draft202012, :draft201909, :draft7, :draft6, :draft4
|
|
597
|
+
config.schema_id = nil # Base URI for $id keyword (nil = no $id)
|
|
598
|
+
config.use_refs = false # Use $ref for nested models instead of inlining
|
|
714
599
|
end
|
|
715
600
|
```
|
|
716
601
|
|
|
@@ -738,45 +623,18 @@ end
|
|
|
738
623
|
```
|
|
739
624
|
|
|
740
625
|
### Per-Model Configuration
|
|
741
|
-
|
|
626
|
+
You can configure additional properties for individual models:
|
|
742
627
|
|
|
743
628
|
```ruby
|
|
744
|
-
class
|
|
745
|
-
include EasyTalk::Model
|
|
746
|
-
|
|
747
|
-
enhance_schema({
|
|
748
|
-
additionalProperties: true,
|
|
749
|
-
ignore: [:internal_ref_id, :legacy_code]
|
|
750
|
-
})
|
|
751
|
-
end
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
### Exclusion Rules
|
|
755
|
-
Columns are excluded based on the following rules (in order of precedence):
|
|
756
|
-
|
|
757
|
-
1. Explicitly listed in `excluded_columns` global setting
|
|
758
|
-
2. Listed in the model's `schema_enhancements[:ignore]` array
|
|
759
|
-
3. Is a primary key when `exclude_primary_key` is true (default)
|
|
760
|
-
4. Is a timestamp column when `exclude_timestamps` is true (default)
|
|
761
|
-
5. Matches a foreign key pattern when `exclude_foreign_keys` is true
|
|
762
|
-
|
|
763
|
-
### Customizing Output
|
|
764
|
-
You can customize the JSON Schema output by enhancing the schema:
|
|
765
|
-
|
|
766
|
-
```ruby
|
|
767
|
-
class User < ActiveRecord::Base
|
|
629
|
+
class User
|
|
768
630
|
include EasyTalk::Model
|
|
769
631
|
|
|
770
|
-
|
|
771
|
-
title
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
description: "User's full name"
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
})
|
|
632
|
+
define_schema do
|
|
633
|
+
title "User"
|
|
634
|
+
additional_properties true # Allow arbitrary additional properties on this model
|
|
635
|
+
property :name, String
|
|
636
|
+
property :email, String, format: "email"
|
|
637
|
+
end
|
|
780
638
|
end
|
|
781
639
|
```
|
|
782
640
|
|
|
@@ -1277,7 +1135,7 @@ EasyTalk.configure do |config|
|
|
|
1277
1135
|
|
|
1278
1136
|
# Existing options (unchanged)
|
|
1279
1137
|
config.nilable_is_optional = false
|
|
1280
|
-
config.
|
|
1138
|
+
config.default_additional_properties = false
|
|
1281
1139
|
# ... other existing config
|
|
1282
1140
|
end
|
|
1283
1141
|
```
|
|
@@ -1287,7 +1145,7 @@ end
|
|
|
1287
1145
|
- **Ruby Version**: Still requires Ruby 3.2+
|
|
1288
1146
|
- **Dependencies**: Core dependencies remain the same
|
|
1289
1147
|
- **JSON Schema Output**: No changes to generated schemas
|
|
1290
|
-
- **
|
|
1148
|
+
- **ActiveModel Integration**: Fully backward compatible
|
|
1291
1149
|
|
|
1292
1150
|
## Development and Contributing
|
|
1293
1151
|
|
|
@@ -1317,17 +1175,657 @@ bundle exec rubocop
|
|
|
1317
1175
|
### Contributing Guidelines
|
|
1318
1176
|
Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/easy_talk.
|
|
1319
1177
|
|
|
1178
|
+
## JSON Schema Version (`$schema` Keyword)
|
|
1179
|
+
|
|
1180
|
+
The `$schema` keyword declares which JSON Schema dialect (draft version) a schema conforms to. EasyTalk supports configuring this at both the global and per-model level.
|
|
1181
|
+
|
|
1182
|
+
### Why Use `$schema`?
|
|
1183
|
+
|
|
1184
|
+
The `$schema` keyword:
|
|
1185
|
+
- Declares the JSON Schema version your schema is written against
|
|
1186
|
+
- Helps validators understand which specification to use
|
|
1187
|
+
- Enables tooling to provide appropriate validation and autocomplete
|
|
1188
|
+
- Documents the schema dialect for consumers of your API
|
|
1189
|
+
|
|
1190
|
+
### Supported Draft Versions
|
|
1191
|
+
|
|
1192
|
+
EasyTalk supports the following JSON Schema draft versions:
|
|
1193
|
+
|
|
1194
|
+
| Symbol | JSON Schema Version | URI |
|
|
1195
|
+
|--------|---------------------|-----|
|
|
1196
|
+
| `:draft202012` | Draft 2020-12 (latest) | `https://json-schema.org/draft/2020-12/schema` |
|
|
1197
|
+
| `:draft201909` | Draft 2019-09 | `https://json-schema.org/draft/2019-09/schema` |
|
|
1198
|
+
| `:draft7` | Draft-07 | `http://json-schema.org/draft-07/schema#` |
|
|
1199
|
+
| `:draft6` | Draft-06 | `http://json-schema.org/draft-06/schema#` |
|
|
1200
|
+
| `:draft4` | Draft-04 | `http://json-schema.org/draft-04/schema#` |
|
|
1201
|
+
| `:none` | No `$schema` output (default) | N/A |
|
|
1202
|
+
|
|
1203
|
+
### Global Configuration
|
|
1204
|
+
|
|
1205
|
+
Configure the schema version globally to apply to all models:
|
|
1206
|
+
|
|
1207
|
+
```ruby
|
|
1208
|
+
EasyTalk.configure do |config|
|
|
1209
|
+
config.schema_version = :draft202012 # Use JSON Schema Draft 2020-12
|
|
1210
|
+
end
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
With this configuration, all models will include `$schema` in their output:
|
|
1214
|
+
|
|
1215
|
+
```ruby
|
|
1216
|
+
class User
|
|
1217
|
+
include EasyTalk::Model
|
|
1218
|
+
|
|
1219
|
+
define_schema do
|
|
1220
|
+
property :name, String
|
|
1221
|
+
end
|
|
1222
|
+
end
|
|
1223
|
+
|
|
1224
|
+
User.json_schema
|
|
1225
|
+
# => {
|
|
1226
|
+
# "$schema" => "https://json-schema.org/draft/2020-12/schema",
|
|
1227
|
+
# "type" => "object",
|
|
1228
|
+
# "properties" => { "name" => { "type" => "string" } },
|
|
1229
|
+
# "required" => ["name"],
|
|
1230
|
+
# "additionalProperties" => false
|
|
1231
|
+
# }
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
### Per-Model Configuration
|
|
1235
|
+
|
|
1236
|
+
Override the global setting for individual models using the `schema_version` keyword in the schema definition:
|
|
1237
|
+
|
|
1238
|
+
```ruby
|
|
1239
|
+
class LegacyModel
|
|
1240
|
+
include EasyTalk::Model
|
|
1241
|
+
|
|
1242
|
+
define_schema do
|
|
1243
|
+
schema_version :draft7 # Use Draft-07 for this specific model
|
|
1244
|
+
property :name, String
|
|
1245
|
+
end
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
LegacyModel.json_schema
|
|
1249
|
+
# => {
|
|
1250
|
+
# "$schema" => "http://json-schema.org/draft-07/schema#",
|
|
1251
|
+
# "type" => "object",
|
|
1252
|
+
# ...
|
|
1253
|
+
# }
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
### Disabling `$schema` for Specific Models
|
|
1257
|
+
|
|
1258
|
+
If you have a global schema version configured but want to exclude `$schema` from a specific model, use `:none`:
|
|
1259
|
+
|
|
1260
|
+
```ruby
|
|
1261
|
+
EasyTalk.configure do |config|
|
|
1262
|
+
config.schema_version = :draft202012 # Global default
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
class InternalModel
|
|
1266
|
+
include EasyTalk::Model
|
|
1267
|
+
|
|
1268
|
+
define_schema do
|
|
1269
|
+
schema_version :none # No $schema for this model
|
|
1270
|
+
property :data, String
|
|
1271
|
+
end
|
|
1272
|
+
end
|
|
1273
|
+
|
|
1274
|
+
InternalModel.json_schema
|
|
1275
|
+
# => {
|
|
1276
|
+
# "type" => "object",
|
|
1277
|
+
# "properties" => { "data" => { "type" => "string" } },
|
|
1278
|
+
# ...
|
|
1279
|
+
# }
|
|
1280
|
+
# Note: No "$schema" key present
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
### Custom Schema URIs
|
|
1284
|
+
|
|
1285
|
+
You can also specify a custom URI if you're using a custom meta-schema or a different schema registry:
|
|
1286
|
+
|
|
1287
|
+
```ruby
|
|
1288
|
+
class CustomModel
|
|
1289
|
+
include EasyTalk::Model
|
|
1290
|
+
|
|
1291
|
+
define_schema do
|
|
1292
|
+
schema_version 'https://my-company.com/schemas/v1/meta-schema.json'
|
|
1293
|
+
property :id, String
|
|
1294
|
+
end
|
|
1295
|
+
end
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
### Nested Models
|
|
1299
|
+
|
|
1300
|
+
The `$schema` keyword only appears at the root level of the schema. When you have nested EasyTalk models, only the top-level model's `json_schema` output will include `$schema`:
|
|
1301
|
+
|
|
1302
|
+
```ruby
|
|
1303
|
+
EasyTalk.configure do |config|
|
|
1304
|
+
config.schema_version = :draft202012
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1307
|
+
class Address
|
|
1308
|
+
include EasyTalk::Model
|
|
1309
|
+
define_schema do
|
|
1310
|
+
property :city, String
|
|
1311
|
+
end
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
class User
|
|
1315
|
+
include EasyTalk::Model
|
|
1316
|
+
define_schema do
|
|
1317
|
+
property :name, String
|
|
1318
|
+
property :address, Address
|
|
1319
|
+
end
|
|
1320
|
+
end
|
|
1321
|
+
|
|
1322
|
+
User.json_schema
|
|
1323
|
+
# => {
|
|
1324
|
+
# "$schema" => "https://json-schema.org/draft/2020-12/schema", # Only at root
|
|
1325
|
+
# "type" => "object",
|
|
1326
|
+
# "properties" => {
|
|
1327
|
+
# "name" => { "type" => "string" },
|
|
1328
|
+
# "address" => {
|
|
1329
|
+
# "type" => "object", # No $schema here
|
|
1330
|
+
# "properties" => { "city" => { "type" => "string" } },
|
|
1331
|
+
# ...
|
|
1332
|
+
# }
|
|
1333
|
+
# },
|
|
1334
|
+
# ...
|
|
1335
|
+
# }
|
|
1336
|
+
```
|
|
1337
|
+
|
|
1338
|
+
### Default Behavior
|
|
1339
|
+
|
|
1340
|
+
By default, `schema_version` is set to `:none`, meaning no `$schema` keyword is included in the generated schemas. This maintains backward compatibility with previous versions of EasyTalk.
|
|
1341
|
+
|
|
1342
|
+
### Best Practices
|
|
1343
|
+
|
|
1344
|
+
1. **Choose a version appropriate for your validators**: If you're using a specific JSON Schema validator, check which drafts it supports.
|
|
1345
|
+
|
|
1346
|
+
2. **Use Draft 2020-12 for new projects**: It's the latest stable version with the most features.
|
|
1347
|
+
|
|
1348
|
+
3. **Be consistent**: Use global configuration for consistency across your application, and only override per-model when necessary.
|
|
1349
|
+
|
|
1350
|
+
4. **Consider your consumers**: If your schemas are consumed by external systems, ensure they support the draft version you're using.
|
|
1351
|
+
|
|
1352
|
+
## Schema Identifier (`$id` Keyword)
|
|
1353
|
+
|
|
1354
|
+
The `$id` keyword provides a unique identifier for your JSON Schema document. EasyTalk supports configuring this at both the global and per-model level.
|
|
1355
|
+
|
|
1356
|
+
### Why Use `$id`?
|
|
1357
|
+
|
|
1358
|
+
The `$id` keyword:
|
|
1359
|
+
- Establishes a unique URI identifier for the schema
|
|
1360
|
+
- Enables referencing schemas from other documents via `$ref`
|
|
1361
|
+
- Provides a base URI for resolving relative references within the schema
|
|
1362
|
+
- Documents the canonical location of the schema
|
|
1363
|
+
|
|
1364
|
+
### Global Configuration
|
|
1365
|
+
|
|
1366
|
+
Configure the schema ID globally to apply to all models:
|
|
1367
|
+
|
|
1368
|
+
```ruby
|
|
1369
|
+
EasyTalk.configure do |config|
|
|
1370
|
+
config.schema_id = 'https://example.com/schemas/base.json'
|
|
1371
|
+
end
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
With this configuration, all models will include `$id` in their output:
|
|
1375
|
+
|
|
1376
|
+
```ruby
|
|
1377
|
+
class User
|
|
1378
|
+
include EasyTalk::Model
|
|
1379
|
+
|
|
1380
|
+
define_schema do
|
|
1381
|
+
property :name, String
|
|
1382
|
+
end
|
|
1383
|
+
end
|
|
1384
|
+
|
|
1385
|
+
User.json_schema
|
|
1386
|
+
# => {
|
|
1387
|
+
# "$id" => "https://example.com/schemas/base.json",
|
|
1388
|
+
# "type" => "object",
|
|
1389
|
+
# "properties" => { "name" => { "type" => "string" } },
|
|
1390
|
+
# "required" => ["name"],
|
|
1391
|
+
# "additionalProperties" => false
|
|
1392
|
+
# }
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
### Per-Model Configuration
|
|
1396
|
+
|
|
1397
|
+
Override the global setting for individual models using the `schema_id` keyword in the schema definition:
|
|
1398
|
+
|
|
1399
|
+
```ruby
|
|
1400
|
+
class User
|
|
1401
|
+
include EasyTalk::Model
|
|
1402
|
+
|
|
1403
|
+
define_schema do
|
|
1404
|
+
schema_id 'https://example.com/schemas/user.schema.json'
|
|
1405
|
+
property :name, String
|
|
1406
|
+
property :email, String
|
|
1407
|
+
end
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
User.json_schema
|
|
1411
|
+
# => {
|
|
1412
|
+
# "$id" => "https://example.com/schemas/user.schema.json",
|
|
1413
|
+
# "type" => "object",
|
|
1414
|
+
# ...
|
|
1415
|
+
# }
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
### Disabling `$id` for Specific Models
|
|
1419
|
+
|
|
1420
|
+
If you have a global schema ID configured but want to exclude `$id` from a specific model, use `:none`:
|
|
1421
|
+
|
|
1422
|
+
```ruby
|
|
1423
|
+
EasyTalk.configure do |config|
|
|
1424
|
+
config.schema_id = 'https://example.com/schemas/default.json'
|
|
1425
|
+
end
|
|
1426
|
+
|
|
1427
|
+
class InternalModel
|
|
1428
|
+
include EasyTalk::Model
|
|
1429
|
+
|
|
1430
|
+
define_schema do
|
|
1431
|
+
schema_id :none # No $id for this model
|
|
1432
|
+
property :data, String
|
|
1433
|
+
end
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
InternalModel.json_schema
|
|
1437
|
+
# => {
|
|
1438
|
+
# "type" => "object",
|
|
1439
|
+
# "properties" => { "data" => { "type" => "string" } },
|
|
1440
|
+
# ...
|
|
1441
|
+
# }
|
|
1442
|
+
# Note: No "$id" key present
|
|
1443
|
+
```
|
|
1444
|
+
|
|
1445
|
+
### Combining `$schema` and `$id`
|
|
1446
|
+
|
|
1447
|
+
When both `$schema` and `$id` are configured, they appear in the standard order (`$schema` first, then `$id`):
|
|
1448
|
+
|
|
1449
|
+
```ruby
|
|
1450
|
+
class Product
|
|
1451
|
+
include EasyTalk::Model
|
|
1452
|
+
|
|
1453
|
+
define_schema do
|
|
1454
|
+
schema_version :draft202012
|
|
1455
|
+
schema_id 'https://example.com/schemas/product.schema.json'
|
|
1456
|
+
property :name, String
|
|
1457
|
+
property :price, Float
|
|
1458
|
+
end
|
|
1459
|
+
end
|
|
1460
|
+
|
|
1461
|
+
Product.json_schema
|
|
1462
|
+
# => {
|
|
1463
|
+
# "$schema" => "https://json-schema.org/draft/2020-12/schema",
|
|
1464
|
+
# "$id" => "https://example.com/schemas/product.schema.json",
|
|
1465
|
+
# "type" => "object",
|
|
1466
|
+
# ...
|
|
1467
|
+
# }
|
|
1468
|
+
```
|
|
1469
|
+
|
|
1470
|
+
### Nested Models
|
|
1471
|
+
|
|
1472
|
+
The `$id` keyword only appears at the root level of the schema. When you have nested EasyTalk models, only the top-level model's `json_schema` output will include `$id`:
|
|
1473
|
+
|
|
1474
|
+
```ruby
|
|
1475
|
+
EasyTalk.configure do |config|
|
|
1476
|
+
config.schema_id = 'https://example.com/schemas/user.json'
|
|
1477
|
+
end
|
|
1478
|
+
|
|
1479
|
+
class Address
|
|
1480
|
+
include EasyTalk::Model
|
|
1481
|
+
define_schema do
|
|
1482
|
+
property :city, String
|
|
1483
|
+
end
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
class User
|
|
1487
|
+
include EasyTalk::Model
|
|
1488
|
+
define_schema do
|
|
1489
|
+
property :name, String
|
|
1490
|
+
property :address, Address
|
|
1491
|
+
end
|
|
1492
|
+
end
|
|
1493
|
+
|
|
1494
|
+
User.json_schema
|
|
1495
|
+
# => {
|
|
1496
|
+
# "$id" => "https://example.com/schemas/user.json", # Only at root
|
|
1497
|
+
# "type" => "object",
|
|
1498
|
+
# "properties" => {
|
|
1499
|
+
# "name" => { "type" => "string" },
|
|
1500
|
+
# "address" => {
|
|
1501
|
+
# "type" => "object", # No $id here
|
|
1502
|
+
# "properties" => { "city" => { "type" => "string" } },
|
|
1503
|
+
# ...
|
|
1504
|
+
# }
|
|
1505
|
+
# },
|
|
1506
|
+
# ...
|
|
1507
|
+
# }
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
### URI Formats
|
|
1511
|
+
|
|
1512
|
+
The `$id` accepts various URI formats:
|
|
1513
|
+
|
|
1514
|
+
```ruby
|
|
1515
|
+
# Absolute URI (recommended for published schemas)
|
|
1516
|
+
schema_id 'https://example.com/schemas/user.schema.json'
|
|
1517
|
+
|
|
1518
|
+
# Relative URI
|
|
1519
|
+
schema_id 'user.schema.json'
|
|
1520
|
+
|
|
1521
|
+
# URN format
|
|
1522
|
+
schema_id 'urn:example:user-schema'
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
### Default Behavior
|
|
1526
|
+
|
|
1527
|
+
By default, `schema_id` is set to `nil`, meaning no `$id` keyword is included in the generated schemas. This maintains backward compatibility with previous versions of EasyTalk.
|
|
1528
|
+
|
|
1529
|
+
### Best Practices
|
|
1530
|
+
|
|
1531
|
+
1. **Use absolute URIs for published schemas**: This ensures global uniqueness and enables external references.
|
|
1532
|
+
|
|
1533
|
+
2. **Follow a consistent naming convention**: For example, `https://yourdomain.com/schemas/{model-name}.schema.json`.
|
|
1534
|
+
|
|
1535
|
+
3. **Keep IDs stable**: Once published, avoid changing schema IDs as external systems may reference them.
|
|
1536
|
+
|
|
1537
|
+
4. **Combine with `$schema`**: When publishing schemas, include both `$schema` (for validation) and `$id` (for identification).
|
|
1538
|
+
|
|
1539
|
+
## Schema References (`$ref` and `$defs`)
|
|
1540
|
+
|
|
1541
|
+
The `$ref` keyword allows you to reference reusable schema definitions, reducing duplication when the same model is used in multiple places. EasyTalk supports automatic `$ref` generation for nested models.
|
|
1542
|
+
|
|
1543
|
+
### Why Use `$ref`?
|
|
1544
|
+
|
|
1545
|
+
The `$ref` keyword:
|
|
1546
|
+
- Reduces schema duplication when the same model appears multiple times
|
|
1547
|
+
- Produces cleaner, more organized schemas
|
|
1548
|
+
- Improves schema readability and maintainability
|
|
1549
|
+
- Aligns with JSON Schema best practices for reusable definitions
|
|
1550
|
+
|
|
1551
|
+
### Default Behavior (Inline Schemas)
|
|
1552
|
+
|
|
1553
|
+
By default, EasyTalk inlines nested model schemas directly:
|
|
1554
|
+
|
|
1555
|
+
```ruby
|
|
1556
|
+
class Address
|
|
1557
|
+
include EasyTalk::Model
|
|
1558
|
+
define_schema do
|
|
1559
|
+
property :street, String
|
|
1560
|
+
property :city, String
|
|
1561
|
+
end
|
|
1562
|
+
end
|
|
1563
|
+
|
|
1564
|
+
class Person
|
|
1565
|
+
include EasyTalk::Model
|
|
1566
|
+
define_schema do
|
|
1567
|
+
property :name, String
|
|
1568
|
+
property :address, Address
|
|
1569
|
+
end
|
|
1570
|
+
end
|
|
1571
|
+
|
|
1572
|
+
Person.json_schema
|
|
1573
|
+
# => {
|
|
1574
|
+
# "type" => "object",
|
|
1575
|
+
# "properties" => {
|
|
1576
|
+
# "name" => { "type" => "string" },
|
|
1577
|
+
# "address" => {
|
|
1578
|
+
# "type" => "object",
|
|
1579
|
+
# "properties" => {
|
|
1580
|
+
# "street" => { "type" => "string" },
|
|
1581
|
+
# "city" => { "type" => "string" }
|
|
1582
|
+
# },
|
|
1583
|
+
# ...
|
|
1584
|
+
# }
|
|
1585
|
+
# },
|
|
1586
|
+
# ...
|
|
1587
|
+
# }
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
### Enabling `$ref` References
|
|
1591
|
+
|
|
1592
|
+
#### Global Configuration
|
|
1593
|
+
|
|
1594
|
+
Enable `$ref` generation for all nested models:
|
|
1595
|
+
|
|
1596
|
+
```ruby
|
|
1597
|
+
EasyTalk.configure do |config|
|
|
1598
|
+
config.use_refs = true
|
|
1599
|
+
end
|
|
1600
|
+
```
|
|
1601
|
+
|
|
1602
|
+
With this configuration, nested models are referenced via `$ref` and their definitions are placed in `$defs`:
|
|
1603
|
+
|
|
1604
|
+
```ruby
|
|
1605
|
+
Person.json_schema
|
|
1606
|
+
# => {
|
|
1607
|
+
# "type" => "object",
|
|
1608
|
+
# "properties" => {
|
|
1609
|
+
# "name" => { "type" => "string" },
|
|
1610
|
+
# "address" => { "$ref" => "#/$defs/Address" }
|
|
1611
|
+
# },
|
|
1612
|
+
# "$defs" => {
|
|
1613
|
+
# "Address" => {
|
|
1614
|
+
# "type" => "object",
|
|
1615
|
+
# "properties" => {
|
|
1616
|
+
# "street" => { "type" => "string" },
|
|
1617
|
+
# "city" => { "type" => "string" }
|
|
1618
|
+
# },
|
|
1619
|
+
# ...
|
|
1620
|
+
# }
|
|
1621
|
+
# },
|
|
1622
|
+
# ...
|
|
1623
|
+
# }
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
#### Per-Property Configuration
|
|
1627
|
+
|
|
1628
|
+
You can also enable `$ref` for specific properties using the `ref: true` constraint:
|
|
1629
|
+
|
|
1630
|
+
```ruby
|
|
1631
|
+
class Person
|
|
1632
|
+
include EasyTalk::Model
|
|
1633
|
+
define_schema do
|
|
1634
|
+
property :name, String
|
|
1635
|
+
property :address, Address, ref: true # Use $ref for this property
|
|
1636
|
+
end
|
|
1637
|
+
end
|
|
1638
|
+
```
|
|
1639
|
+
|
|
1640
|
+
Or disable `$ref` for specific properties when it's enabled globally:
|
|
1641
|
+
|
|
1642
|
+
```ruby
|
|
1643
|
+
EasyTalk.configure do |config|
|
|
1644
|
+
config.use_refs = true
|
|
1645
|
+
end
|
|
1646
|
+
|
|
1647
|
+
class Person
|
|
1648
|
+
include EasyTalk::Model
|
|
1649
|
+
define_schema do
|
|
1650
|
+
property :name, String
|
|
1651
|
+
property :address, Address, ref: false # Inline this property despite global setting
|
|
1652
|
+
end
|
|
1653
|
+
end
|
|
1654
|
+
```
|
|
1655
|
+
|
|
1656
|
+
### Arrays of Models
|
|
1657
|
+
|
|
1658
|
+
When using `$ref` with arrays of models, the `$ref` applies to the array items:
|
|
1659
|
+
|
|
1660
|
+
```ruby
|
|
1661
|
+
EasyTalk.configure do |config|
|
|
1662
|
+
config.use_refs = true
|
|
1663
|
+
end
|
|
1664
|
+
|
|
1665
|
+
class Company
|
|
1666
|
+
include EasyTalk::Model
|
|
1667
|
+
define_schema do
|
|
1668
|
+
property :name, String
|
|
1669
|
+
property :addresses, T::Array[Address]
|
|
1670
|
+
end
|
|
1671
|
+
end
|
|
1672
|
+
|
|
1673
|
+
Company.json_schema
|
|
1674
|
+
# => {
|
|
1675
|
+
# "type" => "object",
|
|
1676
|
+
# "properties" => {
|
|
1677
|
+
# "name" => { "type" => "string" },
|
|
1678
|
+
# "addresses" => {
|
|
1679
|
+
# "type" => "array",
|
|
1680
|
+
# "items" => { "$ref" => "#/$defs/Address" }
|
|
1681
|
+
# }
|
|
1682
|
+
# },
|
|
1683
|
+
# "$defs" => {
|
|
1684
|
+
# "Address" => { ... }
|
|
1685
|
+
# },
|
|
1686
|
+
# ...
|
|
1687
|
+
# }
|
|
1688
|
+
```
|
|
1689
|
+
|
|
1690
|
+
You can also use the per-property `ref` constraint with arrays:
|
|
1691
|
+
|
|
1692
|
+
```ruby
|
|
1693
|
+
property :addresses, T::Array[Address], ref: true
|
|
1694
|
+
```
|
|
1695
|
+
|
|
1696
|
+
### Nilable Models with `$ref`
|
|
1697
|
+
|
|
1698
|
+
When using `$ref` with nilable model types, EasyTalk uses `anyOf` to combine the reference with the null type:
|
|
1699
|
+
|
|
1700
|
+
```ruby
|
|
1701
|
+
EasyTalk.configure do |config|
|
|
1702
|
+
config.use_refs = true
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
class Person
|
|
1706
|
+
include EasyTalk::Model
|
|
1707
|
+
define_schema do
|
|
1708
|
+
property :name, String
|
|
1709
|
+
property :address, T.nilable(Address)
|
|
1710
|
+
end
|
|
1711
|
+
end
|
|
1712
|
+
|
|
1713
|
+
Person.json_schema
|
|
1714
|
+
# => {
|
|
1715
|
+
# "type" => "object",
|
|
1716
|
+
# "properties" => {
|
|
1717
|
+
# "name" => { "type" => "string" },
|
|
1718
|
+
# "address" => {
|
|
1719
|
+
# "anyOf" => [
|
|
1720
|
+
# { "$ref" => "#/$defs/Address" },
|
|
1721
|
+
# { "type" => "null" }
|
|
1722
|
+
# ]
|
|
1723
|
+
# }
|
|
1724
|
+
# },
|
|
1725
|
+
# "$defs" => {
|
|
1726
|
+
# "Address" => { ... }
|
|
1727
|
+
# },
|
|
1728
|
+
# ...
|
|
1729
|
+
# }
|
|
1730
|
+
```
|
|
1731
|
+
|
|
1732
|
+
### Multiple References to the Same Model
|
|
1733
|
+
|
|
1734
|
+
When the same model is used multiple times, it only appears once in `$defs`:
|
|
1735
|
+
|
|
1736
|
+
```ruby
|
|
1737
|
+
class Person
|
|
1738
|
+
include EasyTalk::Model
|
|
1739
|
+
define_schema do
|
|
1740
|
+
property :name, String
|
|
1741
|
+
property :home_address, Address, ref: true
|
|
1742
|
+
property :work_address, Address, ref: true
|
|
1743
|
+
property :shipping_addresses, T::Array[Address], ref: true
|
|
1744
|
+
end
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
Person.json_schema
|
|
1748
|
+
# => {
|
|
1749
|
+
# "type" => "object",
|
|
1750
|
+
# "properties" => {
|
|
1751
|
+
# "name" => { "type" => "string" },
|
|
1752
|
+
# "home_address" => { "$ref" => "#/$defs/Address" },
|
|
1753
|
+
# "work_address" => { "$ref" => "#/$defs/Address" },
|
|
1754
|
+
# "shipping_addresses" => {
|
|
1755
|
+
# "type" => "array",
|
|
1756
|
+
# "items" => { "$ref" => "#/$defs/Address" }
|
|
1757
|
+
# }
|
|
1758
|
+
# },
|
|
1759
|
+
# "$defs" => {
|
|
1760
|
+
# "Address" => { ... } # Only defined once
|
|
1761
|
+
# },
|
|
1762
|
+
# ...
|
|
1763
|
+
# }
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1766
|
+
### Combining `$ref` with Other Constraints
|
|
1767
|
+
|
|
1768
|
+
You can add additional constraints alongside `$ref`:
|
|
1769
|
+
|
|
1770
|
+
```ruby
|
|
1771
|
+
class Person
|
|
1772
|
+
include EasyTalk::Model
|
|
1773
|
+
define_schema do
|
|
1774
|
+
property :address, Address, ref: true, description: "Primary address", title: "Main Address"
|
|
1775
|
+
end
|
|
1776
|
+
end
|
|
1777
|
+
|
|
1778
|
+
Person.json_schema["properties"]["address"]
|
|
1779
|
+
# => {
|
|
1780
|
+
# "$ref" => "#/$defs/Address",
|
|
1781
|
+
# "description" => "Primary address",
|
|
1782
|
+
# "title" => "Main Address"
|
|
1783
|
+
# }
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1786
|
+
### Interaction with `compose`
|
|
1787
|
+
|
|
1788
|
+
When using `compose` with `T::AllOf`, `T::AnyOf`, or `T::OneOf`, the composed models are also placed in `$defs`:
|
|
1789
|
+
|
|
1790
|
+
```ruby
|
|
1791
|
+
class Employee
|
|
1792
|
+
include EasyTalk::Model
|
|
1793
|
+
define_schema do
|
|
1794
|
+
compose T::AllOf[Person, EmployeeDetails]
|
|
1795
|
+
property :badge_number, String
|
|
1796
|
+
end
|
|
1797
|
+
end
|
|
1798
|
+
```
|
|
1799
|
+
|
|
1800
|
+
If you also have properties using `$ref`, both the composed models and property models will appear in `$defs`.
|
|
1801
|
+
|
|
1802
|
+
### Best Practices
|
|
1803
|
+
|
|
1804
|
+
1. **Use global configuration for consistency**: If you prefer `$ref` style, enable it globally rather than per-property.
|
|
1805
|
+
|
|
1806
|
+
2. **Consider schema consumers**: Some JSON Schema validators and tools work better with inlined schemas, while others prefer `$ref`. Choose based on your use case.
|
|
1807
|
+
|
|
1808
|
+
3. **Use `$ref` for frequently reused models**: If a model appears in many places, `$ref` reduces schema size and improves maintainability.
|
|
1809
|
+
|
|
1810
|
+
4. **Keep inline for simple, single-use models**: For models used only once, inlining may be more readable.
|
|
1811
|
+
|
|
1812
|
+
### Default Behavior
|
|
1813
|
+
|
|
1814
|
+
By default, `use_refs` is set to `false`, meaning nested models are inlined. This maintains backward compatibility with previous versions of EasyTalk.
|
|
1815
|
+
|
|
1320
1816
|
## JSON Schema Compatibility
|
|
1321
1817
|
|
|
1322
1818
|
### Supported Versions
|
|
1323
|
-
EasyTalk
|
|
1819
|
+
EasyTalk supports generating schemas compatible with JSON Schema Draft-04 through Draft 2020-12. Use the `schema_version` configuration option to declare which version your schemas conform to (see [JSON Schema Version](#json-schema-version-schema-keyword) above).
|
|
1820
|
+
|
|
1821
|
+
While EasyTalk allows you to specify any draft version via the `$schema` keyword, the generated schema structure is generally compatible across versions. Some newer draft features may require manual adjustment.
|
|
1324
1822
|
|
|
1325
1823
|
### Specification Compliance
|
|
1326
1824
|
To learn about current capabilities, see the [spec/easy_talk/examples](https://github.com/sergiobayona/easy_talk/tree/main/spec/easy_talk/examples) folder. The examples illustrate how EasyTalk generates JSON Schema in different scenarios.
|
|
1327
1825
|
|
|
1328
1826
|
### Known Limitations
|
|
1329
1827
|
- Limited support for custom formats
|
|
1330
|
-
-
|
|
1828
|
+
- Some draft-specific keywords may not be supported
|
|
1331
1829
|
- Complex composition scenarios may require manual adjustment
|
|
1332
1830
|
|
|
1333
1831
|
## License
|