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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.yardopts +13 -0
  4. data/CHANGELOG.md +75 -0
  5. data/README.md +616 -35
  6. data/Rakefile +27 -0
  7. data/docs/.gitignore +1 -0
  8. data/docs/about.markdown +28 -8
  9. data/docs/getting-started.markdown +102 -0
  10. data/docs/index.markdown +51 -4
  11. data/docs/json_schema_compliance.md +55 -0
  12. data/docs/nested-models.markdown +216 -0
  13. data/docs/property-types.markdown +212 -0
  14. data/docs/schema-definition.markdown +180 -0
  15. data/lib/easy_talk/builders/base_builder.rb +4 -2
  16. data/lib/easy_talk/builders/composition_builder.rb +10 -12
  17. data/lib/easy_talk/builders/object_builder.rb +45 -30
  18. data/lib/easy_talk/builders/registry.rb +168 -0
  19. data/lib/easy_talk/builders/typed_array_builder.rb +15 -4
  20. data/lib/easy_talk/configuration.rb +31 -1
  21. data/lib/easy_talk/error_formatter/base.rb +100 -0
  22. data/lib/easy_talk/error_formatter/error_code_mapper.rb +82 -0
  23. data/lib/easy_talk/error_formatter/flat.rb +38 -0
  24. data/lib/easy_talk/error_formatter/json_pointer.rb +38 -0
  25. data/lib/easy_talk/error_formatter/jsonapi.rb +64 -0
  26. data/lib/easy_talk/error_formatter/path_converter.rb +53 -0
  27. data/lib/easy_talk/error_formatter/rfc7807.rb +69 -0
  28. data/lib/easy_talk/error_formatter.rb +143 -0
  29. data/lib/easy_talk/errors.rb +2 -0
  30. data/lib/easy_talk/errors_helper.rb +63 -34
  31. data/lib/easy_talk/model.rb +123 -90
  32. data/lib/easy_talk/model_helper.rb +13 -0
  33. data/lib/easy_talk/naming_strategies.rb +20 -0
  34. data/lib/easy_talk/property.rb +16 -94
  35. data/lib/easy_talk/ref_helper.rb +27 -0
  36. data/lib/easy_talk/schema.rb +198 -0
  37. data/lib/easy_talk/schema_definition.rb +7 -1
  38. data/lib/easy_talk/schema_methods.rb +80 -0
  39. data/lib/easy_talk/tools/function_builder.rb +1 -1
  40. data/lib/easy_talk/type_introspection.rb +178 -0
  41. data/lib/easy_talk/types/base_composer.rb +2 -1
  42. data/lib/easy_talk/types/composer.rb +4 -0
  43. data/lib/easy_talk/validation_adapters/active_model_adapter.rb +329 -0
  44. data/lib/easy_talk/validation_adapters/base.rb +144 -0
  45. data/lib/easy_talk/validation_adapters/none_adapter.rb +36 -0
  46. data/lib/easy_talk/validation_adapters/registry.rb +87 -0
  47. data/lib/easy_talk/validation_builder.rb +28 -309
  48. data/lib/easy_talk/version.rb +1 -1
  49. data/lib/easy_talk.rb +41 -0
  50. metadata +26 -4
  51. data/docs/404.html +0 -25
  52. data/docs/_posts/2024-05-07-welcome-to-jekyll.markdown +0 -29
  53. data/easy_talk.gemspec +0 -39
data/README.md CHANGED
@@ -2,23 +2,179 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/easy_talk.svg)](https://badge.fury.io/rb/easy_talk)
4
4
  [![Ruby](https://github.com/sergiobayona/easy_talk/actions/workflows/dev-build.yml/badge.svg)](https://github.com/sergiobayona/easy_talk/actions/workflows/dev-build.yml)
5
+ [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](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 that simplifies defining and generating JSON Schema. It provides an intuitive interface for Ruby developers to define structured data models that can be used for validation and documentation.
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**: Use Ruby classes and methods to define JSON Schema documents easily.
13
- * **Automatic ActiveModel Validations**: Schema constraints automatically generate corresponding ActiveModel validations (configurable).
14
- * **Works for plain Ruby classes and ActiveModel classes**: Integrate with existing code or build from scratch.
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
- * **Schema Composition**: Define EasyTalk models and reference them in other EasyTalk models to create complex schemas.
17
- * **Enhanced Model Integration**: Automatic instantiation of nested EasyTalk models from hash attributes.
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.
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.
22
178
 
23
179
  ### Use Cases
24
180
  - API request/response validation
@@ -28,7 +184,7 @@ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema.
28
184
  - Configuration schema definitions
29
185
 
30
186
  ### Inspiration
31
- 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.
32
188
 
33
189
  ## Installation
34
190
 
@@ -367,6 +523,27 @@ company.location = "New York" # Additional property
367
523
  company.employee_count = 100 # Additional property
368
524
  ```
369
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
+
370
547
  ## Schema Composition
371
548
 
372
549
  ### Using T::AnyOf
@@ -408,13 +585,67 @@ class VehicleRegistration
408
585
  end
409
586
  ```
410
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
+
411
642
  ### Complex Compositions
412
643
  You can combine composition types to create complex schemas:
413
644
 
414
645
  ```ruby
415
646
  class ComplexObject
416
647
  include EasyTalk::Model
417
-
648
+
418
649
  define_schema do
419
650
  property :basic_info, BaseInfo
420
651
  property :specific_details, T::OneOf[DetailTypeA, DetailTypeB]
@@ -480,6 +711,78 @@ user.errors[:age] # => ["must be greater than 21"] # Custom validation
480
711
  user.errors[:height] # => ["must be greater than 0"] # Overridden validation
481
712
  ```
482
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
+
483
786
  ### Model Attributes
484
787
  EasyTalk models provide getters and setters for all defined properties:
485
788
 
@@ -523,6 +826,57 @@ user.address.street # => "123 Main St"
523
826
 
524
827
  ## Advanced Features
525
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
+
526
880
  ### LLM Function Generation
527
881
  EasyTalk provides a helper method for generating OpenAI function specifications:
528
882
 
@@ -569,16 +923,125 @@ property :age, Integer, enum: ["young", "old"] # Error!
569
923
  ```
570
924
 
571
925
  ### Custom Type Builders
572
- For advanced use cases, you can create custom type builders:
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:
573
932
 
574
933
  ```ruby
575
- module EasyTalk
576
- module Builders
577
- class MyCustomTypeBuilder < BaseBuilder
578
- # Custom implementation
579
- end
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
580
1006
  end
581
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
582
1045
  ```
583
1046
 
584
1047
  ## Configuration
@@ -591,11 +1054,20 @@ EasyTalk.configure do |config|
591
1054
  # Schema behavior options
592
1055
  config.default_additional_properties = false # Control additional properties on all models
593
1056
  config.nilable_is_optional = false # Makes T.nilable properties also optional
594
- config.auto_validations = true # Automatically generate ActiveModel validations
595
1057
  config.schema_version = :none # JSON Schema version for $schema keyword
596
1058
  # Options: :none, :draft202012, :draft201909, :draft7, :draft6, :draft4
597
1059
  config.schema_id = nil # Base URI for $id keyword (nil = no $id)
598
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
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
599
1071
  end
600
1072
  ```
601
1073
 
@@ -628,7 +1100,7 @@ You can configure additional properties for individual models:
628
1100
  ```ruby
629
1101
  class User
630
1102
  include EasyTalk::Model
631
-
1103
+
632
1104
  define_schema do
633
1105
  title "User"
634
1106
  additional_properties true # Allow arbitrary additional properties on this model
@@ -638,6 +1110,74 @@ class User
638
1110
  end
639
1111
  ```
640
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
+
641
1181
  ## Examples
642
1182
 
643
1183
  ### User Registration (with Auto-Validations)
@@ -878,18 +1418,18 @@ property :status, String, enum: ["active", "inactive", "pending"]
878
1418
  9. **Use nullable_optional_property** for fields that can be omitted or null
879
1419
  10. **Document breaking changes** when updating schema definitions
880
1420
 
881
- # Nullable vs Optional Properties in EasyTalk
1421
+ ## Nullable vs Optional Properties in EasyTalk
882
1422
 
883
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.
884
1424
 
885
- ## Key Concepts
1425
+ ### Key Concepts
886
1426
 
887
1427
  | Concept | Description | JSON Schema Effect | EasyTalk Syntax |
888
1428
  |---------|-------------|-------------------|-----------------|
889
1429
  | **Nullable** | Property can have a `null` value | Adds `"null"` to the type array | `T.nilable(Type)` |
890
1430
  | **Optional** | Property doesn't have to exist | Omits property from `"required"` array | `optional: true` constraint |
891
1431
 
892
- ## Nullable Properties
1432
+ ### Nullable Properties
893
1433
 
894
1434
  A **nullable** property can contain a `null` value, but the property itself must still be present in the object:
895
1435
 
@@ -915,7 +1455,7 @@ In this case, the following data would be valid:
915
1455
  But this would be invalid:
916
1456
  - `{ }` (missing the age property entirely)
917
1457
 
918
- ## Optional Properties
1458
+ ### Optional Properties
919
1459
 
920
1460
  An **optional** property doesn't have to be present in the object at all:
921
1461
 
@@ -941,7 +1481,7 @@ In this case, the following data would be valid:
941
1481
  But this would be invalid:
942
1482
  - `{ "nickname": null }` (null is not allowed because the property isn't nullable)
943
1483
 
944
- ## Nullable AND Optional Properties
1484
+ ### Nullable AND Optional Properties
945
1485
 
946
1486
  For properties that should be both nullable and optional (can be omitted or null), you need to combine both approaches:
947
1487
 
@@ -968,7 +1508,7 @@ nullable_optional_property :bio, String
968
1508
 
969
1509
  Which is equivalent to the above.
970
1510
 
971
- ## Configuration Options
1511
+ ### Configuration Options
972
1512
 
973
1513
  By default, nullable properties are still required. You can change this global behavior:
974
1514
 
@@ -980,9 +1520,9 @@ end
980
1520
 
981
1521
  With this configuration, any property defined with `T.nilable(Type)` will be treated as both nullable and optional.
982
1522
 
983
- ## Practical Examples
1523
+ ### Practical Examples
984
1524
 
985
- ### User Profile Schema
1525
+ #### User Profile Schema
986
1526
 
987
1527
  ```ruby
988
1528
  class UserProfile
@@ -1011,17 +1551,17 @@ This creates clear expectations for data validation:
1011
1551
  - `email` doesn't have to be present, but if it is, it cannot be null
1012
1552
  - `bio` doesn't have to be present, and if it is, it can be null
1013
1553
 
1014
- ## Common Gotchas
1554
+ ### Common Gotchas
1015
1555
 
1016
- ### Misconception: Nullable Implies Optional
1556
+ #### Misconception: Nullable Implies Optional
1017
1557
 
1018
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.
1019
1559
 
1020
- ### Misconception: Optional Properties Accept Null
1560
+ #### Misconception: Optional Properties Accept Null
1021
1561
 
1022
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)`.
1023
1563
 
1024
- ## Migration from Earlier Versions
1564
+ ### Migration from Earlier Versions
1025
1565
 
1026
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.
1027
1567
 
@@ -1035,14 +1575,14 @@ end
1035
1575
 
1036
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.
1037
1577
 
1038
- ## Best Practices
1578
+ ### Best Practices
1039
1579
 
1040
1580
  1. **Be explicit about intent**: Always clarify whether properties should be nullable, optional, or both
1041
1581
  2. **Use the helper method**: For properties that are both nullable and optional, use `nullable_optional_property`
1042
1582
  3. **Document expectations**: Use comments to clarify validation requirements for complex schemas
1043
1583
  4. **Consider validation implications**: Remember that ActiveModel validations operate independently of the schema definition
1044
1584
 
1045
- ## JSON Schema Comparison
1585
+ ### JSON Schema Comparison
1046
1586
 
1047
1587
  | EasyTalk Definition | Required | Nullable | JSON Schema Equivalent |
1048
1588
  |--------------------|----------|----------|------------------------|
@@ -1828,6 +2368,47 @@ To learn about current capabilities, see the [spec/easy_talk/examples](https://g
1828
2368
  - Some draft-specific keywords may not be supported
1829
2369
  - Complex composition scenarios may require manual adjustment
1830
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) |
2411
+
1831
2412
  ## License
1832
2413
 
1833
2414
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).