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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.yardopts +13 -0
  4. data/CHANGELOG.md +105 -0
  5. data/README.md +1268 -40
  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 +119 -10
  18. data/lib/easy_talk/builders/registry.rb +168 -0
  19. data/lib/easy_talk/builders/typed_array_builder.rb +20 -6
  20. data/lib/easy_talk/configuration.rb +51 -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/keywords.rb +2 -0
  32. data/lib/easy_talk/model.rb +125 -41
  33. data/lib/easy_talk/model_helper.rb +13 -0
  34. data/lib/easy_talk/naming_strategies.rb +20 -0
  35. data/lib/easy_talk/property.rb +32 -44
  36. data/lib/easy_talk/ref_helper.rb +27 -0
  37. data/lib/easy_talk/schema.rb +198 -0
  38. data/lib/easy_talk/schema_definition.rb +7 -1
  39. data/lib/easy_talk/schema_methods.rb +80 -0
  40. data/lib/easy_talk/tools/function_builder.rb +1 -1
  41. data/lib/easy_talk/type_introspection.rb +178 -0
  42. data/lib/easy_talk/types/base_composer.rb +2 -1
  43. data/lib/easy_talk/types/composer.rb +4 -0
  44. data/lib/easy_talk/validation_adapters/active_model_adapter.rb +329 -0
  45. data/lib/easy_talk/validation_adapters/base.rb +144 -0
  46. data/lib/easy_talk/validation_adapters/none_adapter.rb +36 -0
  47. data/lib/easy_talk/validation_adapters/registry.rb +87 -0
  48. data/lib/easy_talk/validation_builder.rb +28 -309
  49. data/lib/easy_talk/version.rb +1 -1
  50. data/lib/easy_talk.rb +41 -0
  51. metadata +28 -6
  52. data/docs/404.html +0 -25
  53. data/docs/_posts/2024-05-07-welcome-to-jekyll.markdown +0 -29
  54. data/easy_talk.gemspec +0 -39
@@ -0,0 +1,212 @@
1
+ ---
2
+ layout: page
3
+ title: Property Types
4
+ permalink: /property-types/
5
+ ---
6
+
7
+ # Property Types
8
+
9
+ EasyTalk supports Ruby's built-in types plus Sorbet-style generic types for more complex schemas.
10
+
11
+ ## Basic Types
12
+
13
+ ### String
14
+
15
+ ```ruby
16
+ property :name, String
17
+ # => { "type": "string" }
18
+ ```
19
+
20
+ **Constraints:**
21
+
22
+ | Constraint | Description | Example |
23
+ |------------|-------------|---------|
24
+ | `min_length` | Minimum length | `min_length: 1` |
25
+ | `max_length` | Maximum length | `max_length: 100` |
26
+ | `pattern` | Regex pattern | `pattern: /^[a-z]+$/` |
27
+ | `format` | JSON Schema format | `format: "email"` |
28
+ | `enum` | Allowed values | `enum: %w[a b c]` |
29
+
30
+ **Common formats:** `email`, `uri`, `uuid`, `date`, `date-time`, `time`, `ipv4`, `ipv6`, `hostname`
31
+
32
+ ### Integer
33
+
34
+ ```ruby
35
+ property :age, Integer
36
+ # => { "type": "integer" }
37
+ ```
38
+
39
+ **Constraints:**
40
+
41
+ | Constraint | Description | Example |
42
+ |------------|-------------|---------|
43
+ | `minimum` | Minimum value (inclusive) | `minimum: 0` |
44
+ | `maximum` | Maximum value (inclusive) | `maximum: 100` |
45
+ | `exclusive_minimum` | Minimum value (exclusive) | `exclusive_minimum: 0` |
46
+ | `exclusive_maximum` | Maximum value (exclusive) | `exclusive_maximum: 100` |
47
+ | `multiple_of` | Must be multiple of | `multiple_of: 5` |
48
+ | `enum` | Allowed values | `enum: [1, 2, 3]` |
49
+
50
+ ### Float / Number
51
+
52
+ ```ruby
53
+ property :price, Float
54
+ # => { "type": "number" }
55
+ ```
56
+
57
+ Supports the same constraints as Integer.
58
+
59
+ ### Boolean
60
+
61
+ ```ruby
62
+ property :active, T::Boolean
63
+ # => { "type": "boolean" }
64
+ ```
65
+
66
+ Note: Use `T::Boolean` (not Ruby's `TrueClass`/`FalseClass`).
67
+
68
+ ## Date and Time Types
69
+
70
+ ### Date
71
+
72
+ ```ruby
73
+ property :birth_date, Date
74
+ # => { "type": "string", "format": "date" }
75
+ ```
76
+
77
+ ### DateTime
78
+
79
+ ```ruby
80
+ property :created_at, DateTime
81
+ # => { "type": "string", "format": "date-time" }
82
+ ```
83
+
84
+ ### Time
85
+
86
+ ```ruby
87
+ property :start_time, Time
88
+ # => { "type": "string", "format": "time" }
89
+ ```
90
+
91
+ ## Generic Types
92
+
93
+ EasyTalk uses Sorbet-style generics for complex types.
94
+
95
+ ### Arrays
96
+
97
+ ```ruby
98
+ property :tags, T::Array[String]
99
+ # => { "type": "array", "items": { "type": "string" } }
100
+ ```
101
+
102
+ **Array Constraints:**
103
+
104
+ | Constraint | Description | Example |
105
+ |------------|-------------|---------|
106
+ | `min_items` | Minimum array length | `min_items: 1` |
107
+ | `max_items` | Maximum array length | `max_items: 10` |
108
+ | `unique_items` | All items must be unique | `unique_items: true` |
109
+
110
+ ```ruby
111
+ property :scores, T::Array[Integer], min_items: 1, max_items: 5
112
+ ```
113
+
114
+ ### Nullable Types
115
+
116
+ Use `T.nilable` to allow null values:
117
+
118
+ ```ruby
119
+ property :nickname, T.nilable(String)
120
+ # => { "anyOf": [{ "type": "string" }, { "type": "null" }] }
121
+ ```
122
+
123
+ **Note:** `T.nilable` makes the property nullable but still required. To make it optional as well:
124
+
125
+ ```ruby
126
+ property :nickname, T.nilable(String), optional: true
127
+ ```
128
+
129
+ Or use the helper method:
130
+
131
+ ```ruby
132
+ nullable_optional_property :nickname, String
133
+ ```
134
+
135
+ ## Nested Models
136
+
137
+ Reference other EasyTalk models directly:
138
+
139
+ ```ruby
140
+ class Address
141
+ include EasyTalk::Model
142
+ define_schema do
143
+ property :street, String
144
+ property :city, String
145
+ end
146
+ end
147
+
148
+ class User
149
+ include EasyTalk::Model
150
+ define_schema do
151
+ property :name, String
152
+ property :address, Address # Nested model
153
+ end
154
+ end
155
+ ```
156
+
157
+ Arrays of models:
158
+
159
+ ```ruby
160
+ property :addresses, T::Array[Address]
161
+ ```
162
+
163
+ ## Composition Types
164
+
165
+ ### OneOf
166
+
167
+ Exactly one schema must match:
168
+
169
+ ```ruby
170
+ property :contact, T::OneOf[Email, Phone]
171
+ ```
172
+
173
+ ### AnyOf
174
+
175
+ At least one schema must match:
176
+
177
+ ```ruby
178
+ property :identifier, T::AnyOf[UserId, Email, Username]
179
+ ```
180
+
181
+ ### AllOf
182
+
183
+ All schemas must match (for combining schemas):
184
+
185
+ ```ruby
186
+ property :profile, T::AllOf[BasicInfo, ExtendedInfo]
187
+ ```
188
+
189
+ ## Null Type
190
+
191
+ For explicit null-only values:
192
+
193
+ ```ruby
194
+ property :deprecated_field, NilClass
195
+ # => { "type": "null" }
196
+ ```
197
+
198
+ ## Type Summary
199
+
200
+ | Ruby Type | JSON Schema Type |
201
+ |-----------|------------------|
202
+ | `String` | `"string"` |
203
+ | `Integer` | `"integer"` |
204
+ | `Float` | `"number"` |
205
+ | `T::Boolean` | `"boolean"` |
206
+ | `Date` | `"string"` + `"date"` format |
207
+ | `DateTime` | `"string"` + `"date-time"` format |
208
+ | `Time` | `"string"` + `"time"` format |
209
+ | `T::Array[T]` | `"array"` |
210
+ | `T.nilable(T)` | `anyOf` with null |
211
+ | `NilClass` | `"null"` |
212
+ | Model class | `"object"` (inline or `$ref`) |
@@ -0,0 +1,180 @@
1
+ ---
2
+ layout: page
3
+ title: Schema Definition
4
+ permalink: /schema-definition/
5
+ ---
6
+
7
+ # Schema Definition
8
+
9
+ The `define_schema` block is where you declare your model's structure. It provides a clean DSL for defining JSON Schema properties and metadata.
10
+
11
+ ## Basic Structure
12
+
13
+ ```ruby
14
+ class MyModel
15
+ include EasyTalk::Model
16
+
17
+ define_schema do
18
+ title "Model Title"
19
+ description "What this model represents"
20
+
21
+ property :field_name, Type, constraints...
22
+ end
23
+ end
24
+ ```
25
+
26
+ ## Schema Metadata
27
+
28
+ ### title
29
+
30
+ Sets the schema title (appears in JSON Schema output):
31
+
32
+ ```ruby
33
+ define_schema do
34
+ title "User Account"
35
+ end
36
+ ```
37
+
38
+ ### description
39
+
40
+ Adds a description to the schema:
41
+
42
+ ```ruby
43
+ define_schema do
44
+ description "Represents a user account in the system"
45
+ end
46
+ ```
47
+
48
+ ## Defining Properties
49
+
50
+ The `property` method defines a schema property:
51
+
52
+ ```ruby
53
+ property :name, Type, option: value, ...
54
+ ```
55
+
56
+ ### Required vs Optional
57
+
58
+ By default, all properties are **required**. Use `optional: true` to make a property optional:
59
+
60
+ ```ruby
61
+ define_schema do
62
+ property :name, String # Required
63
+ property :nickname, String, optional: true # Optional
64
+ end
65
+ ```
66
+
67
+ ### Property Titles and Descriptions
68
+
69
+ Add metadata to individual properties:
70
+
71
+ ```ruby
72
+ property :email, String,
73
+ title: "Email Address",
74
+ description: "The user's primary email"
75
+ ```
76
+
77
+ ### Property Renaming
78
+
79
+ Use `:as` to rename a property in the JSON Schema output:
80
+
81
+ ```ruby
82
+ property :created_at, String, as: :createdAt
83
+ ```
84
+
85
+ This creates a property named `createdAt` in the schema while keeping `created_at` as the Ruby attribute.
86
+
87
+ ## Type Constraints
88
+
89
+ Different types support different constraints. See [Property Types](property-types) for the full list.
90
+
91
+ ### String Constraints
92
+
93
+ ```ruby
94
+ property :username, String,
95
+ min_length: 3,
96
+ max_length: 20,
97
+ pattern: /^[a-z0-9_]+$/
98
+ ```
99
+
100
+ ### Numeric Constraints
101
+
102
+ ```ruby
103
+ property :age, Integer,
104
+ minimum: 0,
105
+ maximum: 150
106
+
107
+ property :price, Float,
108
+ exclusive_minimum: 0
109
+ ```
110
+
111
+ ### Enum Values
112
+
113
+ ```ruby
114
+ property :status, String, enum: %w[active inactive pending]
115
+ ```
116
+
117
+ ## Composition
118
+
119
+ ### compose
120
+
121
+ Use `compose` to include schemas from other models:
122
+
123
+ ```ruby
124
+ class FullProfile
125
+ include EasyTalk::Model
126
+
127
+ define_schema do
128
+ compose T::AllOf[BasicInfo, ContactInfo, Preferences]
129
+ end
130
+ end
131
+ ```
132
+
133
+ ### Composition Types
134
+
135
+ - `T::AllOf[A, B]` - Must match all schemas
136
+ - `T::AnyOf[A, B]` - Must match at least one schema
137
+ - `T::OneOf[A, B]` - Must match exactly one schema
138
+
139
+ ## Configuration Options
140
+
141
+ ### Per-Model Validation Control
142
+
143
+ Disable automatic validations for a specific model:
144
+
145
+ ```ruby
146
+ define_schema(validations: false) do
147
+ property :data, String
148
+ end
149
+ ```
150
+
151
+ ### Per-Property Validation Control
152
+
153
+ Disable validation for specific properties:
154
+
155
+ ```ruby
156
+ property :legacy_field, String, validate: false
157
+ ```
158
+
159
+ ## Example: Complete Model
160
+
161
+ ```ruby
162
+ class Product
163
+ include EasyTalk::Model
164
+
165
+ define_schema do
166
+ title "Product"
167
+ description "A product in the catalog"
168
+
169
+ property :id, String, format: "uuid"
170
+ property :name, String, min_length: 1, max_length: 100
171
+ property :description, String, optional: true
172
+ property :price, Float, minimum: 0
173
+ property :currency, String, enum: %w[USD EUR GBP], default: "USD"
174
+ property :category, String
175
+ property :tags, T::Array[String], optional: true
176
+ property :active, T::Boolean, default: true
177
+ property :created_at, DateTime, as: :createdAt
178
+ end
179
+ end
180
+ ```
@@ -12,7 +12,9 @@ module EasyTalk
12
12
  COMMON_OPTIONS = {
13
13
  title: { type: T.nilable(String), key: :title },
14
14
  description: { type: T.nilable(String), key: :description },
15
- optional: { type: T.nilable(T::Boolean), key: :optional }
15
+ optional: { type: T.nilable(T::Boolean), key: :optional },
16
+ as: { type: T.nilable(T.any(String, Symbol)), key: :as },
17
+ validate: { type: T.nilable(T::Boolean), key: :validate }
16
18
  }.freeze
17
19
 
18
20
  attr_reader :property_name, :schema, :options
@@ -42,7 +44,7 @@ module EasyTalk
42
44
  # Builds the schema object based on the provided options.
43
45
  sig { returns(T::Hash[Symbol, T.untyped]) }
44
46
  def build
45
- @valid_options.each_with_object(schema) do |(constraint_name, value), obj|
47
+ @valid_options.except(:ref).each_with_object(schema) do |(constraint_name, value), obj|
46
48
  next if @options[constraint_name].nil?
47
49
 
48
50
  # Use our centralized validation
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'collection_helpers'
4
+ require_relative '../ref_helper'
4
5
 
5
6
  module EasyTalk
6
7
  module Builders
@@ -15,17 +16,18 @@ module EasyTalk
15
16
  'OneOfBuilder' => 'oneOf'
16
17
  }.freeze
17
18
 
18
- sig { params(name: Symbol, type: T.untyped, _constraints: Hash).void }
19
+ sig { params(name: Symbol, type: T.untyped, constraints: Hash).void }
19
20
  # Initializes a new instance of the CompositionBuilder class.
20
21
  #
21
22
  # @param name [Symbol] The name of the composition.
22
23
  # @param type [Class] The type of the composition.
23
- # @param _constraints [Hash] The constraints for the composition (not used in this method).
24
- def initialize(name, type, _constraints)
24
+ # @param constraints [Hash] The constraints for the composition.
25
+ def initialize(name, type, constraints)
25
26
  @composer_type = self.class.name.split('::').last
26
27
  @name = name
27
28
  @type = type
28
29
  @context = {}
30
+ @constraints = constraints
29
31
  end
30
32
 
31
33
  # Builds the composed JSON schema.
@@ -50,17 +52,13 @@ module EasyTalk
50
52
  # @return [Array<Hash>] The array of schemas.
51
53
  def schemas
52
54
  items.map do |type|
53
- if type.respond_to?(:schema)
55
+ if EasyTalk::RefHelper.should_use_ref?(type, @constraints)
56
+ EasyTalk::RefHelper.build_ref_schema(type, @constraints)
57
+ elsif type.respond_to?(:schema)
54
58
  type.schema
55
59
  else
56
- # Map Float type to 'number' in JSON Schema
57
- json_type = case type.to_s
58
- when 'Float', 'BigDecimal'
59
- 'number'
60
- else
61
- type.to_s.downcase
62
- end
63
- { type: json_type }
60
+ # Map Ruby type to JSON Schema type
61
+ { type: TypeIntrospection.json_schema_type(type) }
64
62
  end
65
63
  end
66
64
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base_builder'
4
+ require_relative '../model_helper'
4
5
 
5
6
  module EasyTalk
6
7
  module Builders
@@ -33,12 +34,15 @@ module EasyTalk
33
34
  def initialize(schema_definition)
34
35
  # Keep a reference to the original schema definition
35
36
  @schema_definition = schema_definition
36
- # Duplicate the raw schema hash so we can mutate it safely
37
- @original_schema = schema_definition.schema.dup
37
+ # Deep duplicate the raw schema hash so we can mutate it safely
38
+ @original_schema = deep_dup(schema_definition.schema)
38
39
 
39
40
  # We'll collect required property names in this Set
40
41
  @required_properties = Set.new
41
42
 
43
+ # Collect models that are referenced via $ref for $defs generation
44
+ @ref_models = Set.new
45
+
42
46
  # Usually the name is a string (class name). Fallback to :klass if nil.
43
47
  name_for_builder = schema_definition.name ? schema_definition.name.to_sym : :klass
44
48
 
@@ -53,6 +57,24 @@ module EasyTalk
53
57
 
54
58
  private
55
59
 
60
+ ##
61
+ # Deep duplicates a hash, including nested hashes.
62
+ # This prevents mutations from leaking back to the original schema.
63
+ #
64
+ def deep_dup(obj)
65
+ case obj
66
+ when Hash
67
+ obj.transform_values { |v| deep_dup(v) }
68
+ when Array
69
+ obj.map { |v| deep_dup(v) }
70
+ when Class, Module
71
+ # Don't duplicate Class or Module objects - they represent types
72
+ obj
73
+ else
74
+ obj.duplicable? ? obj.dup : obj
75
+ end
76
+ end
77
+
56
78
  ##
57
79
  # Main aggregator: merges the top-level schema keys (like :properties, :subschemas)
58
80
  # into a single hash that we'll feed to BaseBuilder.
@@ -60,12 +82,20 @@ module EasyTalk
60
82
  # Start with a copy of the raw schema
61
83
  merged = @original_schema.dup
62
84
 
85
+ # Remove schema_version and schema_id as they're handled separately in json_schema output
86
+ merged.delete(:schema_version)
87
+ merged.delete(:schema_id)
88
+
63
89
  # Extract and build sub-schemas first (handles allOf/anyOf/oneOf references, etc.)
64
90
  process_subschemas(merged)
65
91
 
66
92
  # Build :properties into a final form (and find "required" props)
93
+ # This also collects models that use $ref into @ref_models
67
94
  merged[:properties] = build_properties(merged.delete(:properties))
68
95
 
96
+ # Add $defs for any models that are referenced via $ref
97
+ add_ref_model_defs(merged) if @ref_models.any?
98
+
69
99
  # Populate the final "required" array from @required_properties
70
100
  merged[:required] = @required_properties.to_a if @required_properties.any?
71
101
 
@@ -88,16 +118,18 @@ module EasyTalk
88
118
  # Cache with a key based on property name and its full configuration
89
119
  @properties_cache ||= {}
90
120
 
91
- properties_hash.each_with_object({}) do |(prop_name, prop_options), result|
92
- cache_key = [prop_name, prop_options].hash
121
+ properties_hash.each_with_object({}) do |(original_name, prop_options), result|
122
+ # Use :as constraint for property name without mutating original constraints
123
+ property_name = (prop_options[:constraints][:as] || original_name).to_sym
124
+ cache_key = [property_name, prop_options].hash
93
125
 
94
126
  # Use cache if the exact property and configuration have been processed before
95
127
  @properties_cache[cache_key] ||= begin
96
- mark_required_unless_optional(prop_name, prop_options)
97
- build_property(prop_name, prop_options)
128
+ mark_required_unless_optional(property_name, prop_options)
129
+ build_property(property_name, prop_options)
98
130
  end
99
131
 
100
- result[prop_name] = @properties_cache[cache_key]
132
+ result[property_name] = @properties_cache[cache_key]
101
133
  end
102
134
  end
103
135
 
@@ -127,17 +159,94 @@ module EasyTalk
127
159
  ##
128
160
  # Builds a single property. Could be a nested schema if it has sub-properties,
129
161
  # or a standard scalar property (String, Integer, etc.).
162
+ # Also tracks EasyTalk models that should be added to $defs when using $ref.
130
163
  #
131
164
  def build_property(prop_name, prop_options)
132
165
  @property_cache ||= {}
133
166
 
134
167
  # Memoize so we only build each property once
135
168
  @property_cache[prop_name] ||= begin
136
- # Remove optional constraints from the property
137
- constraints = prop_options[:constraints].except(:optional)
169
+ # Remove internal constraints that shouldn't be passed to Property
170
+ constraints = prop_options[:constraints].except(:optional, :as)
171
+ prop_type = prop_options[:type]
172
+
173
+ # Track models that will use $ref for later $defs generation
174
+ collect_ref_models(prop_type, constraints)
175
+
138
176
  # Normal property: e.g. { type: String, constraints: {...} }
139
- Property.new(prop_name, prop_options[:type], constraints)
177
+ Property.new(prop_name, prop_type, constraints)
178
+ end
179
+ end
180
+
181
+ ##
182
+ # Collects EasyTalk models that will be referenced via $ref.
183
+ # These models need to be added to $defs in the final schema.
184
+ #
185
+ def collect_ref_models(prop_type, constraints)
186
+ # Check if this type should use $ref
187
+ if should_collect_ref?(prop_type, constraints)
188
+ @ref_models.add(prop_type)
189
+ elsif prop_type.is_a?(EasyTalk::Types::Composer)
190
+ collect_ref_models(prop_type.items, constraints)
191
+ # Handle typed arrays with EasyTalk model items
192
+ elsif typed_array?(prop_type)
193
+ extract_inner_types(prop_type).each { |inner_type| collect_ref_models(inner_type, constraints) }
194
+ # Handle nilable types
195
+ elsif nilable_with_model?(prop_type)
196
+ actual_type = T::Utils::Nilable.get_underlying_type(prop_type)
197
+ @ref_models.add(actual_type) if should_collect_ref?(actual_type, constraints)
198
+ end
199
+ end
200
+
201
+ ##
202
+ # Determines if a type should be collected for $ref based on config and constraints.
203
+ #
204
+ def should_collect_ref?(check_type, constraints)
205
+ return false unless ModelHelper.easytalk_model?(check_type)
206
+
207
+ # Per-property constraint takes precedence
208
+ return constraints[:ref] if constraints.key?(:ref)
209
+
210
+ # Fall back to global configuration
211
+ EasyTalk.configuration.use_refs
212
+ end
213
+
214
+ def typed_array?(prop_type)
215
+ prop_type.is_a?(T::Types::TypedArray)
216
+ end
217
+
218
+ def extract_inner_types(prop_type)
219
+ return [] unless typed_array?(prop_type)
220
+
221
+ if prop_type.type.is_a?(EasyTalk::Types::Composer)
222
+ prop_type.type.items
223
+ else
224
+ [prop_type.type.raw_type]
225
+ end
226
+ end
227
+
228
+ ##
229
+ # Checks if type is nilable and contains an EasyTalk model.
230
+ #
231
+ def nilable_with_model?(prop_type)
232
+ return false unless prop_type.respond_to?(:types)
233
+ return false unless prop_type.types.all? { |t| t.respond_to?(:raw_type) }
234
+ return false unless prop_type.types.any? { |t| t.raw_type == NilClass }
235
+
236
+ actual_type = T::Utils::Nilable.get_underlying_type(prop_type)
237
+ ModelHelper.easytalk_model?(actual_type)
238
+ end
239
+
240
+ ##
241
+ # Adds $defs entries for all collected ref models.
242
+ #
243
+ def add_ref_model_defs(schema_hash)
244
+ definitions = @ref_models.each_with_object({}) do |model, acc|
245
+ acc[model.name] = model.schema
140
246
  end
247
+
248
+ existing_defs = schema_hash[:defs] || {}
249
+ schema_hash[:defs] = existing_defs.merge(definitions)
141
250
  end
142
251
 
143
252
  ##