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.
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 ActiveRecord models**: Integrate with existing code or build from scratch.
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 # Makes T.nilable properties also optional
711
-
712
- # NEW in v2.0.0: Automatic validation generation
713
- config.auto_validations = true # Automatically generate ActiveModel validations
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
- Some settings can be configured per model:
626
+ You can configure additional properties for individual models:
742
627
 
743
628
  ```ruby
744
- class Product < ActiveRecord::Base
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
- enhance_schema({
771
- title: "User Account",
772
- description: "User account information",
773
- properties: {
774
- name: {
775
- title: "Full Name",
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.exclude_foreign_keys = true
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
- - **ActiveRecord Integration**: Fully backward compatible
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 is currently loose about JSON Schema versions. It doesn't strictly enforce or adhere to any particular version of the specification. The goal is to add more robust support for the latest JSON Schema specs in the future.
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
- - No direct support for JSON Schema draft 2020-12 features
1828
+ - Some draft-specific keywords may not be supported
1331
1829
  - Complex composition scenarios may require manual adjustment
1332
1830
 
1333
1831
  ## License