easy_talk 3.1.0 → 3.2.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/.rubocop.yml +4 -0
- data/.yardopts +13 -0
- data/CHANGELOG.md +75 -0
- data/README.md +616 -35
- data/Rakefile +27 -0
- data/docs/.gitignore +1 -0
- data/docs/about.markdown +28 -8
- data/docs/getting-started.markdown +102 -0
- data/docs/index.markdown +51 -4
- data/docs/json_schema_compliance.md +55 -0
- data/docs/nested-models.markdown +216 -0
- data/docs/property-types.markdown +212 -0
- data/docs/schema-definition.markdown +180 -0
- data/lib/easy_talk/builders/base_builder.rb +4 -2
- data/lib/easy_talk/builders/composition_builder.rb +10 -12
- data/lib/easy_talk/builders/object_builder.rb +45 -30
- data/lib/easy_talk/builders/registry.rb +168 -0
- data/lib/easy_talk/builders/typed_array_builder.rb +15 -4
- data/lib/easy_talk/configuration.rb +31 -1
- data/lib/easy_talk/error_formatter/base.rb +100 -0
- data/lib/easy_talk/error_formatter/error_code_mapper.rb +82 -0
- data/lib/easy_talk/error_formatter/flat.rb +38 -0
- data/lib/easy_talk/error_formatter/json_pointer.rb +38 -0
- data/lib/easy_talk/error_formatter/jsonapi.rb +64 -0
- data/lib/easy_talk/error_formatter/path_converter.rb +53 -0
- data/lib/easy_talk/error_formatter/rfc7807.rb +69 -0
- data/lib/easy_talk/error_formatter.rb +143 -0
- data/lib/easy_talk/errors.rb +2 -0
- data/lib/easy_talk/errors_helper.rb +63 -34
- data/lib/easy_talk/model.rb +123 -90
- data/lib/easy_talk/model_helper.rb +13 -0
- data/lib/easy_talk/naming_strategies.rb +20 -0
- data/lib/easy_talk/property.rb +16 -94
- data/lib/easy_talk/ref_helper.rb +27 -0
- data/lib/easy_talk/schema.rb +198 -0
- data/lib/easy_talk/schema_definition.rb +7 -1
- data/lib/easy_talk/schema_methods.rb +80 -0
- data/lib/easy_talk/tools/function_builder.rb +1 -1
- data/lib/easy_talk/type_introspection.rb +178 -0
- data/lib/easy_talk/types/base_composer.rb +2 -1
- data/lib/easy_talk/types/composer.rb +4 -0
- data/lib/easy_talk/validation_adapters/active_model_adapter.rb +329 -0
- data/lib/easy_talk/validation_adapters/base.rb +144 -0
- data/lib/easy_talk/validation_adapters/none_adapter.rb +36 -0
- data/lib/easy_talk/validation_adapters/registry.rb +87 -0
- data/lib/easy_talk/validation_builder.rb +28 -309
- data/lib/easy_talk/version.rb +1 -1
- data/lib/easy_talk.rb +41 -0
- metadata +26 -4
- data/docs/404.html +0 -25
- data/docs/_posts/2024-05-07-welcome-to-jekyll.markdown +0 -29
- data/easy_talk.gemspec +0 -39
data/Rakefile
CHANGED
|
@@ -3,9 +3,36 @@
|
|
|
3
3
|
require 'bundler/gem_tasks'
|
|
4
4
|
require 'rspec/core/rake_task'
|
|
5
5
|
require 'rubocop/rake_task'
|
|
6
|
+
require 'yard'
|
|
6
7
|
|
|
7
8
|
RSpec::Core::RakeTask.new(:spec)
|
|
8
9
|
|
|
9
10
|
RuboCop::RakeTask.new
|
|
10
11
|
|
|
12
|
+
YARD::Rake::YardocTask.new(:yard) do |t|
|
|
13
|
+
t.files = ['lib/**/*.rb']
|
|
14
|
+
t.options = ['--readme', 'README.md', '--output-dir', 'docs/api']
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
namespace :docs do
|
|
18
|
+
desc 'Generate YARD API documentation'
|
|
19
|
+
task api: :yard
|
|
20
|
+
|
|
21
|
+
desc 'Serve Jekyll documentation locally'
|
|
22
|
+
task :serve do
|
|
23
|
+
Dir.chdir('docs') do
|
|
24
|
+
sh 'bundle install --quiet'
|
|
25
|
+
sh 'bundle exec jekyll serve'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc 'Build all documentation (Jekyll + YARD)'
|
|
30
|
+
task build: :yard do
|
|
31
|
+
Dir.chdir('docs') do
|
|
32
|
+
sh 'bundle install --quiet'
|
|
33
|
+
sh 'bundle exec jekyll build'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
11
38
|
task default: %i[spec rubocop]
|
data/docs/.gitignore
CHANGED
data/docs/about.markdown
CHANGED
|
@@ -4,15 +4,35 @@ title: About
|
|
|
4
4
|
permalink: /about/
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# About EasyTalk
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
[jekyll][jekyll-organization] /
|
|
11
|
-
[minima](https://github.com/jekyll/minima)
|
|
9
|
+
EasyTalk is a Ruby library for defining and generating JSON Schema from Ruby classes. Inspired by Python's Pydantic library, it provides an ActiveModel-like interface for defining structured data models with automatic validation and JSON Schema generation.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
[jekyll][jekyll-organization] /
|
|
15
|
-
[jekyll](https://github.com/jekyll/jekyll)
|
|
11
|
+
## Why EasyTalk?
|
|
16
12
|
|
|
13
|
+
- **Type Safety** - Define your data structures once and get validation automatically
|
|
14
|
+
- **JSON Schema Generation** - Produce standards-compliant JSON Schema from Ruby code
|
|
15
|
+
- **LLM Integration** - Generate function schemas for OpenAI and other LLM providers
|
|
16
|
+
- **ActiveModel Compatible** - Works with Rails forms, validations, and serialization
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Use Cases
|
|
19
|
+
|
|
20
|
+
- **API Request/Response Validation** - Define expected shapes for API payloads
|
|
21
|
+
- **LLM Function Calling** - Generate structured output schemas for AI applications
|
|
22
|
+
- **Configuration Files** - Validate configuration against a schema
|
|
23
|
+
- **Data Pipelines** - Ensure data conforms to expected structures
|
|
24
|
+
|
|
25
|
+
## Links
|
|
26
|
+
|
|
27
|
+
- [GitHub Repository](https://github.com/sergiobayona/easy_talk)
|
|
28
|
+
- [RubyGems](https://rubygems.org/gems/easy_talk)
|
|
29
|
+
- [API Documentation](api/)
|
|
30
|
+
- [Changelog](https://github.com/sergiobayona/easy_talk/blob/main/CHANGELOG.md)
|
|
31
|
+
|
|
32
|
+
## Author
|
|
33
|
+
|
|
34
|
+
Created by [Sergio Bayona](https://github.com/sergiobayona).
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
EasyTalk is released under the [MIT License](https://github.com/sergiobayona/easy_talk/blob/main/LICENSE.txt).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: page
|
|
3
|
+
title: Getting Started
|
|
4
|
+
permalink: /getting-started/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Getting Started with EasyTalk
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Ruby 3.2 or later
|
|
12
|
+
- ActiveModel/ActiveSupport 7.0-8.x
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add EasyTalk to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem 'easy_talk'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then run:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bundle install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or install directly:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gem install easy_talk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Basic Usage
|
|
35
|
+
|
|
36
|
+
### 1. Define a Model
|
|
37
|
+
|
|
38
|
+
Include `EasyTalk::Model` in your class and use `define_schema` to declare properties:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
require 'easy_talk'
|
|
42
|
+
|
|
43
|
+
class Person
|
|
44
|
+
include EasyTalk::Model
|
|
45
|
+
|
|
46
|
+
define_schema do
|
|
47
|
+
title "Person"
|
|
48
|
+
description "A person record"
|
|
49
|
+
|
|
50
|
+
property :name, String
|
|
51
|
+
property :age, Integer
|
|
52
|
+
property :email, String, format: "email"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Generate JSON Schema
|
|
58
|
+
|
|
59
|
+
Call `.json_schema` on your class to get the JSON Schema:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
Person.json_schema
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This produces:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"type": "object",
|
|
70
|
+
"title": "Person",
|
|
71
|
+
"description": "A person record",
|
|
72
|
+
"properties": {
|
|
73
|
+
"name": { "type": "string" },
|
|
74
|
+
"age": { "type": "integer" },
|
|
75
|
+
"email": { "type": "string", "format": "email" }
|
|
76
|
+
},
|
|
77
|
+
"required": ["name", "age", "email"],
|
|
78
|
+
"additionalProperties": false
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Create and Validate Instances
|
|
83
|
+
|
|
84
|
+
EasyTalk models work like ActiveModel objects:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
person = Person.new(name: "Alice", age: 30, email: "alice@example.com")
|
|
88
|
+
person.valid? # => true
|
|
89
|
+
person.name # => "Alice"
|
|
90
|
+
|
|
91
|
+
# Invalid data triggers validation errors
|
|
92
|
+
invalid = Person.new(name: "", age: -5, email: "not-an-email")
|
|
93
|
+
invalid.valid? # => false
|
|
94
|
+
invalid.errors.full_messages
|
|
95
|
+
# => ["Name is too short", "Age must be greater than or equal to 0", ...]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Next Steps
|
|
99
|
+
|
|
100
|
+
- [Schema Definition](schema-definition) - Learn the full DSL
|
|
101
|
+
- [Property Types](property-types) - Explore available types and constraints
|
|
102
|
+
- [Nested Models](nested-models) - Build complex, composable schemas
|
data/docs/index.markdown
CHANGED
|
@@ -1,7 +1,54 @@
|
|
|
1
1
|
---
|
|
2
|
-
# Feel free to add content and custom Front Matter to this file.
|
|
3
|
-
# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults
|
|
4
|
-
|
|
5
2
|
layout: home
|
|
3
|
+
title: Home
|
|
6
4
|
---
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
# EasyTalk
|
|
7
|
+
|
|
8
|
+
EasyTalk is a Ruby library for defining and generating JSON Schema from Ruby classes. It provides an ActiveModel-like interface for defining structured data models with validation and JSON Schema generation capabilities.
|
|
9
|
+
|
|
10
|
+
## Key Features
|
|
11
|
+
|
|
12
|
+
- **Schema Definition DSL** - Define JSON Schema using a clean Ruby DSL
|
|
13
|
+
- **Type System** - Support for Ruby types and Sorbet-style generics
|
|
14
|
+
- **ActiveModel Integration** - Built-in validations from schema constraints
|
|
15
|
+
- **Nested Models** - Compose complex schemas from simple building blocks
|
|
16
|
+
- **LLM Function Calling** - Generate OpenAI-compatible function schemas
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
class User
|
|
22
|
+
include EasyTalk::Model
|
|
23
|
+
|
|
24
|
+
define_schema do
|
|
25
|
+
title "User"
|
|
26
|
+
description "A user in the system"
|
|
27
|
+
property :name, String, min_length: 2
|
|
28
|
+
property :email, String, format: "email"
|
|
29
|
+
property :age, Integer, minimum: 0, optional: true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Generate JSON Schema
|
|
34
|
+
User.json_schema
|
|
35
|
+
# => {"type"=>"object", "title"=>"User", ...}
|
|
36
|
+
|
|
37
|
+
# Create and validate instances
|
|
38
|
+
user = User.new(name: "Alice", email: "alice@example.com")
|
|
39
|
+
user.valid? # => true
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
- [Getting Started](getting-started) - Installation and basic usage
|
|
45
|
+
- [Schema Definition](schema-definition) - How to define schemas
|
|
46
|
+
- [Property Types](property-types) - Available types and constraints
|
|
47
|
+
- [Nested Models](nested-models) - Composing complex schemas
|
|
48
|
+
- [API Reference](api/) - Generated API documentation
|
|
49
|
+
|
|
50
|
+
## Links
|
|
51
|
+
|
|
52
|
+
- [GitHub Repository](https://github.com/sergiobayona/easy_talk)
|
|
53
|
+
- [RubyGems](https://rubygems.org/gems/easy_talk)
|
|
54
|
+
- [Changelog](https://github.com/sergiobayona/easy_talk/blob/main/CHANGELOG.md)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# JSON Schema Compliance Guide
|
|
2
|
+
|
|
3
|
+
This document defines the strategy for testing and improving `EasyTalk`'s compliance with the official [JSON Schema Specification](https://json-schema.org/specification).
|
|
4
|
+
|
|
5
|
+
## Test Suite Setup
|
|
6
|
+
|
|
7
|
+
`EasyTalk` integrates the standard [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) as a git submodule in `spec/fixtures/json_schema_test_suite`. This provides a language-agnostic set of test cases covering all aspects of the specification.
|
|
8
|
+
|
|
9
|
+
### Infrastructure
|
|
10
|
+
|
|
11
|
+
* **Submodule**: Located at `spec/fixtures/json_schema_test_suite`.
|
|
12
|
+
* **Converter**: `spec/support/json_schema_converter.rb` dynamically converts raw JSON Schema definitions into `EasyTalk::Model` classes at runtime.
|
|
13
|
+
* **Runner**: `spec/integration/json_schema_compliance_spec.rb` iterates over the test suite files, generates models, and asserts valid/invalid behavior.
|
|
14
|
+
|
|
15
|
+
## Running the Tests
|
|
16
|
+
|
|
17
|
+
The compliance tests are **optional** and excluded from the default `rspec` run to prevent noise from known incompatibilities.
|
|
18
|
+
|
|
19
|
+
To run the compliance suite:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bundle exec rspec --tag json_schema_compliance spec/integration/json_schema_compliance_spec.rb
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Compliance Strategy
|
|
26
|
+
|
|
27
|
+
The goal is to incrementally improve compliance by enabling more tests and fixing the underlying issues in `EasyTalk`.
|
|
28
|
+
|
|
29
|
+
### 1. Identify Gaps
|
|
30
|
+
The current test runner skips many tests (marked as `pending` or explicit `skip`).
|
|
31
|
+
* **Root Primitives**: `EasyTalk` models are Objects. Tests for root integers/strings are skipped.
|
|
32
|
+
* **Strict Naming**: Tests with property names that are invalid Ruby identifiers (e.g., `foo-bar`, `123`, `constructor`) are currently skipped.
|
|
33
|
+
* **Missing Keywords**: Keywords like `patternProperties`, `const`, and `oneOf` (in specific contexts) may fail.
|
|
34
|
+
|
|
35
|
+
### 2. Workflow for Improvements
|
|
36
|
+
1. **Select a Feature**: Pick a specific file (e.g., `properties.json`, `required.json`) or a skipped section.
|
|
37
|
+
2. **Un-skip Tests**: Remove the `skip` logic in `spec/integration/json_schema_compliance_spec.rb` for that feature.
|
|
38
|
+
3. **Run & Analyze**: Run the specific test file.
|
|
39
|
+
```bash
|
|
40
|
+
bundle exec rspec --tag json_schema_compliance
|
|
41
|
+
```
|
|
42
|
+
4. **Implement Fix**: Modify `EasyTalk` internals (e.g., `keywords.rb`, `schema_definition.rb`) to support the feature.
|
|
43
|
+
5. **Sanitize Inputs**: Update `JsonSchemaConverter` if the test case requires adaptation (e.g., mapping a JSON key to a safe Ruby method name via `as:`) without changing the underlying validation logic.
|
|
44
|
+
|
|
45
|
+
### 3. Known Critical Issues
|
|
46
|
+
* **Reserved Words**: Properties like `method`, `class`, `constructor` conflict with Ruby. Fix requires a robust proxy or sanitization layer in `EasyTalk::Model`.
|
|
47
|
+
* **Boolean Schemas**: `properties: { foo: false }` is valid JSON Schema (property forbidden) but not currently supported by `EasyTalk`.
|
|
48
|
+
* **Strict Property Validation**: `EasyTalk` raises errors for invalid property names at definition time. Compliance requires allowing arbitrary property keys (perhaps via `validates_with` logic instead of metaprogramming methods).
|
|
49
|
+
|
|
50
|
+
## Contributing
|
|
51
|
+
|
|
52
|
+
When adding support for a new JSON Schema keyword:
|
|
53
|
+
1. Check if a test file exists in `spec/fixtures/json_schema_test_suite/tests/draft7/`.
|
|
54
|
+
2. Add the filename to the `FOCUS_FILES` list in `spec/integration/json_schema_compliance_spec.rb`.
|
|
55
|
+
3. Implement the feature and verify pass.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: page
|
|
3
|
+
title: Nested Models
|
|
4
|
+
permalink: /nested-models/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Nested Models
|
|
8
|
+
|
|
9
|
+
EasyTalk supports composing complex schemas from simpler building blocks. This enables clean, reusable data structures.
|
|
10
|
+
|
|
11
|
+
## Basic Nesting
|
|
12
|
+
|
|
13
|
+
Reference another EasyTalk model as a property type:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
class Address
|
|
17
|
+
include EasyTalk::Model
|
|
18
|
+
|
|
19
|
+
define_schema do
|
|
20
|
+
property :street, String
|
|
21
|
+
property :city, String
|
|
22
|
+
property :zip_code, String, pattern: /^\d{5}$/
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Person
|
|
27
|
+
include EasyTalk::Model
|
|
28
|
+
|
|
29
|
+
define_schema do
|
|
30
|
+
property :name, String
|
|
31
|
+
property :home_address, Address
|
|
32
|
+
property :work_address, Address, optional: true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Generated Schema
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"name": { "type": "string" },
|
|
44
|
+
"home_address": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"properties": {
|
|
47
|
+
"street": { "type": "string" },
|
|
48
|
+
"city": { "type": "string" },
|
|
49
|
+
"zip_code": { "type": "string", "pattern": "^\\d{5}$" }
|
|
50
|
+
},
|
|
51
|
+
"required": ["street", "city", "zip_code"]
|
|
52
|
+
},
|
|
53
|
+
"work_address": { ... }
|
|
54
|
+
},
|
|
55
|
+
"required": ["name", "home_address"]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Arrays of Models
|
|
60
|
+
|
|
61
|
+
Use `T::Array[ModelClass]` for collections:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
class Order
|
|
65
|
+
include EasyTalk::Model
|
|
66
|
+
|
|
67
|
+
define_schema do
|
|
68
|
+
property :id, String
|
|
69
|
+
property :items, T::Array[LineItem], min_items: 1
|
|
70
|
+
property :shipping_address, Address
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class LineItem
|
|
75
|
+
include EasyTalk::Model
|
|
76
|
+
|
|
77
|
+
define_schema do
|
|
78
|
+
property :product_id, String
|
|
79
|
+
property :quantity, Integer, minimum: 1
|
|
80
|
+
property :price, Float, minimum: 0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Auto-Instantiation
|
|
86
|
+
|
|
87
|
+
When you pass a Hash to a nested model property, EasyTalk automatically instantiates the nested model:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
person = Person.new(
|
|
91
|
+
name: "Alice",
|
|
92
|
+
home_address: {
|
|
93
|
+
street: "123 Main St",
|
|
94
|
+
city: "Boston",
|
|
95
|
+
zip_code: "02101"
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
person.home_address.class # => Address
|
|
100
|
+
person.home_address.city # => "Boston"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This works recursively for deeply nested structures.
|
|
104
|
+
|
|
105
|
+
## Using $ref for Reusability
|
|
106
|
+
|
|
107
|
+
By default, nested models are inlined. Enable `$ref` for cleaner, more reusable schemas:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
class Person
|
|
111
|
+
include EasyTalk::Model
|
|
112
|
+
|
|
113
|
+
define_schema do
|
|
114
|
+
property :name, String
|
|
115
|
+
property :address, Address, ref: true
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This generates:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"type": "object",
|
|
125
|
+
"properties": {
|
|
126
|
+
"name": { "type": "string" },
|
|
127
|
+
"address": { "$ref": "#/$defs/Address" }
|
|
128
|
+
},
|
|
129
|
+
"$defs": {
|
|
130
|
+
"Address": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"properties": { ... }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Nullable Nested Models
|
|
139
|
+
|
|
140
|
+
Allow null values for nested models:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
property :backup_address, T.nilable(Address)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or make it both nullable and optional:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
property :backup_address, T.nilable(Address), optional: true
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Composition with compose
|
|
153
|
+
|
|
154
|
+
Use `compose` to merge multiple schemas into one:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
class BasicInfo
|
|
158
|
+
include EasyTalk::Model
|
|
159
|
+
define_schema do
|
|
160
|
+
property :name, String
|
|
161
|
+
property :email, String
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class ContactInfo
|
|
166
|
+
include EasyTalk::Model
|
|
167
|
+
define_schema do
|
|
168
|
+
property :phone, String
|
|
169
|
+
property :address, Address
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
class FullProfile
|
|
174
|
+
include EasyTalk::Model
|
|
175
|
+
define_schema do
|
|
176
|
+
compose T::AllOf[BasicInfo, ContactInfo]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Polymorphic Types
|
|
182
|
+
|
|
183
|
+
Use `T::OneOf` or `T::AnyOf` for polymorphic properties:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
class EmailContact
|
|
187
|
+
include EasyTalk::Model
|
|
188
|
+
define_schema do
|
|
189
|
+
property :email, String, format: "email"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class PhoneContact
|
|
194
|
+
include EasyTalk::Model
|
|
195
|
+
define_schema do
|
|
196
|
+
property :phone, String
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
class User
|
|
201
|
+
include EasyTalk::Model
|
|
202
|
+
define_schema do
|
|
203
|
+
property :name, String
|
|
204
|
+
property :primary_contact, T::OneOf[EmailContact, PhoneContact]
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This ensures the `primary_contact` matches exactly one of the specified schemas.
|
|
210
|
+
|
|
211
|
+
## Best Practices
|
|
212
|
+
|
|
213
|
+
1. **Keep models focused** - Each model should represent one concept
|
|
214
|
+
2. **Reuse models** - Define common structures (Address, Money, etc.) once
|
|
215
|
+
3. **Use $ref for large schemas** - Reduces duplication and improves readability
|
|
216
|
+
4. **Validate at boundaries** - Nested models validate automatically when the parent validates
|