easy_talk 3.0.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 +105 -0
- data/README.md +1268 -40
- 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 +119 -10
- data/lib/easy_talk/builders/registry.rb +168 -0
- data/lib/easy_talk/builders/typed_array_builder.rb +20 -6
- data/lib/easy_talk/configuration.rb +51 -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/keywords.rb +2 -0
- data/lib/easy_talk/model.rb +125 -41
- data/lib/easy_talk/model_helper.rb +13 -0
- data/lib/easy_talk/naming_strategies.rb +20 -0
- data/lib/easy_talk/property.rb +32 -44
- 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 +28 -6
- 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/README.md
CHANGED
|
@@ -2,20 +2,179 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/easy_talk)
|
|
4
4
|
[](https://github.com/sergiobayona/easy_talk/actions/workflows/dev-build.yml)
|
|
5
|
+
[](https://rubydoc.info/gems/easy_talk)
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Introduction](#introduction)
|
|
10
|
+
- [What is EasyTalk?](#what-is-easytalk)
|
|
11
|
+
- [Key Features](#key-features)
|
|
12
|
+
- [Use Cases](#use-cases)
|
|
13
|
+
- [Inspiration](#inspiration)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Requirements](#requirements)
|
|
16
|
+
- [Version 2.0.0 Breaking Changes](#version-200-breaking-changes)
|
|
17
|
+
- [Installation Steps](#installation-steps)
|
|
18
|
+
- [Verification](#verification)
|
|
19
|
+
- [Quick Start](#quick-start)
|
|
20
|
+
- [Minimal Example](#minimal-example)
|
|
21
|
+
- [Generated JSON Schema](#generated-json-schema)
|
|
22
|
+
- [Basic Usage](#basic-usage)
|
|
23
|
+
- [Core Concepts](#core-concepts)
|
|
24
|
+
- [Schema Definition](#schema-definition)
|
|
25
|
+
- [Property Types](#property-types)
|
|
26
|
+
- [Ruby Types](#ruby-types)
|
|
27
|
+
- [Sorbet-Style Types](#sorbet-style-types)
|
|
28
|
+
- [Custom Types](#custom-types)
|
|
29
|
+
- [Property Constraints](#property-constraints)
|
|
30
|
+
- [Required vs Optional Properties](#required-vs-optional-properties)
|
|
31
|
+
- [Automatic Validation Generation](#automatic-validation-generation)
|
|
32
|
+
- [Manual Validation Overrides](#manual-validation-overrides)
|
|
33
|
+
- [Defining Schemas](#defining-schemas)
|
|
34
|
+
- [Basic Schema Structure](#basic-schema-structure)
|
|
35
|
+
- [Property Definitions](#property-definitions)
|
|
36
|
+
- [Arrays and Collections](#arrays-and-collections)
|
|
37
|
+
- [Constraints and Automatic Validations](#constraints-and-automatic-validations)
|
|
38
|
+
- [Supported Constraint-to-Validation Mappings](#supported-constraint-to-validation-mappings)
|
|
39
|
+
- [Additional Properties](#additional-properties)
|
|
40
|
+
- [Property Naming](#property-naming)
|
|
41
|
+
- [Schema Composition](#schema-composition)
|
|
42
|
+
- [Using T::AnyOf](#using-tanyof)
|
|
43
|
+
- [Using T::OneOf](#using-toneof)
|
|
44
|
+
- [Using T::AllOf](#using-tallof)
|
|
45
|
+
- [Array Composition](#array-composition)
|
|
46
|
+
- [Complex Compositions](#complex-compositions)
|
|
47
|
+
- [Reusing Models](#reusing-models)
|
|
48
|
+
- [ActiveModel Integration](#activemodel-integration)
|
|
49
|
+
- [Enhanced Validation System](#enhanced-validation-system)
|
|
50
|
+
- [Error Handling](#error-handling)
|
|
51
|
+
- [Standardized Error Formatting](#standardized-error-formatting)
|
|
52
|
+
- [Available Formats](#available-formats)
|
|
53
|
+
- [Instance Methods](#instance-methods)
|
|
54
|
+
- [Direct API Usage](#direct-api-usage)
|
|
55
|
+
- [Configuration](#configuration)
|
|
56
|
+
- [Model Attributes](#model-attributes)
|
|
57
|
+
- [Advanced Features](#advanced-features)
|
|
58
|
+
- [Schema-Only Mode (EasyTalk::Schema)](#schema-only-mode-easytalkschema)
|
|
59
|
+
- [Key Differences from EasyTalk::Model](#key-differences-from-easytalkmodel)
|
|
60
|
+
- [When to Use Each](#when-to-use-each)
|
|
61
|
+
- [LLM Function Generation](#llm-function-generation)
|
|
62
|
+
- [Schema Transformation](#schema-transformation)
|
|
63
|
+
- [Type Checking and Validation](#type-checking-and-validation)
|
|
64
|
+
- [Custom Type Builders](#custom-type-builders)
|
|
65
|
+
- [Registering Custom Types](#registering-custom-types)
|
|
66
|
+
- [Creating a Custom Builder](#creating-a-custom-builder)
|
|
67
|
+
- [Collection Type Builders](#collection-type-builders)
|
|
68
|
+
- [Overriding Built-in Types](#overriding-built-in-types)
|
|
69
|
+
- [Registry API](#registry-api)
|
|
70
|
+
- [Type Introspection](#type-introspection)
|
|
71
|
+
- [Configuration](#configuration-1)
|
|
72
|
+
- [Global Settings](#global-settings)
|
|
73
|
+
- [Automatic Validation Configuration](#automatic-validation-configuration)
|
|
74
|
+
- [Per-Model Configuration](#per-model-configuration)
|
|
75
|
+
- [Validation Adapters](#validation-adapters)
|
|
76
|
+
- [Built-in Adapters](#built-in-adapters)
|
|
77
|
+
- [Global Adapter Configuration](#global-adapter-configuration)
|
|
78
|
+
- [Per-Model Validation Control](#per-model-validation-control)
|
|
79
|
+
- [Per-Property Validation Control](#per-property-validation-control)
|
|
80
|
+
- [Custom Validation Adapters](#custom-validation-adapters)
|
|
81
|
+
- [Examples](#examples)
|
|
82
|
+
- [User Registration (with Auto-Validations)](#user-registration-with-auto-validations)
|
|
83
|
+
- [Payment Processing](#payment-processing)
|
|
84
|
+
- [Complex Object Hierarchies](#complex-object-hierarchies)
|
|
85
|
+
- [API Integration](#api-integration)
|
|
86
|
+
- [Troubleshooting](#troubleshooting)
|
|
87
|
+
- [Common Errors](#common-errors)
|
|
88
|
+
- ["Invalid property name"](#invalid-property-name)
|
|
89
|
+
- ["Property type is missing"](#property-type-is-missing)
|
|
90
|
+
- ["Unknown option"](#unknown-option)
|
|
91
|
+
- [Schema Validation Issues](#schema-validation-issues)
|
|
92
|
+
- [Type Errors](#type-errors)
|
|
93
|
+
- [Best Practices](#best-practices)
|
|
94
|
+
- [Nullable vs Optional Properties in EasyTalk](#nullable-vs-optional-properties-in-easytalk)
|
|
95
|
+
- [Key Concepts](#key-concepts)
|
|
96
|
+
- [Nullable Properties](#nullable-properties)
|
|
97
|
+
- [Optional Properties](#optional-properties)
|
|
98
|
+
- [Nullable AND Optional Properties](#nullable-and-optional-properties)
|
|
99
|
+
- [Configuration Options](#configuration-options)
|
|
100
|
+
- [Practical Examples](#practical-examples)
|
|
101
|
+
- [User Profile Schema](#user-profile-schema)
|
|
102
|
+
- [Common Gotchas](#common-gotchas)
|
|
103
|
+
- [Misconception: Nullable Implies Optional](#misconception-nullable-implies-optional)
|
|
104
|
+
- [Misconception: Optional Properties Accept Null](#misconception-optional-properties-accept-null)
|
|
105
|
+
- [Migration from Earlier Versions](#migration-from-earlier-versions)
|
|
106
|
+
- [Best Practices](#best-practices-1)
|
|
107
|
+
- [JSON Schema Comparison](#json-schema-comparison)
|
|
108
|
+
- [Migration Guide from v1.x to v2.0](#migration-guide-from-v1x-to-v20)
|
|
109
|
+
- [Breaking Changes Summary](#breaking-changes-summary)
|
|
110
|
+
- [Migration Steps](#migration-steps)
|
|
111
|
+
- [1. Replace Hash-based Nested Schemas](#1-replace-hash-based-nested-schemas)
|
|
112
|
+
- [2. Review Automatic Validations](#2-review-automatic-validations)
|
|
113
|
+
- [3. Configuration Updates](#3-configuration-updates)
|
|
114
|
+
- [Compatibility Notes](#compatibility-notes)
|
|
115
|
+
- [Development and Contributing](#development-and-contributing)
|
|
116
|
+
- [Setting Up the Development Environment](#setting-up-the-development-environment)
|
|
117
|
+
- [Running Tests](#running-tests)
|
|
118
|
+
- [Code Quality](#code-quality)
|
|
119
|
+
- [Contributing Guidelines](#contributing-guidelines)
|
|
120
|
+
- [JSON Schema Version (`$schema` Keyword)](#json-schema-version-schema-keyword)
|
|
121
|
+
- [Why Use `$schema`?](#why-use-schema)
|
|
122
|
+
- [Supported Draft Versions](#supported-draft-versions)
|
|
123
|
+
- [Global Configuration](#global-configuration)
|
|
124
|
+
- [Per-Model Configuration](#per-model-configuration-1)
|
|
125
|
+
- [Disabling `$schema` for Specific Models](#disabling-schema-for-specific-models)
|
|
126
|
+
- [Custom Schema URIs](#custom-schema-uris)
|
|
127
|
+
- [Nested Models](#nested-models)
|
|
128
|
+
- [Default Behavior](#default-behavior)
|
|
129
|
+
- [Best Practices](#best-practices-2)
|
|
130
|
+
- [Schema Identifier (`$id` Keyword)](#schema-identifier-id-keyword)
|
|
131
|
+
- [Why Use `$id`?](#why-use-id)
|
|
132
|
+
- [Global Configuration](#global-configuration-1)
|
|
133
|
+
- [Per-Model Configuration](#per-model-configuration-2)
|
|
134
|
+
- [Disabling `$id` for Specific Models](#disabling-id-for-specific-models)
|
|
135
|
+
- [Combining `$schema` and `$id`](#combining-schema-and-id)
|
|
136
|
+
- [Nested Models](#nested-models-1)
|
|
137
|
+
- [URI Formats](#uri-formats)
|
|
138
|
+
- [Default Behavior](#default-behavior-1)
|
|
139
|
+
- [Best Practices](#best-practices-3)
|
|
140
|
+
- [Schema References (`$ref` and `$defs`)](#schema-references-ref-and-defs)
|
|
141
|
+
- [Why Use `$ref`?](#why-use-ref)
|
|
142
|
+
- [Default Behavior (Inline Schemas)](#default-behavior-inline-schemas)
|
|
143
|
+
- [Enabling `$ref` References](#enabling-ref-references)
|
|
144
|
+
- [Global Configuration](#global-configuration-2)
|
|
145
|
+
- [Per-Property Configuration](#per-property-configuration)
|
|
146
|
+
- [Arrays of Models](#arrays-of-models)
|
|
147
|
+
- [Nilable Models with `$ref`](#nilable-models-with-ref)
|
|
148
|
+
- [Multiple References to the Same Model](#multiple-references-to-the-same-model)
|
|
149
|
+
- [Combining `$ref` with Other Constraints](#combining-ref-with-other-constraints)
|
|
150
|
+
- [Interaction with `compose`](#interaction-with-compose)
|
|
151
|
+
- [Best Practices](#best-practices-4)
|
|
152
|
+
- [Default Behavior](#default-behavior-2)
|
|
153
|
+
- [JSON Schema Compatibility](#json-schema-compatibility)
|
|
154
|
+
- [Supported Versions](#supported-versions)
|
|
155
|
+
- [Specification Compliance](#specification-compliance)
|
|
156
|
+
- [Known Limitations](#known-limitations)
|
|
157
|
+
- [API Reference](#api-reference)
|
|
158
|
+
- [Core Modules](#core-modules)
|
|
159
|
+
- [Builders](#builders)
|
|
160
|
+
- [Configuration & Utilities](#configuration--utilities)
|
|
161
|
+
- [License](#license)
|
|
5
162
|
|
|
6
163
|
## Introduction
|
|
7
164
|
|
|
8
165
|
### What is EasyTalk?
|
|
9
|
-
EasyTalk is a Ruby library
|
|
166
|
+
EasyTalk is a Ruby library for defining structured data models with automatic JSON Schema generation and flexible validation. Define your schema once and get both a JSON Schema document and runtime validations from a single source of truth.
|
|
10
167
|
|
|
11
168
|
### Key Features
|
|
12
|
-
* **Intuitive Schema Definition**:
|
|
13
|
-
* **
|
|
14
|
-
* **
|
|
15
|
-
* **
|
|
16
|
-
* **
|
|
17
|
-
* **
|
|
18
|
-
* **
|
|
169
|
+
* **Intuitive Schema Definition**: Define JSON Schema using Ruby classes with a clean, declarative DSL.
|
|
170
|
+
* **Rich Type System**: Supports Ruby primitives plus Sorbet-style types (`T::Array[Type]`, `T.nilable(Type)`, `T::Boolean`) and composition types (`T::AnyOf`, `T::OneOf`, `T::AllOf`).
|
|
171
|
+
* **Automatic Validations**: Schema constraints automatically generate ActiveModel validations, including nested model validation within arrays.
|
|
172
|
+
* **API Error Formatting**: Format validation errors in multiple standards (flat, JSON Pointer, RFC 7807, JSON:API).
|
|
173
|
+
* **LLM Function Support**: Generate JSON Schema for LLM function calling (OpenAI, Anthropic, etc.).
|
|
174
|
+
* **Schema Composition**: Reference models within other models and use `$ref`/`$defs` for reusable definitions.
|
|
175
|
+
* **Nested Model Instantiation**: Hash attributes automatically instantiate nested EasyTalk models, including within arrays.
|
|
176
|
+
* **Flexible Configuration**: Global and per-model settings for validation behavior, naming strategies, and schema output.
|
|
177
|
+
* **JSON Schema Versions**: Support for Draft-04 through Draft 2020-12 with configurable `$schema` and `$id` keywords.
|
|
19
178
|
|
|
20
179
|
### Use Cases
|
|
21
180
|
- API request/response validation
|
|
@@ -25,7 +184,7 @@ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema.
|
|
|
25
184
|
- Configuration schema definitions
|
|
26
185
|
|
|
27
186
|
### Inspiration
|
|
28
|
-
Inspired by Python's Pydantic library, EasyTalk brings similar functionality to the Ruby ecosystem, providing a Ruby-friendly approach to JSON Schema operations.
|
|
187
|
+
Inspired by Python's [Pydantic](https://docs.pydantic.dev/) library, EasyTalk brings similar functionality to the Ruby ecosystem, providing a Ruby-friendly approach to JSON Schema operations.
|
|
29
188
|
|
|
30
189
|
## Installation
|
|
31
190
|
|
|
@@ -364,6 +523,27 @@ company.location = "New York" # Additional property
|
|
|
364
523
|
company.employee_count = 100 # Additional property
|
|
365
524
|
```
|
|
366
525
|
|
|
526
|
+
### Property Naming
|
|
527
|
+
You can configure the naming strategy for properties globally or per schema:
|
|
528
|
+
|
|
529
|
+
```ruby
|
|
530
|
+
EasyTalk.configure do |config|
|
|
531
|
+
config.property_naming_strategy = :snake_case # Options: :identity, :snake_case, :camel_case, :pascal_case
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
define_schema do
|
|
535
|
+
property_naming_strategy :camel_case # Overrides global setting for this schema
|
|
536
|
+
property :name, String
|
|
537
|
+
end
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
This affects how property names are represented in the generated JSON Schema.
|
|
541
|
+
Additionally, names can be overridden per property:
|
|
542
|
+
|
|
543
|
+
```ruby
|
|
544
|
+
property :first_name, String, as: "firstName" # Overrides global naming strategy
|
|
545
|
+
```
|
|
546
|
+
|
|
367
547
|
## Schema Composition
|
|
368
548
|
|
|
369
549
|
### Using T::AnyOf
|
|
@@ -405,13 +585,67 @@ class VehicleRegistration
|
|
|
405
585
|
end
|
|
406
586
|
```
|
|
407
587
|
|
|
588
|
+
### Array Composition
|
|
589
|
+
Composition types can be combined with arrays to define collections where each item must match one of several schemas:
|
|
590
|
+
|
|
591
|
+
```ruby
|
|
592
|
+
class ProductA
|
|
593
|
+
include EasyTalk::Model
|
|
594
|
+
define_schema do
|
|
595
|
+
property :sku, String
|
|
596
|
+
property :weight, Float
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
class ProductB
|
|
601
|
+
include EasyTalk::Model
|
|
602
|
+
define_schema do
|
|
603
|
+
property :sku, String
|
|
604
|
+
property :digital_url, String
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
class Order
|
|
609
|
+
include EasyTalk::Model
|
|
610
|
+
|
|
611
|
+
define_schema do
|
|
612
|
+
property :order_id, String
|
|
613
|
+
# Each item in the array must match exactly one of the product schemas
|
|
614
|
+
property :items, T::Array[T::OneOf[ProductA, ProductB]]
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
This generates a JSON Schema where the `items` array validates each element against `oneOf`:
|
|
620
|
+
|
|
621
|
+
```json
|
|
622
|
+
{
|
|
623
|
+
"properties": {
|
|
624
|
+
"items": {
|
|
625
|
+
"type": "array",
|
|
626
|
+
"items": {
|
|
627
|
+
"oneOf": [
|
|
628
|
+
{ "type": "object", "properties": { "sku": {...}, "weight": {...} }, ... },
|
|
629
|
+
{ "type": "object", "properties": { "sku": {...}, "digital_url": {...} }, ... }
|
|
630
|
+
]
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
You can use any composition type with arrays:
|
|
638
|
+
- `T::Array[T::OneOf[A, B]]` - each item matches exactly one schema
|
|
639
|
+
- `T::Array[T::AnyOf[A, B]]` - each item matches one or more schemas
|
|
640
|
+
- `T::Array[T::AllOf[A, B]]` - each item matches all schemas
|
|
641
|
+
|
|
408
642
|
### Complex Compositions
|
|
409
643
|
You can combine composition types to create complex schemas:
|
|
410
644
|
|
|
411
645
|
```ruby
|
|
412
646
|
class ComplexObject
|
|
413
647
|
include EasyTalk::Model
|
|
414
|
-
|
|
648
|
+
|
|
415
649
|
define_schema do
|
|
416
650
|
property :basic_info, BaseInfo
|
|
417
651
|
property :specific_details, T::OneOf[DetailTypeA, DetailTypeB]
|
|
@@ -477,6 +711,78 @@ user.errors[:age] # => ["must be greater than 21"] # Custom validation
|
|
|
477
711
|
user.errors[:height] # => ["must be greater than 0"] # Overridden validation
|
|
478
712
|
```
|
|
479
713
|
|
|
714
|
+
### Standardized Error Formatting
|
|
715
|
+
|
|
716
|
+
EasyTalk provides multiple output formats for validation errors, making it easy to build consistent API responses.
|
|
717
|
+
|
|
718
|
+
#### Available Formats
|
|
719
|
+
|
|
720
|
+
| Format | Description | Use Case |
|
|
721
|
+
|--------|-------------|----------|
|
|
722
|
+
| `:flat` | Simple array of field/message/code objects | General purpose APIs |
|
|
723
|
+
| `:json_pointer` | Array with JSON Pointer (RFC 6901) paths | JSON Schema validation |
|
|
724
|
+
| `:rfc7807` | RFC 7807 Problem Details format | Standards-compliant APIs |
|
|
725
|
+
| `:jsonapi` | JSON:API specification error format | JSON:API implementations |
|
|
726
|
+
|
|
727
|
+
#### Instance Methods
|
|
728
|
+
|
|
729
|
+
Every EasyTalk model includes convenient methods for error formatting:
|
|
730
|
+
|
|
731
|
+
```ruby
|
|
732
|
+
user = User.new(name: "", email: "invalid")
|
|
733
|
+
user.valid?
|
|
734
|
+
|
|
735
|
+
# Use default format (configurable globally)
|
|
736
|
+
user.validation_errors
|
|
737
|
+
# => [{"field" => "name", "message" => "can't be blank", "code" => "blank"}, ...]
|
|
738
|
+
|
|
739
|
+
# Flat format
|
|
740
|
+
user.validation_errors_flat
|
|
741
|
+
# => [{"field" => "name", "message" => "can't be blank", "code" => "blank"}]
|
|
742
|
+
|
|
743
|
+
# JSON Pointer format
|
|
744
|
+
user.validation_errors_json_pointer
|
|
745
|
+
# => [{"pointer" => "/properties/name", "message" => "can't be blank", "code" => "blank"}]
|
|
746
|
+
|
|
747
|
+
# RFC 7807 Problem Details
|
|
748
|
+
user.validation_errors_rfc7807
|
|
749
|
+
# => {
|
|
750
|
+
# "type" => "about:blank#validation-error",
|
|
751
|
+
# "title" => "Validation Failed",
|
|
752
|
+
# "status" => 422,
|
|
753
|
+
# "detail" => "The request contains invalid parameters",
|
|
754
|
+
# "errors" => [...]
|
|
755
|
+
# }
|
|
756
|
+
|
|
757
|
+
# JSON:API format
|
|
758
|
+
user.validation_errors_jsonapi
|
|
759
|
+
# => {
|
|
760
|
+
# "errors" => [
|
|
761
|
+
# {"status" => "422", "source" => {"pointer" => "/data/attributes/name"}, ...}
|
|
762
|
+
# ]
|
|
763
|
+
# }
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
#### Direct API Usage
|
|
767
|
+
|
|
768
|
+
You can also format errors directly using the `ErrorFormatter` module:
|
|
769
|
+
|
|
770
|
+
```ruby
|
|
771
|
+
EasyTalk::ErrorFormatter.format(user.errors, format: :rfc7807, title: "User Validation Failed")
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
#### Configuration
|
|
775
|
+
|
|
776
|
+
Configure error formatting globally:
|
|
777
|
+
|
|
778
|
+
```ruby
|
|
779
|
+
EasyTalk.configure do |config|
|
|
780
|
+
config.default_error_format = :rfc7807 # Default format for validation_errors
|
|
781
|
+
config.error_type_base_uri = 'https://api.example.com/errors' # Base URI for RFC 7807
|
|
782
|
+
config.include_error_codes = true # Include error codes in output
|
|
783
|
+
end
|
|
784
|
+
```
|
|
785
|
+
|
|
480
786
|
### Model Attributes
|
|
481
787
|
EasyTalk models provide getters and setters for all defined properties:
|
|
482
788
|
|
|
@@ -520,6 +826,57 @@ user.address.street # => "123 Main St"
|
|
|
520
826
|
|
|
521
827
|
## Advanced Features
|
|
522
828
|
|
|
829
|
+
### Schema-Only Mode (EasyTalk::Schema)
|
|
830
|
+
|
|
831
|
+
For scenarios where you need JSON Schema generation without ActiveModel validations, use `EasyTalk::Schema` instead of `EasyTalk::Model`. This is ideal for:
|
|
832
|
+
|
|
833
|
+
- API documentation and OpenAPI spec generation
|
|
834
|
+
- Schema-first design where validation happens elsewhere
|
|
835
|
+
- High-performance scenarios where validation overhead is unwanted
|
|
836
|
+
- Generating schemas for external systems
|
|
837
|
+
|
|
838
|
+
```ruby
|
|
839
|
+
class ApiContract
|
|
840
|
+
include EasyTalk::Schema # Not EasyTalk::Model
|
|
841
|
+
|
|
842
|
+
define_schema do
|
|
843
|
+
title 'API Contract'
|
|
844
|
+
description 'A schema-only contract'
|
|
845
|
+
property :name, String, min_length: 2
|
|
846
|
+
property :age, Integer, minimum: 0
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
# Schema generation works the same
|
|
851
|
+
ApiContract.json_schema
|
|
852
|
+
# => {"type" => "object", "title" => "API Contract", ...}
|
|
853
|
+
|
|
854
|
+
# Instances can be created and accessed
|
|
855
|
+
contract = ApiContract.new(name: 'Test', age: 25)
|
|
856
|
+
contract.name # => 'Test'
|
|
857
|
+
|
|
858
|
+
# But no validation methods are available
|
|
859
|
+
contract.valid? # => NoMethodError
|
|
860
|
+
contract.errors # => NoMethodError
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
#### Key Differences from EasyTalk::Model
|
|
864
|
+
|
|
865
|
+
| Feature | EasyTalk::Model | EasyTalk::Schema |
|
|
866
|
+
|---------|-----------------|------------------|
|
|
867
|
+
| JSON Schema generation | Yes | Yes |
|
|
868
|
+
| Property accessors | Yes | Yes |
|
|
869
|
+
| Nested model instantiation | Yes | Yes |
|
|
870
|
+
| ActiveModel::Validations | Yes | No |
|
|
871
|
+
| `valid?` / `errors` | Yes | No |
|
|
872
|
+
| Validation adapters | Yes | N/A |
|
|
873
|
+
| Error formatting | Yes | No |
|
|
874
|
+
|
|
875
|
+
#### When to Use Each
|
|
876
|
+
|
|
877
|
+
- **Use `EasyTalk::Model`** when you need runtime validation of data (form inputs, API requests, user data)
|
|
878
|
+
- **Use `EasyTalk::Schema`** when you only need the schema definition (documentation, code generation, external validation)
|
|
879
|
+
|
|
523
880
|
### LLM Function Generation
|
|
524
881
|
EasyTalk provides a helper method for generating OpenAI function specifications:
|
|
525
882
|
|
|
@@ -566,16 +923,125 @@ property :age, Integer, enum: ["young", "old"] # Error!
|
|
|
566
923
|
```
|
|
567
924
|
|
|
568
925
|
### Custom Type Builders
|
|
569
|
-
|
|
926
|
+
|
|
927
|
+
EasyTalk provides a type registry that allows you to register custom types with their corresponding schema builders.
|
|
928
|
+
|
|
929
|
+
#### Registering Custom Types
|
|
930
|
+
|
|
931
|
+
Register types in your configuration:
|
|
570
932
|
|
|
571
933
|
```ruby
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
934
|
+
EasyTalk.configure do |config|
|
|
935
|
+
config.register_type(Money, MoneySchemaBuilder)
|
|
936
|
+
end
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
Or register directly with the registry:
|
|
940
|
+
|
|
941
|
+
```ruby
|
|
942
|
+
EasyTalk::Builders::Registry.register(Money, MoneySchemaBuilder)
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
#### Creating a Custom Builder
|
|
946
|
+
|
|
947
|
+
Custom builders extend `BaseBuilder` and implement the schema generation logic:
|
|
948
|
+
|
|
949
|
+
```ruby
|
|
950
|
+
class MoneySchemaBuilder < EasyTalk::Builders::BaseBuilder
|
|
951
|
+
VALID_OPTIONS = {
|
|
952
|
+
currency: { type: T.nilable(String), key: :currency }
|
|
953
|
+
}.freeze
|
|
954
|
+
|
|
955
|
+
def initialize(name, options = {})
|
|
956
|
+
super(name, { type: 'object' }, options, VALID_OPTIONS)
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
def build
|
|
960
|
+
schema.merge(
|
|
961
|
+
properties: {
|
|
962
|
+
amount: { type: 'number' },
|
|
963
|
+
currency: { type: 'string', default: options[:currency] || 'USD' }
|
|
964
|
+
},
|
|
965
|
+
required: %w[amount currency]
|
|
966
|
+
)
|
|
967
|
+
end
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
# Register and use
|
|
971
|
+
EasyTalk::Builders::Registry.register(Money, MoneySchemaBuilder)
|
|
972
|
+
|
|
973
|
+
class Order
|
|
974
|
+
include EasyTalk::Model
|
|
975
|
+
|
|
976
|
+
define_schema do
|
|
977
|
+
property :total, Money, currency: 'EUR'
|
|
978
|
+
end
|
|
979
|
+
end
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
#### Collection Type Builders
|
|
983
|
+
|
|
984
|
+
For types that wrap other types (like arrays), use the `collection: true` option:
|
|
985
|
+
|
|
986
|
+
```ruby
|
|
987
|
+
EasyTalk::Builders::Registry.register(
|
|
988
|
+
CustomCollection,
|
|
989
|
+
CustomCollectionBuilder,
|
|
990
|
+
collection: true
|
|
991
|
+
)
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
Collection builders receive `(name, inner_type, constraints)` instead of `(name, constraints)`.
|
|
995
|
+
|
|
996
|
+
#### Overriding Built-in Types
|
|
997
|
+
|
|
998
|
+
You can override built-in type builders:
|
|
999
|
+
|
|
1000
|
+
```ruby
|
|
1001
|
+
class EnhancedStringBuilder < EasyTalk::Builders::StringBuilder
|
|
1002
|
+
def build
|
|
1003
|
+
result = super
|
|
1004
|
+
result[:custom_extension] = true
|
|
1005
|
+
result
|
|
577
1006
|
end
|
|
578
1007
|
end
|
|
1008
|
+
|
|
1009
|
+
EasyTalk::Builders::Registry.register(String, EnhancedStringBuilder)
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
#### Registry API
|
|
1013
|
+
|
|
1014
|
+
```ruby
|
|
1015
|
+
# Check if a type is registered
|
|
1016
|
+
EasyTalk::Builders::Registry.registered?(Money) # => true
|
|
1017
|
+
|
|
1018
|
+
# List all registered types
|
|
1019
|
+
EasyTalk::Builders::Registry.registered_types
|
|
1020
|
+
|
|
1021
|
+
# Unregister a type
|
|
1022
|
+
EasyTalk::Builders::Registry.unregister(Money)
|
|
1023
|
+
|
|
1024
|
+
# Reset registry to defaults
|
|
1025
|
+
EasyTalk::Builders::Registry.reset!
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
### Type Introspection
|
|
1029
|
+
|
|
1030
|
+
EasyTalk provides a `TypeIntrospection` module for reliable type detection, useful when building custom type builders:
|
|
1031
|
+
|
|
1032
|
+
```ruby
|
|
1033
|
+
# Check type categories
|
|
1034
|
+
EasyTalk::TypeIntrospection.boolean_type?(T::Boolean) # => true
|
|
1035
|
+
EasyTalk::TypeIntrospection.typed_array?(T::Array[String]) # => true
|
|
1036
|
+
EasyTalk::TypeIntrospection.nilable_type?(T.nilable(String)) # => true
|
|
1037
|
+
EasyTalk::TypeIntrospection.primitive_type?(Integer) # => true
|
|
1038
|
+
|
|
1039
|
+
# Get JSON Schema type string
|
|
1040
|
+
EasyTalk::TypeIntrospection.json_schema_type(Integer) # => 'integer'
|
|
1041
|
+
EasyTalk::TypeIntrospection.json_schema_type(Float) # => 'number'
|
|
1042
|
+
|
|
1043
|
+
# Extract inner type from nilable
|
|
1044
|
+
EasyTalk::TypeIntrospection.extract_inner_type(T.nilable(String)) # => String
|
|
579
1045
|
```
|
|
580
1046
|
|
|
581
1047
|
## Configuration
|
|
@@ -588,7 +1054,20 @@ EasyTalk.configure do |config|
|
|
|
588
1054
|
# Schema behavior options
|
|
589
1055
|
config.default_additional_properties = false # Control additional properties on all models
|
|
590
1056
|
config.nilable_is_optional = false # Makes T.nilable properties also optional
|
|
1057
|
+
config.schema_version = :none # JSON Schema version for $schema keyword
|
|
1058
|
+
# Options: :none, :draft202012, :draft201909, :draft7, :draft6, :draft4
|
|
1059
|
+
config.schema_id = nil # Base URI for $id keyword (nil = no $id)
|
|
1060
|
+
config.use_refs = false # Use $ref for nested models instead of inlining
|
|
1061
|
+
config.property_naming_strategy = :camel_case # Options: :identity (default), :snake_case, :camel_case, :pascal_case
|
|
1062
|
+
|
|
1063
|
+
# Validation options
|
|
591
1064
|
config.auto_validations = true # Automatically generate ActiveModel validations
|
|
1065
|
+
config.validation_adapter = :active_model # Validation backend (:active_model, :none, or custom)
|
|
1066
|
+
|
|
1067
|
+
# Error formatting options
|
|
1068
|
+
config.default_error_format = :flat # Default format (:flat, :json_pointer, :rfc7807, :jsonapi)
|
|
1069
|
+
config.error_type_base_uri = 'about:blank' # Base URI for RFC 7807 error types
|
|
1070
|
+
config.include_error_codes = true # Include error codes in formatted output
|
|
592
1071
|
end
|
|
593
1072
|
```
|
|
594
1073
|
|
|
@@ -621,7 +1100,7 @@ You can configure additional properties for individual models:
|
|
|
621
1100
|
```ruby
|
|
622
1101
|
class User
|
|
623
1102
|
include EasyTalk::Model
|
|
624
|
-
|
|
1103
|
+
|
|
625
1104
|
define_schema do
|
|
626
1105
|
title "User"
|
|
627
1106
|
additional_properties true # Allow arbitrary additional properties on this model
|
|
@@ -631,6 +1110,74 @@ class User
|
|
|
631
1110
|
end
|
|
632
1111
|
```
|
|
633
1112
|
|
|
1113
|
+
### Validation Adapters
|
|
1114
|
+
|
|
1115
|
+
EasyTalk uses a pluggable validation adapter system that allows you to customize how validations are generated from schema constraints.
|
|
1116
|
+
|
|
1117
|
+
#### Built-in Adapters
|
|
1118
|
+
|
|
1119
|
+
| Adapter | Description |
|
|
1120
|
+
|---------|-------------|
|
|
1121
|
+
| `:active_model` | Default. Generates ActiveModel validations from schema constraints |
|
|
1122
|
+
| `:none` | Skips validation generation entirely (schema-only mode) |
|
|
1123
|
+
|
|
1124
|
+
#### Global Adapter Configuration
|
|
1125
|
+
|
|
1126
|
+
```ruby
|
|
1127
|
+
EasyTalk.configure do |config|
|
|
1128
|
+
config.validation_adapter = :none # Disable all automatic validations
|
|
1129
|
+
end
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
#### Per-Model Validation Control
|
|
1133
|
+
|
|
1134
|
+
Disable validations for a specific model while keeping them enabled globally:
|
|
1135
|
+
|
|
1136
|
+
```ruby
|
|
1137
|
+
class LegacyModel
|
|
1138
|
+
include EasyTalk::Model
|
|
1139
|
+
|
|
1140
|
+
define_schema(validations: false) do
|
|
1141
|
+
property :data, String, min_length: 1 # No validation generated
|
|
1142
|
+
end
|
|
1143
|
+
end
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
#### Per-Property Validation Control
|
|
1147
|
+
|
|
1148
|
+
Disable validation for specific properties:
|
|
1149
|
+
|
|
1150
|
+
```ruby
|
|
1151
|
+
class User
|
|
1152
|
+
include EasyTalk::Model
|
|
1153
|
+
|
|
1154
|
+
define_schema do
|
|
1155
|
+
property :name, String, min_length: 2 # Validation generated
|
|
1156
|
+
property :legacy_field, String, validate: false # No validation for this property
|
|
1157
|
+
end
|
|
1158
|
+
end
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
#### Custom Validation Adapters
|
|
1162
|
+
|
|
1163
|
+
Create custom adapters for specialized validation needs:
|
|
1164
|
+
|
|
1165
|
+
```ruby
|
|
1166
|
+
class MyCustomAdapter < EasyTalk::ValidationAdapters::Base
|
|
1167
|
+
def self.build_validations(klass, property_name, type, constraints)
|
|
1168
|
+
# Custom validation logic
|
|
1169
|
+
end
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
# Register the adapter
|
|
1173
|
+
EasyTalk::ValidationAdapters::Registry.register(:custom, MyCustomAdapter)
|
|
1174
|
+
|
|
1175
|
+
# Use it globally
|
|
1176
|
+
EasyTalk.configure do |config|
|
|
1177
|
+
config.validation_adapter = :custom
|
|
1178
|
+
end
|
|
1179
|
+
```
|
|
1180
|
+
|
|
634
1181
|
## Examples
|
|
635
1182
|
|
|
636
1183
|
### User Registration (with Auto-Validations)
|
|
@@ -871,18 +1418,18 @@ property :status, String, enum: ["active", "inactive", "pending"]
|
|
|
871
1418
|
9. **Use nullable_optional_property** for fields that can be omitted or null
|
|
872
1419
|
10. **Document breaking changes** when updating schema definitions
|
|
873
1420
|
|
|
874
|
-
|
|
1421
|
+
## Nullable vs Optional Properties in EasyTalk
|
|
875
1422
|
|
|
876
1423
|
One of the most important distinctions when defining schemas is understanding the difference between **nullable** properties and **optional** properties. This guide explains these concepts and how to use them effectively in EasyTalk.
|
|
877
1424
|
|
|
878
|
-
|
|
1425
|
+
### Key Concepts
|
|
879
1426
|
|
|
880
1427
|
| Concept | Description | JSON Schema Effect | EasyTalk Syntax |
|
|
881
1428
|
|---------|-------------|-------------------|-----------------|
|
|
882
1429
|
| **Nullable** | Property can have a `null` value | Adds `"null"` to the type array | `T.nilable(Type)` |
|
|
883
1430
|
| **Optional** | Property doesn't have to exist | Omits property from `"required"` array | `optional: true` constraint |
|
|
884
1431
|
|
|
885
|
-
|
|
1432
|
+
### Nullable Properties
|
|
886
1433
|
|
|
887
1434
|
A **nullable** property can contain a `null` value, but the property itself must still be present in the object:
|
|
888
1435
|
|
|
@@ -908,7 +1455,7 @@ In this case, the following data would be valid:
|
|
|
908
1455
|
But this would be invalid:
|
|
909
1456
|
- `{ }` (missing the age property entirely)
|
|
910
1457
|
|
|
911
|
-
|
|
1458
|
+
### Optional Properties
|
|
912
1459
|
|
|
913
1460
|
An **optional** property doesn't have to be present in the object at all:
|
|
914
1461
|
|
|
@@ -934,7 +1481,7 @@ In this case, the following data would be valid:
|
|
|
934
1481
|
But this would be invalid:
|
|
935
1482
|
- `{ "nickname": null }` (null is not allowed because the property isn't nullable)
|
|
936
1483
|
|
|
937
|
-
|
|
1484
|
+
### Nullable AND Optional Properties
|
|
938
1485
|
|
|
939
1486
|
For properties that should be both nullable and optional (can be omitted or null), you need to combine both approaches:
|
|
940
1487
|
|
|
@@ -961,7 +1508,7 @@ nullable_optional_property :bio, String
|
|
|
961
1508
|
|
|
962
1509
|
Which is equivalent to the above.
|
|
963
1510
|
|
|
964
|
-
|
|
1511
|
+
### Configuration Options
|
|
965
1512
|
|
|
966
1513
|
By default, nullable properties are still required. You can change this global behavior:
|
|
967
1514
|
|
|
@@ -973,9 +1520,9 @@ end
|
|
|
973
1520
|
|
|
974
1521
|
With this configuration, any property defined with `T.nilable(Type)` will be treated as both nullable and optional.
|
|
975
1522
|
|
|
976
|
-
|
|
1523
|
+
### Practical Examples
|
|
977
1524
|
|
|
978
|
-
|
|
1525
|
+
#### User Profile Schema
|
|
979
1526
|
|
|
980
1527
|
```ruby
|
|
981
1528
|
class UserProfile
|
|
@@ -1004,17 +1551,17 @@ This creates clear expectations for data validation:
|
|
|
1004
1551
|
- `email` doesn't have to be present, but if it is, it cannot be null
|
|
1005
1552
|
- `bio` doesn't have to be present, and if it is, it can be null
|
|
1006
1553
|
|
|
1007
|
-
|
|
1554
|
+
### Common Gotchas
|
|
1008
1555
|
|
|
1009
|
-
|
|
1556
|
+
#### Misconception: Nullable Implies Optional
|
|
1010
1557
|
|
|
1011
1558
|
A common mistake is assuming that `T.nilable(Type)` makes a property optional. By default, it only allows the property to have a null value - the property itself is still required to exist in the object.
|
|
1012
1559
|
|
|
1013
|
-
|
|
1560
|
+
#### Misconception: Optional Properties Accept Null
|
|
1014
1561
|
|
|
1015
1562
|
An optional property (defined with `optional: true`) can be omitted entirely, but if it is present, it must conform to its type constraint. If you want to allow null values, you must also make it nullable with `T.nilable(Type)`.
|
|
1016
1563
|
|
|
1017
|
-
|
|
1564
|
+
### Migration from Earlier Versions
|
|
1018
1565
|
|
|
1019
1566
|
If you're upgrading from EasyTalk version 1.0.1 or earlier, be aware that the handling of nullable vs optional properties has been improved for clarity.
|
|
1020
1567
|
|
|
@@ -1028,14 +1575,14 @@ end
|
|
|
1028
1575
|
|
|
1029
1576
|
We recommend updating your schema definitions to explicitly declare which properties are optional using the `optional: true` constraint, as this makes your intent clearer.
|
|
1030
1577
|
|
|
1031
|
-
|
|
1578
|
+
### Best Practices
|
|
1032
1579
|
|
|
1033
1580
|
1. **Be explicit about intent**: Always clarify whether properties should be nullable, optional, or both
|
|
1034
1581
|
2. **Use the helper method**: For properties that are both nullable and optional, use `nullable_optional_property`
|
|
1035
1582
|
3. **Document expectations**: Use comments to clarify validation requirements for complex schemas
|
|
1036
1583
|
4. **Consider validation implications**: Remember that ActiveModel validations operate independently of the schema definition
|
|
1037
1584
|
|
|
1038
|
-
|
|
1585
|
+
### JSON Schema Comparison
|
|
1039
1586
|
|
|
1040
1587
|
| EasyTalk Definition | Required | Nullable | JSON Schema Equivalent |
|
|
1041
1588
|
|--------------------|----------|----------|------------------------|
|
|
@@ -1168,18 +1715,699 @@ bundle exec rubocop
|
|
|
1168
1715
|
### Contributing Guidelines
|
|
1169
1716
|
Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/easy_talk.
|
|
1170
1717
|
|
|
1171
|
-
## JSON Schema
|
|
1718
|
+
## JSON Schema Version (`$schema` Keyword)
|
|
1172
1719
|
|
|
1173
|
-
|
|
1174
|
-
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.
|
|
1720
|
+
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.
|
|
1175
1721
|
|
|
1176
|
-
###
|
|
1177
|
-
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.
|
|
1722
|
+
### Why Use `$schema`?
|
|
1178
1723
|
|
|
1179
|
-
|
|
1180
|
-
-
|
|
1181
|
-
-
|
|
1182
|
-
-
|
|
1724
|
+
The `$schema` keyword:
|
|
1725
|
+
- Declares the JSON Schema version your schema is written against
|
|
1726
|
+
- Helps validators understand which specification to use
|
|
1727
|
+
- Enables tooling to provide appropriate validation and autocomplete
|
|
1728
|
+
- Documents the schema dialect for consumers of your API
|
|
1729
|
+
|
|
1730
|
+
### Supported Draft Versions
|
|
1731
|
+
|
|
1732
|
+
EasyTalk supports the following JSON Schema draft versions:
|
|
1733
|
+
|
|
1734
|
+
| Symbol | JSON Schema Version | URI |
|
|
1735
|
+
|--------|---------------------|-----|
|
|
1736
|
+
| `:draft202012` | Draft 2020-12 (latest) | `https://json-schema.org/draft/2020-12/schema` |
|
|
1737
|
+
| `:draft201909` | Draft 2019-09 | `https://json-schema.org/draft/2019-09/schema` |
|
|
1738
|
+
| `:draft7` | Draft-07 | `http://json-schema.org/draft-07/schema#` |
|
|
1739
|
+
| `:draft6` | Draft-06 | `http://json-schema.org/draft-06/schema#` |
|
|
1740
|
+
| `:draft4` | Draft-04 | `http://json-schema.org/draft-04/schema#` |
|
|
1741
|
+
| `:none` | No `$schema` output (default) | N/A |
|
|
1742
|
+
|
|
1743
|
+
### Global Configuration
|
|
1744
|
+
|
|
1745
|
+
Configure the schema version globally to apply to all models:
|
|
1746
|
+
|
|
1747
|
+
```ruby
|
|
1748
|
+
EasyTalk.configure do |config|
|
|
1749
|
+
config.schema_version = :draft202012 # Use JSON Schema Draft 2020-12
|
|
1750
|
+
end
|
|
1751
|
+
```
|
|
1752
|
+
|
|
1753
|
+
With this configuration, all models will include `$schema` in their output:
|
|
1754
|
+
|
|
1755
|
+
```ruby
|
|
1756
|
+
class User
|
|
1757
|
+
include EasyTalk::Model
|
|
1758
|
+
|
|
1759
|
+
define_schema do
|
|
1760
|
+
property :name, String
|
|
1761
|
+
end
|
|
1762
|
+
end
|
|
1763
|
+
|
|
1764
|
+
User.json_schema
|
|
1765
|
+
# => {
|
|
1766
|
+
# "$schema" => "https://json-schema.org/draft/2020-12/schema",
|
|
1767
|
+
# "type" => "object",
|
|
1768
|
+
# "properties" => { "name" => { "type" => "string" } },
|
|
1769
|
+
# "required" => ["name"],
|
|
1770
|
+
# "additionalProperties" => false
|
|
1771
|
+
# }
|
|
1772
|
+
```
|
|
1773
|
+
|
|
1774
|
+
### Per-Model Configuration
|
|
1775
|
+
|
|
1776
|
+
Override the global setting for individual models using the `schema_version` keyword in the schema definition:
|
|
1777
|
+
|
|
1778
|
+
```ruby
|
|
1779
|
+
class LegacyModel
|
|
1780
|
+
include EasyTalk::Model
|
|
1781
|
+
|
|
1782
|
+
define_schema do
|
|
1783
|
+
schema_version :draft7 # Use Draft-07 for this specific model
|
|
1784
|
+
property :name, String
|
|
1785
|
+
end
|
|
1786
|
+
end
|
|
1787
|
+
|
|
1788
|
+
LegacyModel.json_schema
|
|
1789
|
+
# => {
|
|
1790
|
+
# "$schema" => "http://json-schema.org/draft-07/schema#",
|
|
1791
|
+
# "type" => "object",
|
|
1792
|
+
# ...
|
|
1793
|
+
# }
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
### Disabling `$schema` for Specific Models
|
|
1797
|
+
|
|
1798
|
+
If you have a global schema version configured but want to exclude `$schema` from a specific model, use `:none`:
|
|
1799
|
+
|
|
1800
|
+
```ruby
|
|
1801
|
+
EasyTalk.configure do |config|
|
|
1802
|
+
config.schema_version = :draft202012 # Global default
|
|
1803
|
+
end
|
|
1804
|
+
|
|
1805
|
+
class InternalModel
|
|
1806
|
+
include EasyTalk::Model
|
|
1807
|
+
|
|
1808
|
+
define_schema do
|
|
1809
|
+
schema_version :none # No $schema for this model
|
|
1810
|
+
property :data, String
|
|
1811
|
+
end
|
|
1812
|
+
end
|
|
1813
|
+
|
|
1814
|
+
InternalModel.json_schema
|
|
1815
|
+
# => {
|
|
1816
|
+
# "type" => "object",
|
|
1817
|
+
# "properties" => { "data" => { "type" => "string" } },
|
|
1818
|
+
# ...
|
|
1819
|
+
# }
|
|
1820
|
+
# Note: No "$schema" key present
|
|
1821
|
+
```
|
|
1822
|
+
|
|
1823
|
+
### Custom Schema URIs
|
|
1824
|
+
|
|
1825
|
+
You can also specify a custom URI if you're using a custom meta-schema or a different schema registry:
|
|
1826
|
+
|
|
1827
|
+
```ruby
|
|
1828
|
+
class CustomModel
|
|
1829
|
+
include EasyTalk::Model
|
|
1830
|
+
|
|
1831
|
+
define_schema do
|
|
1832
|
+
schema_version 'https://my-company.com/schemas/v1/meta-schema.json'
|
|
1833
|
+
property :id, String
|
|
1834
|
+
end
|
|
1835
|
+
end
|
|
1836
|
+
```
|
|
1837
|
+
|
|
1838
|
+
### Nested Models
|
|
1839
|
+
|
|
1840
|
+
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`:
|
|
1841
|
+
|
|
1842
|
+
```ruby
|
|
1843
|
+
EasyTalk.configure do |config|
|
|
1844
|
+
config.schema_version = :draft202012
|
|
1845
|
+
end
|
|
1846
|
+
|
|
1847
|
+
class Address
|
|
1848
|
+
include EasyTalk::Model
|
|
1849
|
+
define_schema do
|
|
1850
|
+
property :city, String
|
|
1851
|
+
end
|
|
1852
|
+
end
|
|
1853
|
+
|
|
1854
|
+
class User
|
|
1855
|
+
include EasyTalk::Model
|
|
1856
|
+
define_schema do
|
|
1857
|
+
property :name, String
|
|
1858
|
+
property :address, Address
|
|
1859
|
+
end
|
|
1860
|
+
end
|
|
1861
|
+
|
|
1862
|
+
User.json_schema
|
|
1863
|
+
# => {
|
|
1864
|
+
# "$schema" => "https://json-schema.org/draft/2020-12/schema", # Only at root
|
|
1865
|
+
# "type" => "object",
|
|
1866
|
+
# "properties" => {
|
|
1867
|
+
# "name" => { "type" => "string" },
|
|
1868
|
+
# "address" => {
|
|
1869
|
+
# "type" => "object", # No $schema here
|
|
1870
|
+
# "properties" => { "city" => { "type" => "string" } },
|
|
1871
|
+
# ...
|
|
1872
|
+
# }
|
|
1873
|
+
# },
|
|
1874
|
+
# ...
|
|
1875
|
+
# }
|
|
1876
|
+
```
|
|
1877
|
+
|
|
1878
|
+
### Default Behavior
|
|
1879
|
+
|
|
1880
|
+
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.
|
|
1881
|
+
|
|
1882
|
+
### Best Practices
|
|
1883
|
+
|
|
1884
|
+
1. **Choose a version appropriate for your validators**: If you're using a specific JSON Schema validator, check which drafts it supports.
|
|
1885
|
+
|
|
1886
|
+
2. **Use Draft 2020-12 for new projects**: It's the latest stable version with the most features.
|
|
1887
|
+
|
|
1888
|
+
3. **Be consistent**: Use global configuration for consistency across your application, and only override per-model when necessary.
|
|
1889
|
+
|
|
1890
|
+
4. **Consider your consumers**: If your schemas are consumed by external systems, ensure they support the draft version you're using.
|
|
1891
|
+
|
|
1892
|
+
## Schema Identifier (`$id` Keyword)
|
|
1893
|
+
|
|
1894
|
+
The `$id` keyword provides a unique identifier for your JSON Schema document. EasyTalk supports configuring this at both the global and per-model level.
|
|
1895
|
+
|
|
1896
|
+
### Why Use `$id`?
|
|
1897
|
+
|
|
1898
|
+
The `$id` keyword:
|
|
1899
|
+
- Establishes a unique URI identifier for the schema
|
|
1900
|
+
- Enables referencing schemas from other documents via `$ref`
|
|
1901
|
+
- Provides a base URI for resolving relative references within the schema
|
|
1902
|
+
- Documents the canonical location of the schema
|
|
1903
|
+
|
|
1904
|
+
### Global Configuration
|
|
1905
|
+
|
|
1906
|
+
Configure the schema ID globally to apply to all models:
|
|
1907
|
+
|
|
1908
|
+
```ruby
|
|
1909
|
+
EasyTalk.configure do |config|
|
|
1910
|
+
config.schema_id = 'https://example.com/schemas/base.json'
|
|
1911
|
+
end
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1914
|
+
With this configuration, all models will include `$id` in their output:
|
|
1915
|
+
|
|
1916
|
+
```ruby
|
|
1917
|
+
class User
|
|
1918
|
+
include EasyTalk::Model
|
|
1919
|
+
|
|
1920
|
+
define_schema do
|
|
1921
|
+
property :name, String
|
|
1922
|
+
end
|
|
1923
|
+
end
|
|
1924
|
+
|
|
1925
|
+
User.json_schema
|
|
1926
|
+
# => {
|
|
1927
|
+
# "$id" => "https://example.com/schemas/base.json",
|
|
1928
|
+
# "type" => "object",
|
|
1929
|
+
# "properties" => { "name" => { "type" => "string" } },
|
|
1930
|
+
# "required" => ["name"],
|
|
1931
|
+
# "additionalProperties" => false
|
|
1932
|
+
# }
|
|
1933
|
+
```
|
|
1934
|
+
|
|
1935
|
+
### Per-Model Configuration
|
|
1936
|
+
|
|
1937
|
+
Override the global setting for individual models using the `schema_id` keyword in the schema definition:
|
|
1938
|
+
|
|
1939
|
+
```ruby
|
|
1940
|
+
class User
|
|
1941
|
+
include EasyTalk::Model
|
|
1942
|
+
|
|
1943
|
+
define_schema do
|
|
1944
|
+
schema_id 'https://example.com/schemas/user.schema.json'
|
|
1945
|
+
property :name, String
|
|
1946
|
+
property :email, String
|
|
1947
|
+
end
|
|
1948
|
+
end
|
|
1949
|
+
|
|
1950
|
+
User.json_schema
|
|
1951
|
+
# => {
|
|
1952
|
+
# "$id" => "https://example.com/schemas/user.schema.json",
|
|
1953
|
+
# "type" => "object",
|
|
1954
|
+
# ...
|
|
1955
|
+
# }
|
|
1956
|
+
```
|
|
1957
|
+
|
|
1958
|
+
### Disabling `$id` for Specific Models
|
|
1959
|
+
|
|
1960
|
+
If you have a global schema ID configured but want to exclude `$id` from a specific model, use `:none`:
|
|
1961
|
+
|
|
1962
|
+
```ruby
|
|
1963
|
+
EasyTalk.configure do |config|
|
|
1964
|
+
config.schema_id = 'https://example.com/schemas/default.json'
|
|
1965
|
+
end
|
|
1966
|
+
|
|
1967
|
+
class InternalModel
|
|
1968
|
+
include EasyTalk::Model
|
|
1969
|
+
|
|
1970
|
+
define_schema do
|
|
1971
|
+
schema_id :none # No $id for this model
|
|
1972
|
+
property :data, String
|
|
1973
|
+
end
|
|
1974
|
+
end
|
|
1975
|
+
|
|
1976
|
+
InternalModel.json_schema
|
|
1977
|
+
# => {
|
|
1978
|
+
# "type" => "object",
|
|
1979
|
+
# "properties" => { "data" => { "type" => "string" } },
|
|
1980
|
+
# ...
|
|
1981
|
+
# }
|
|
1982
|
+
# Note: No "$id" key present
|
|
1983
|
+
```
|
|
1984
|
+
|
|
1985
|
+
### Combining `$schema` and `$id`
|
|
1986
|
+
|
|
1987
|
+
When both `$schema` and `$id` are configured, they appear in the standard order (`$schema` first, then `$id`):
|
|
1988
|
+
|
|
1989
|
+
```ruby
|
|
1990
|
+
class Product
|
|
1991
|
+
include EasyTalk::Model
|
|
1992
|
+
|
|
1993
|
+
define_schema do
|
|
1994
|
+
schema_version :draft202012
|
|
1995
|
+
schema_id 'https://example.com/schemas/product.schema.json'
|
|
1996
|
+
property :name, String
|
|
1997
|
+
property :price, Float
|
|
1998
|
+
end
|
|
1999
|
+
end
|
|
2000
|
+
|
|
2001
|
+
Product.json_schema
|
|
2002
|
+
# => {
|
|
2003
|
+
# "$schema" => "https://json-schema.org/draft/2020-12/schema",
|
|
2004
|
+
# "$id" => "https://example.com/schemas/product.schema.json",
|
|
2005
|
+
# "type" => "object",
|
|
2006
|
+
# ...
|
|
2007
|
+
# }
|
|
2008
|
+
```
|
|
2009
|
+
|
|
2010
|
+
### Nested Models
|
|
2011
|
+
|
|
2012
|
+
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`:
|
|
2013
|
+
|
|
2014
|
+
```ruby
|
|
2015
|
+
EasyTalk.configure do |config|
|
|
2016
|
+
config.schema_id = 'https://example.com/schemas/user.json'
|
|
2017
|
+
end
|
|
2018
|
+
|
|
2019
|
+
class Address
|
|
2020
|
+
include EasyTalk::Model
|
|
2021
|
+
define_schema do
|
|
2022
|
+
property :city, String
|
|
2023
|
+
end
|
|
2024
|
+
end
|
|
2025
|
+
|
|
2026
|
+
class User
|
|
2027
|
+
include EasyTalk::Model
|
|
2028
|
+
define_schema do
|
|
2029
|
+
property :name, String
|
|
2030
|
+
property :address, Address
|
|
2031
|
+
end
|
|
2032
|
+
end
|
|
2033
|
+
|
|
2034
|
+
User.json_schema
|
|
2035
|
+
# => {
|
|
2036
|
+
# "$id" => "https://example.com/schemas/user.json", # Only at root
|
|
2037
|
+
# "type" => "object",
|
|
2038
|
+
# "properties" => {
|
|
2039
|
+
# "name" => { "type" => "string" },
|
|
2040
|
+
# "address" => {
|
|
2041
|
+
# "type" => "object", # No $id here
|
|
2042
|
+
# "properties" => { "city" => { "type" => "string" } },
|
|
2043
|
+
# ...
|
|
2044
|
+
# }
|
|
2045
|
+
# },
|
|
2046
|
+
# ...
|
|
2047
|
+
# }
|
|
2048
|
+
```
|
|
2049
|
+
|
|
2050
|
+
### URI Formats
|
|
2051
|
+
|
|
2052
|
+
The `$id` accepts various URI formats:
|
|
2053
|
+
|
|
2054
|
+
```ruby
|
|
2055
|
+
# Absolute URI (recommended for published schemas)
|
|
2056
|
+
schema_id 'https://example.com/schemas/user.schema.json'
|
|
2057
|
+
|
|
2058
|
+
# Relative URI
|
|
2059
|
+
schema_id 'user.schema.json'
|
|
2060
|
+
|
|
2061
|
+
# URN format
|
|
2062
|
+
schema_id 'urn:example:user-schema'
|
|
2063
|
+
```
|
|
2064
|
+
|
|
2065
|
+
### Default Behavior
|
|
2066
|
+
|
|
2067
|
+
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.
|
|
2068
|
+
|
|
2069
|
+
### Best Practices
|
|
2070
|
+
|
|
2071
|
+
1. **Use absolute URIs for published schemas**: This ensures global uniqueness and enables external references.
|
|
2072
|
+
|
|
2073
|
+
2. **Follow a consistent naming convention**: For example, `https://yourdomain.com/schemas/{model-name}.schema.json`.
|
|
2074
|
+
|
|
2075
|
+
3. **Keep IDs stable**: Once published, avoid changing schema IDs as external systems may reference them.
|
|
2076
|
+
|
|
2077
|
+
4. **Combine with `$schema`**: When publishing schemas, include both `$schema` (for validation) and `$id` (for identification).
|
|
2078
|
+
|
|
2079
|
+
## Schema References (`$ref` and `$defs`)
|
|
2080
|
+
|
|
2081
|
+
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.
|
|
2082
|
+
|
|
2083
|
+
### Why Use `$ref`?
|
|
2084
|
+
|
|
2085
|
+
The `$ref` keyword:
|
|
2086
|
+
- Reduces schema duplication when the same model appears multiple times
|
|
2087
|
+
- Produces cleaner, more organized schemas
|
|
2088
|
+
- Improves schema readability and maintainability
|
|
2089
|
+
- Aligns with JSON Schema best practices for reusable definitions
|
|
2090
|
+
|
|
2091
|
+
### Default Behavior (Inline Schemas)
|
|
2092
|
+
|
|
2093
|
+
By default, EasyTalk inlines nested model schemas directly:
|
|
2094
|
+
|
|
2095
|
+
```ruby
|
|
2096
|
+
class Address
|
|
2097
|
+
include EasyTalk::Model
|
|
2098
|
+
define_schema do
|
|
2099
|
+
property :street, String
|
|
2100
|
+
property :city, String
|
|
2101
|
+
end
|
|
2102
|
+
end
|
|
2103
|
+
|
|
2104
|
+
class Person
|
|
2105
|
+
include EasyTalk::Model
|
|
2106
|
+
define_schema do
|
|
2107
|
+
property :name, String
|
|
2108
|
+
property :address, Address
|
|
2109
|
+
end
|
|
2110
|
+
end
|
|
2111
|
+
|
|
2112
|
+
Person.json_schema
|
|
2113
|
+
# => {
|
|
2114
|
+
# "type" => "object",
|
|
2115
|
+
# "properties" => {
|
|
2116
|
+
# "name" => { "type" => "string" },
|
|
2117
|
+
# "address" => {
|
|
2118
|
+
# "type" => "object",
|
|
2119
|
+
# "properties" => {
|
|
2120
|
+
# "street" => { "type" => "string" },
|
|
2121
|
+
# "city" => { "type" => "string" }
|
|
2122
|
+
# },
|
|
2123
|
+
# ...
|
|
2124
|
+
# }
|
|
2125
|
+
# },
|
|
2126
|
+
# ...
|
|
2127
|
+
# }
|
|
2128
|
+
```
|
|
2129
|
+
|
|
2130
|
+
### Enabling `$ref` References
|
|
2131
|
+
|
|
2132
|
+
#### Global Configuration
|
|
2133
|
+
|
|
2134
|
+
Enable `$ref` generation for all nested models:
|
|
2135
|
+
|
|
2136
|
+
```ruby
|
|
2137
|
+
EasyTalk.configure do |config|
|
|
2138
|
+
config.use_refs = true
|
|
2139
|
+
end
|
|
2140
|
+
```
|
|
2141
|
+
|
|
2142
|
+
With this configuration, nested models are referenced via `$ref` and their definitions are placed in `$defs`:
|
|
2143
|
+
|
|
2144
|
+
```ruby
|
|
2145
|
+
Person.json_schema
|
|
2146
|
+
# => {
|
|
2147
|
+
# "type" => "object",
|
|
2148
|
+
# "properties" => {
|
|
2149
|
+
# "name" => { "type" => "string" },
|
|
2150
|
+
# "address" => { "$ref" => "#/$defs/Address" }
|
|
2151
|
+
# },
|
|
2152
|
+
# "$defs" => {
|
|
2153
|
+
# "Address" => {
|
|
2154
|
+
# "type" => "object",
|
|
2155
|
+
# "properties" => {
|
|
2156
|
+
# "street" => { "type" => "string" },
|
|
2157
|
+
# "city" => { "type" => "string" }
|
|
2158
|
+
# },
|
|
2159
|
+
# ...
|
|
2160
|
+
# }
|
|
2161
|
+
# },
|
|
2162
|
+
# ...
|
|
2163
|
+
# }
|
|
2164
|
+
```
|
|
2165
|
+
|
|
2166
|
+
#### Per-Property Configuration
|
|
2167
|
+
|
|
2168
|
+
You can also enable `$ref` for specific properties using the `ref: true` constraint:
|
|
2169
|
+
|
|
2170
|
+
```ruby
|
|
2171
|
+
class Person
|
|
2172
|
+
include EasyTalk::Model
|
|
2173
|
+
define_schema do
|
|
2174
|
+
property :name, String
|
|
2175
|
+
property :address, Address, ref: true # Use $ref for this property
|
|
2176
|
+
end
|
|
2177
|
+
end
|
|
2178
|
+
```
|
|
2179
|
+
|
|
2180
|
+
Or disable `$ref` for specific properties when it's enabled globally:
|
|
2181
|
+
|
|
2182
|
+
```ruby
|
|
2183
|
+
EasyTalk.configure do |config|
|
|
2184
|
+
config.use_refs = true
|
|
2185
|
+
end
|
|
2186
|
+
|
|
2187
|
+
class Person
|
|
2188
|
+
include EasyTalk::Model
|
|
2189
|
+
define_schema do
|
|
2190
|
+
property :name, String
|
|
2191
|
+
property :address, Address, ref: false # Inline this property despite global setting
|
|
2192
|
+
end
|
|
2193
|
+
end
|
|
2194
|
+
```
|
|
2195
|
+
|
|
2196
|
+
### Arrays of Models
|
|
2197
|
+
|
|
2198
|
+
When using `$ref` with arrays of models, the `$ref` applies to the array items:
|
|
2199
|
+
|
|
2200
|
+
```ruby
|
|
2201
|
+
EasyTalk.configure do |config|
|
|
2202
|
+
config.use_refs = true
|
|
2203
|
+
end
|
|
2204
|
+
|
|
2205
|
+
class Company
|
|
2206
|
+
include EasyTalk::Model
|
|
2207
|
+
define_schema do
|
|
2208
|
+
property :name, String
|
|
2209
|
+
property :addresses, T::Array[Address]
|
|
2210
|
+
end
|
|
2211
|
+
end
|
|
2212
|
+
|
|
2213
|
+
Company.json_schema
|
|
2214
|
+
# => {
|
|
2215
|
+
# "type" => "object",
|
|
2216
|
+
# "properties" => {
|
|
2217
|
+
# "name" => { "type" => "string" },
|
|
2218
|
+
# "addresses" => {
|
|
2219
|
+
# "type" => "array",
|
|
2220
|
+
# "items" => { "$ref" => "#/$defs/Address" }
|
|
2221
|
+
# }
|
|
2222
|
+
# },
|
|
2223
|
+
# "$defs" => {
|
|
2224
|
+
# "Address" => { ... }
|
|
2225
|
+
# },
|
|
2226
|
+
# ...
|
|
2227
|
+
# }
|
|
2228
|
+
```
|
|
2229
|
+
|
|
2230
|
+
You can also use the per-property `ref` constraint with arrays:
|
|
2231
|
+
|
|
2232
|
+
```ruby
|
|
2233
|
+
property :addresses, T::Array[Address], ref: true
|
|
2234
|
+
```
|
|
2235
|
+
|
|
2236
|
+
### Nilable Models with `$ref`
|
|
2237
|
+
|
|
2238
|
+
When using `$ref` with nilable model types, EasyTalk uses `anyOf` to combine the reference with the null type:
|
|
2239
|
+
|
|
2240
|
+
```ruby
|
|
2241
|
+
EasyTalk.configure do |config|
|
|
2242
|
+
config.use_refs = true
|
|
2243
|
+
end
|
|
2244
|
+
|
|
2245
|
+
class Person
|
|
2246
|
+
include EasyTalk::Model
|
|
2247
|
+
define_schema do
|
|
2248
|
+
property :name, String
|
|
2249
|
+
property :address, T.nilable(Address)
|
|
2250
|
+
end
|
|
2251
|
+
end
|
|
2252
|
+
|
|
2253
|
+
Person.json_schema
|
|
2254
|
+
# => {
|
|
2255
|
+
# "type" => "object",
|
|
2256
|
+
# "properties" => {
|
|
2257
|
+
# "name" => { "type" => "string" },
|
|
2258
|
+
# "address" => {
|
|
2259
|
+
# "anyOf" => [
|
|
2260
|
+
# { "$ref" => "#/$defs/Address" },
|
|
2261
|
+
# { "type" => "null" }
|
|
2262
|
+
# ]
|
|
2263
|
+
# }
|
|
2264
|
+
# },
|
|
2265
|
+
# "$defs" => {
|
|
2266
|
+
# "Address" => { ... }
|
|
2267
|
+
# },
|
|
2268
|
+
# ...
|
|
2269
|
+
# }
|
|
2270
|
+
```
|
|
2271
|
+
|
|
2272
|
+
### Multiple References to the Same Model
|
|
2273
|
+
|
|
2274
|
+
When the same model is used multiple times, it only appears once in `$defs`:
|
|
2275
|
+
|
|
2276
|
+
```ruby
|
|
2277
|
+
class Person
|
|
2278
|
+
include EasyTalk::Model
|
|
2279
|
+
define_schema do
|
|
2280
|
+
property :name, String
|
|
2281
|
+
property :home_address, Address, ref: true
|
|
2282
|
+
property :work_address, Address, ref: true
|
|
2283
|
+
property :shipping_addresses, T::Array[Address], ref: true
|
|
2284
|
+
end
|
|
2285
|
+
end
|
|
2286
|
+
|
|
2287
|
+
Person.json_schema
|
|
2288
|
+
# => {
|
|
2289
|
+
# "type" => "object",
|
|
2290
|
+
# "properties" => {
|
|
2291
|
+
# "name" => { "type" => "string" },
|
|
2292
|
+
# "home_address" => { "$ref" => "#/$defs/Address" },
|
|
2293
|
+
# "work_address" => { "$ref" => "#/$defs/Address" },
|
|
2294
|
+
# "shipping_addresses" => {
|
|
2295
|
+
# "type" => "array",
|
|
2296
|
+
# "items" => { "$ref" => "#/$defs/Address" }
|
|
2297
|
+
# }
|
|
2298
|
+
# },
|
|
2299
|
+
# "$defs" => {
|
|
2300
|
+
# "Address" => { ... } # Only defined once
|
|
2301
|
+
# },
|
|
2302
|
+
# ...
|
|
2303
|
+
# }
|
|
2304
|
+
```
|
|
2305
|
+
|
|
2306
|
+
### Combining `$ref` with Other Constraints
|
|
2307
|
+
|
|
2308
|
+
You can add additional constraints alongside `$ref`:
|
|
2309
|
+
|
|
2310
|
+
```ruby
|
|
2311
|
+
class Person
|
|
2312
|
+
include EasyTalk::Model
|
|
2313
|
+
define_schema do
|
|
2314
|
+
property :address, Address, ref: true, description: "Primary address", title: "Main Address"
|
|
2315
|
+
end
|
|
2316
|
+
end
|
|
2317
|
+
|
|
2318
|
+
Person.json_schema["properties"]["address"]
|
|
2319
|
+
# => {
|
|
2320
|
+
# "$ref" => "#/$defs/Address",
|
|
2321
|
+
# "description" => "Primary address",
|
|
2322
|
+
# "title" => "Main Address"
|
|
2323
|
+
# }
|
|
2324
|
+
```
|
|
2325
|
+
|
|
2326
|
+
### Interaction with `compose`
|
|
2327
|
+
|
|
2328
|
+
When using `compose` with `T::AllOf`, `T::AnyOf`, or `T::OneOf`, the composed models are also placed in `$defs`:
|
|
2329
|
+
|
|
2330
|
+
```ruby
|
|
2331
|
+
class Employee
|
|
2332
|
+
include EasyTalk::Model
|
|
2333
|
+
define_schema do
|
|
2334
|
+
compose T::AllOf[Person, EmployeeDetails]
|
|
2335
|
+
property :badge_number, String
|
|
2336
|
+
end
|
|
2337
|
+
end
|
|
2338
|
+
```
|
|
2339
|
+
|
|
2340
|
+
If you also have properties using `$ref`, both the composed models and property models will appear in `$defs`.
|
|
2341
|
+
|
|
2342
|
+
### Best Practices
|
|
2343
|
+
|
|
2344
|
+
1. **Use global configuration for consistency**: If you prefer `$ref` style, enable it globally rather than per-property.
|
|
2345
|
+
|
|
2346
|
+
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.
|
|
2347
|
+
|
|
2348
|
+
3. **Use `$ref` for frequently reused models**: If a model appears in many places, `$ref` reduces schema size and improves maintainability.
|
|
2349
|
+
|
|
2350
|
+
4. **Keep inline for simple, single-use models**: For models used only once, inlining may be more readable.
|
|
2351
|
+
|
|
2352
|
+
### Default Behavior
|
|
2353
|
+
|
|
2354
|
+
By default, `use_refs` is set to `false`, meaning nested models are inlined. This maintains backward compatibility with previous versions of EasyTalk.
|
|
2355
|
+
|
|
2356
|
+
## JSON Schema Compatibility
|
|
2357
|
+
|
|
2358
|
+
### Supported Versions
|
|
2359
|
+
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).
|
|
2360
|
+
|
|
2361
|
+
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.
|
|
2362
|
+
|
|
2363
|
+
### Specification Compliance
|
|
2364
|
+
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.
|
|
2365
|
+
|
|
2366
|
+
### Known Limitations
|
|
2367
|
+
- Limited support for custom formats
|
|
2368
|
+
- Some draft-specific keywords may not be supported
|
|
2369
|
+
- Complex composition scenarios may require manual adjustment
|
|
2370
|
+
|
|
2371
|
+
## API Reference
|
|
2372
|
+
|
|
2373
|
+
For complete API documentation, visit [RubyDoc.info](https://rubydoc.info/gems/easy_talk).
|
|
2374
|
+
|
|
2375
|
+
### Core Modules
|
|
2376
|
+
|
|
2377
|
+
| Module | Description | Source |
|
|
2378
|
+
|--------|-------------|--------|
|
|
2379
|
+
| [`EasyTalk::Model`](https://rubydoc.info/gems/easy_talk/EasyTalk/Model) | Main mixin providing schema definition, validation, and JSON Schema generation | [lib/easy_talk/model.rb](lib/easy_talk/model.rb) |
|
|
2380
|
+
| [`EasyTalk::Schema`](https://rubydoc.info/gems/easy_talk/EasyTalk/Schema) | Schema-only mode without ActiveModel validations | [lib/easy_talk/schema.rb](lib/easy_talk/schema.rb) |
|
|
2381
|
+
| [`EasyTalk::SchemaDefinition`](https://rubydoc.info/gems/easy_talk/EasyTalk/SchemaDefinition) | DSL for defining properties, titles, and descriptions | [lib/easy_talk/schema_definition.rb](lib/easy_talk/schema_definition.rb) |
|
|
2382
|
+
| [`EasyTalk::Property`](https://rubydoc.info/gems/easy_talk/EasyTalk/Property) | Property definition and type dispatching | [lib/easy_talk/property.rb](lib/easy_talk/property.rb) |
|
|
2383
|
+
|
|
2384
|
+
### Builders
|
|
2385
|
+
|
|
2386
|
+
Type-specific builders that convert Ruby definitions to JSON Schema. See the [builders directory](lib/easy_talk/builders/) for all available builders.
|
|
2387
|
+
|
|
2388
|
+
| Builder | Description |
|
|
2389
|
+
|---------|-------------|
|
|
2390
|
+
| [`ObjectBuilder`](lib/easy_talk/builders/object_builder.rb) | Handles EasyTalk::Model classes |
|
|
2391
|
+
| [`StringBuilder`](lib/easy_talk/builders/string_builder.rb) | String type with format, pattern, length constraints |
|
|
2392
|
+
| [`IntegerBuilder`](lib/easy_talk/builders/integer_builder.rb) | Integer type with min/max constraints |
|
|
2393
|
+
| [`NumberBuilder`](lib/easy_talk/builders/number_builder.rb) | Float/Number type with numeric constraints |
|
|
2394
|
+
| [`BooleanBuilder`](lib/easy_talk/builders/boolean_builder.rb) | Boolean type (T::Boolean) |
|
|
2395
|
+
| [`TypedArrayBuilder`](lib/easy_talk/builders/typed_array_builder.rb) | Typed arrays (T::Array[Type]) |
|
|
2396
|
+
| [`CompositionBuilder`](lib/easy_talk/builders/composition_builder.rb) | Composition types (T::AnyOf, T::OneOf, T::AllOf) |
|
|
2397
|
+
| [`UnionBuilder`](lib/easy_talk/builders/union_builder.rb) | Nilable types (T.nilable) |
|
|
2398
|
+
| [`TemporalBuilder`](lib/easy_talk/builders/temporal_builder.rb) | Date and DateTime types |
|
|
2399
|
+
| [`BaseBuilder`](lib/easy_talk/builders/base_builder.rb) | Base class for all builders |
|
|
2400
|
+
| [`Registry`](lib/easy_talk/builders/registry.rb) | Type-to-builder registration |
|
|
2401
|
+
|
|
2402
|
+
### Configuration & Utilities
|
|
2403
|
+
|
|
2404
|
+
| Class/Module | Description | Source |
|
|
2405
|
+
|--------------|-------------|--------|
|
|
2406
|
+
| [`EasyTalk::Configuration`](https://rubydoc.info/gems/easy_talk/EasyTalk/Configuration) | Global configuration options | [lib/easy_talk/configuration.rb](lib/easy_talk/configuration.rb) |
|
|
2407
|
+
| [`EasyTalk::ValidationBuilder`](https://rubydoc.info/gems/easy_talk/EasyTalk/ValidationBuilder) | Generates ActiveModel validations from constraints | [lib/easy_talk/validation_builder.rb](lib/easy_talk/validation_builder.rb) |
|
|
2408
|
+
| [`EasyTalk::ErrorFormatter`](https://rubydoc.info/gems/easy_talk/EasyTalk/ErrorFormatter) | Formats validation errors (flat, JSON Pointer, RFC 7807, JSON:API) | [lib/easy_talk/error_formatter.rb](lib/easy_talk/error_formatter.rb) |
|
|
2409
|
+
| [`EasyTalk::TypeIntrospection`](https://rubydoc.info/gems/easy_talk/EasyTalk/TypeIntrospection) | Type detection utilities | [lib/easy_talk/type_introspection.rb](lib/easy_talk/type_introspection.rb) |
|
|
2410
|
+
| [`EasyTalk::Tools::FunctionBuilder`](https://rubydoc.info/gems/easy_talk/EasyTalk/Tools/FunctionBuilder) | LLM function specification generator | [lib/easy_talk/tools/function_builder.rb](lib/easy_talk/tools/function_builder.rb) |
|
|
1183
2411
|
|
|
1184
2412
|
## License
|
|
1185
2413
|
|