easy_talk 3.3.0 → 3.3.2

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.
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyTalk
4
+ module Extensions
5
+ # Class methods for RubyLLM compatibility.
6
+ # These are added to the model class via `extend`.
7
+ module RubyLLMCompatibility
8
+ # Returns a Hash representing the schema in a format compatible with RubyLLM.
9
+ # RubyLLM expects an object that responds to #to_json_schema and returns
10
+ # a hash with :name, :description, and :schema keys.
11
+ #
12
+ # @return [Hash] The RubyLLM-compatible schema representation
13
+ def to_json_schema
14
+ {
15
+ name: name,
16
+ description: schema_definition.schema[:description] || "Schema for #{name}",
17
+ schema: json_schema
18
+ }
19
+ end
20
+ end
21
+
22
+ # Overrides for classes that inherit from RubyLLM::Tool.
23
+ # Only overrides schema-related methods, allowing all other RubyLLM::Tool
24
+ # functionality (halt, call, etc.) to work normally.
25
+ #
26
+ # Usage:
27
+ # class WeatherTool < RubyLLM::Tool
28
+ # include EasyTalk::Model
29
+ #
30
+ # define_schema do
31
+ # description 'Gets current weather'
32
+ # property :latitude, String
33
+ # property :longitude, String
34
+ # end
35
+ #
36
+ # def execute(latitude:, longitude:)
37
+ # # Can use halt() since we inherit from RubyLLM::Tool
38
+ # halt "Weather at #{latitude}, #{longitude}"
39
+ # end
40
+ # end
41
+ module RubyLLMToolOverrides
42
+ # Override to use EasyTalk's schema description.
43
+ #
44
+ # @return [String] The tool description from EasyTalk schema
45
+ def description
46
+ schema_def = self.class.schema_definition
47
+ schema_def.schema[:description] || "Tool: #{self.class.name}"
48
+ end
49
+
50
+ # Override to use EasyTalk's JSON schema for parameters.
51
+ #
52
+ # @return [Hash] The JSON schema for parameters
53
+ def params_schema
54
+ self.class.json_schema
55
+ end
56
+ end
57
+ end
58
+ end
@@ -8,10 +8,10 @@ require 'active_support/time'
8
8
  require 'active_support/concern'
9
9
  require 'active_support/json'
10
10
  require 'active_model'
11
- require_relative 'builders/object_builder'
12
- require_relative 'schema_definition'
11
+ require_relative 'schema_base'
13
12
  require_relative 'validation_builder'
14
13
  require_relative 'error_formatter'
14
+ require_relative 'extensions/ruby_llm_compatibility'
15
15
 
16
16
  module EasyTalk
17
17
  # The `Model` module is a mixin that provides functionality for defining and accessing the schema of a model.
@@ -38,150 +38,45 @@ module EasyTalk
38
38
  module Model
39
39
  def self.included(base)
40
40
  base.extend(ClassMethods)
41
+ base.extend(EasyTalk::Extensions::RubyLLMCompatibility) # Add class-level methods
41
42
 
42
43
  base.include ActiveModel::API
43
44
  base.include ActiveModel::Validations
44
45
  base.extend ActiveModel::Callbacks
45
46
  base.include(InstanceMethods)
46
47
  base.include(ErrorFormatter::InstanceMethods)
48
+
49
+ # If inheriting from RubyLLM::Tool, override schema methods to use EasyTalk's schema
50
+ return unless defined?(RubyLLM::Tool) && base < RubyLLM::Tool
51
+
52
+ base.include(EasyTalk::Extensions::RubyLLMToolOverrides)
47
53
  end
48
54
 
49
55
  # Instance methods mixed into models that include EasyTalk::Model
50
56
  module InstanceMethods
57
+ include SchemaBase::InstanceMethods
58
+
51
59
  def initialize(attributes = {})
52
60
  @additional_properties = {}
53
61
  provided_keys = attributes.keys.to_set(&:to_sym)
54
62
 
55
- super # Perform initial mass assignment
63
+ super # Perform initial mass assignment via ActiveModel::API
56
64
 
57
- schema_def = self.class.schema_definition
58
- return unless schema_def.respond_to?(:schema) && schema_def.schema.is_a?(Hash)
59
-
60
- (schema_def.schema[:properties] || {}).each do |prop_name, prop_definition|
61
- process_property_initialization(prop_name, prop_definition, provided_keys)
62
- end
65
+ initialize_schema_properties(provided_keys)
63
66
  end
64
67
 
65
- private
66
-
67
- def process_property_initialization(prop_name, prop_definition, provided_keys)
68
- defined_type = prop_definition[:type]
69
- nilable_type = defined_type.respond_to?(:nilable?) && defined_type.nilable?
70
-
71
- apply_default_value(prop_name, prop_definition, provided_keys)
72
-
73
- current_value = public_send(prop_name)
74
- return if nilable_type && current_value.nil?
75
-
76
- defined_type = T::Utils::Nilable.get_underlying_type(defined_type) if nilable_type
77
- instantiate_nested_models(prop_name, defined_type, current_value)
78
- end
79
-
80
- def apply_default_value(prop_name, prop_definition, provided_keys)
81
- return if provided_keys.include?(prop_name)
82
-
83
- default_value = prop_definition.dig(:constraints, :default)
84
- public_send("#{prop_name}=", default_value) unless default_value.nil?
85
- end
86
-
87
- def instantiate_nested_models(prop_name, defined_type, current_value)
88
- # Single nested model: convert Hash to model instance
89
- if defined_type.is_a?(Class) && defined_type.include?(EasyTalk::Model) && current_value.is_a?(Hash)
90
- public_send("#{prop_name}=", defined_type.new(current_value))
91
- return
92
- end
93
-
94
- # Array of nested models: convert Hash items to model instances
95
- instantiate_array_items(prop_name, defined_type, current_value)
96
- end
97
-
98
- def instantiate_array_items(prop_name, defined_type, current_value)
99
- return unless defined_type.is_a?(T::Types::TypedArray) && current_value.is_a?(Array)
100
-
101
- item_type = defined_type.type.respond_to?(:raw_type) ? defined_type.type.raw_type : nil
102
- return unless item_type.is_a?(Class) && item_type.include?(EasyTalk::Model)
103
-
104
- instantiated = current_value.map { |item| item.is_a?(Hash) ? item_type.new(item) : item }
105
- public_send("#{prop_name}=", instantiated)
106
- end
107
-
108
- public
109
-
110
- def method_missing(method_name, *args)
111
- method_string = method_name.to_s
112
- if method_string.end_with?('=')
113
- property_name = method_string.chomp('=')
114
- if self.class.additional_properties_allowed?
115
- @additional_properties[property_name] = args.first
116
- else
117
- super
118
- end
119
- elsif self.class.additional_properties_allowed? && @additional_properties.key?(method_string)
120
- @additional_properties[method_string]
121
- else
122
- super
123
- end
124
- end
125
-
126
- def respond_to_missing?(method_name, include_private = false)
127
- return super unless self.class.additional_properties_allowed?
128
-
129
- method_string = method_name.to_s
130
- method_string.end_with?('=') || @additional_properties.key?(method_string) || super
131
- end
132
-
133
- # Add to_hash method to convert defined properties to hash
134
- def to_hash
135
- properties_to_include = (self.class.schema_definition.schema[:properties] || {}).keys
136
- return {} if properties_to_include.empty?
137
-
138
- properties_to_include.each_with_object({}) do |prop, hash|
139
- hash[prop.to_s] = send(prop)
140
- end
141
- end
142
-
143
- # Override as_json to include both defined and additional properties
144
- def as_json(_options = {})
145
- to_hash.merge(@additional_properties)
146
- end
147
-
148
- # to_h includes both defined and additional properties
149
- def to_h
150
- to_hash.merge(@additional_properties)
151
- end
152
-
153
- # Allow comparison with hashes
154
- def ==(other)
155
- case other
156
- when Hash
157
- # Convert both to comparable format for comparison
158
- self_hash = (self.class.schema_definition.schema[:properties] || {}).keys.each_with_object({}) do |prop, hash|
159
- hash[prop] = send(prop)
160
- end
161
-
162
- # Handle both symbol and string keys in the other hash
163
- other_normalized = other.transform_keys(&:to_sym)
164
- self_hash == other_normalized
165
- else
166
- super
167
- end
68
+ # Returns a Hash representing the schema in a format compatible with RubyLLM.
69
+ # Delegates to the class method. Required for RubyLLM's with_schema method.
70
+ #
71
+ # @return [Hash] The RubyLLM-compatible schema representation
72
+ def to_json_schema
73
+ self.class.to_json_schema
168
74
  end
169
75
  end
170
76
 
171
77
  # Module containing class-level methods for defining and accessing the schema of a model.
172
78
  module ClassMethods
173
- include SchemaMethods
174
-
175
- # Returns the schema for the model.
176
- #
177
- # @return [Schema] The schema for the model.
178
- def schema
179
- @schema ||= if defined?(@schema_definition) && @schema_definition
180
- build_schema(@schema_definition)
181
- else
182
- {}
183
- end
184
- end
79
+ include SchemaBase::ClassMethods
185
80
 
186
81
  # Define the schema for the model using the provided block.
187
82
  #
@@ -205,22 +100,11 @@ module EasyTalk
205
100
  # property :name, String
206
101
  # end
207
102
  def define_schema(options = {}, &)
208
- raise ArgumentError, 'The class must have a name' unless name.present?
209
-
210
- @schema_definition = SchemaDefinition.new(name)
211
- @schema_definition.klass = self # Pass the model class to the schema definition
212
- @schema_definition.instance_eval(&)
103
+ super(&)
213
104
 
214
105
  # Store validation options for this model
215
106
  @validation_options = normalize_validation_options(options)
216
107
 
217
- # Define accessors immediately based on schema_definition
218
- defined_properties = (@schema_definition.schema[:properties] || {}).keys
219
- attr_accessor(*defined_properties)
220
-
221
- # Track which properties have had validations applied
222
- @validated_properties ||= Set.new
223
-
224
108
  # Initialize mutex eagerly for thread-safe schema-level validation application
225
109
  @schema_level_validation_lock = Mutex.new
226
110
 
@@ -232,6 +116,19 @@ module EasyTalk
232
116
 
233
117
  private
234
118
 
119
+ # Reset all memoized schema state and clear previously registered
120
+ # ActiveModel validators so a second define_schema call is never ignored.
121
+ def clear_schema_state!
122
+ super
123
+ @schema_level_validations_applied = false
124
+ @validated_properties = Set.new
125
+
126
+ return unless @schema_definition
127
+
128
+ reset_callbacks(:validate)
129
+ _validators.clear
130
+ end
131
+
235
132
  # Normalize validation options from various input formats.
236
133
  #
237
134
  # @param options [Hash] The options hash from define_schema
@@ -298,37 +195,6 @@ module EasyTalk
298
195
  @schema_level_validations_applied = true
299
196
  end
300
197
  end
301
-
302
- public
303
-
304
- # Returns the unvalidated schema definition for the model.
305
- #
306
- # @return [SchemaDefinition] The unvalidated schema definition for the model.
307
- def schema_definition
308
- @schema_definition ||= {}
309
- end
310
-
311
- def additional_properties_allowed?
312
- ap = @schema_definition&.schema&.fetch(:additional_properties, false)
313
- # Allow if true, or if it's a schema object (Class or Hash with type)
314
- ap == true || ap.is_a?(Class) || ap.is_a?(Hash)
315
- end
316
-
317
- # Returns the property names defined in the schema
318
- #
319
- # @return [Array<Symbol>] Array of property names as symbols
320
- def properties
321
- (@schema_definition&.schema&.dig(:properties) || {}).keys
322
- end
323
-
324
- # Builds the schema using the provided schema definition.
325
- # This is the convergence point for all schema generation.
326
- #
327
- # @param schema_definition [SchemaDefinition] The schema definition.
328
- # @return [Schema] The validated schema.
329
- def build_schema(schema_definition)
330
- Builders::ObjectBuilder.new(schema_definition).build
331
- end
332
198
  end
333
199
  end
334
200
  end
@@ -112,9 +112,8 @@ module EasyTalk
112
112
  args = is_collection ? [name, type, constraints] : [name, constraints]
113
113
  builder_class.new(*args).build
114
114
  elsif type.respond_to?(:schema)
115
- # merge the top-level constraints from *this* property
116
- # e.g. :title, :description, :default, etc
117
- type.schema.merge!(constraints)
115
+ # deep_dup so nested hashes in the cached schema aren't shared with the result
116
+ EasyTalk.deep_dup(type.schema).merge(constraints)
118
117
  else
119
118
  raise UnknownTypeError,
120
119
  "Unknown type '#{type.inspect}' for property '#{name}'. " \
@@ -7,8 +7,7 @@ require 'active_support/core_ext'
7
7
  require 'active_support/time'
8
8
  require 'active_support/concern'
9
9
  require 'active_support/json'
10
- require_relative 'builders/object_builder'
11
- require_relative 'schema_definition'
10
+ require_relative 'schema_base'
12
11
 
13
12
  module EasyTalk
14
13
  # A lightweight module for schema generation without ActiveModel validations.
@@ -50,150 +49,41 @@ module EasyTalk
50
49
 
51
50
  # Instance methods for schema-only models.
52
51
  module InstanceMethods
52
+ include SchemaBase::InstanceMethods
53
+
53
54
  # Initialize the schema object with attributes.
55
+ # Performs manual attribute assignment (no ActiveModel) then applies
56
+ # defaults and nested model instantiation via the shared base.
54
57
  #
55
58
  # @param attributes [Hash] The attributes to set
56
59
  def initialize(attributes = {})
57
60
  @additional_properties = {}
58
- schema_def = self.class.schema_definition
59
-
60
- return unless schema_def.respond_to?(:schema) && schema_def.schema.is_a?(Hash)
61
-
62
- (schema_def.schema[:properties] || {}).each do |prop_name, prop_definition|
63
- value = attributes[prop_name] || attributes[prop_name.to_s]
64
-
65
- # Handle default values
66
- if value.nil? && !attributes.key?(prop_name) && !attributes.key?(prop_name.to_s)
67
- default_value = prop_definition.dig(:constraints, :default)
68
- value = default_value unless default_value.nil?
69
- end
70
-
71
- # Handle nested EasyTalk::Schema or EasyTalk::Model objects
72
- defined_type = prop_definition[:type]
73
- nilable_type = defined_type.respond_to?(:nilable?) && defined_type.nilable?
74
- defined_type = T::Utils::Nilable.get_underlying_type(defined_type) if nilable_type
75
-
76
- if defined_type.is_a?(Class) &&
77
- (defined_type.include?(EasyTalk::Schema) || defined_type.include?(EasyTalk::Model)) &&
78
- value.is_a?(Hash)
79
- value = defined_type.new(value)
80
- end
81
-
82
- instance_variable_set("@#{prop_name}", value)
83
- end
84
- end
61
+ provided_keys = Set.new
85
62
 
86
- # Convert defined properties to a hash.
87
- #
88
- # @return [Hash] The properties as a hash
89
- def to_hash
90
- properties_to_include = (self.class.schema_definition.schema[:properties] || {}).keys
91
- return {} if properties_to_include.empty?
92
-
93
- properties_to_include.each_with_object({}) do |prop, hash|
94
- hash[prop.to_s] = send(prop)
95
- end
63
+ assign_schema_attributes(attributes, provided_keys)
64
+ initialize_schema_properties(provided_keys)
96
65
  end
97
66
 
98
- # Convert to JSON-compatible hash including additional properties.
99
- #
100
- # @param _options [Hash] JSON options (ignored)
101
- # @return [Hash] The combined hash
102
- def as_json(_options = {})
103
- to_hash.merge(@additional_properties)
104
- end
67
+ private
105
68
 
106
- # Convert to hash including additional properties.
107
- #
108
- # @return [Hash] The combined hash
109
- def to_h
110
- to_hash.merge(@additional_properties)
111
- end
69
+ def assign_schema_attributes(attributes, provided_keys)
70
+ defined_properties = self.class.properties.to_set
112
71
 
113
- # Allow comparison with hashes.
114
- #
115
- # @param other [Object] The object to compare with
116
- # @return [Boolean] True if equal
117
- def ==(other)
118
- case other
119
- when Hash
120
- self_hash = (self.class.schema_definition.schema[:properties] || {}).keys.each_with_object({}) do |prop, hash|
121
- hash[prop] = send(prop)
72
+ attributes.each do |key, value|
73
+ prop_name = key.to_sym
74
+ if defined_properties.include?(prop_name)
75
+ provided_keys << prop_name
76
+ public_send("#{prop_name}=", value)
77
+ elsif self.class.additional_properties_allowed?
78
+ @additional_properties[key.to_s] = value
122
79
  end
123
- other_normalized = other.transform_keys(&:to_sym)
124
- self_hash == other_normalized
125
- else
126
- super
127
80
  end
128
81
  end
129
82
  end
130
83
 
131
84
  # Class methods for schema-only models.
132
85
  module ClassMethods
133
- include SchemaMethods
134
-
135
- # Returns the schema for the model.
136
- #
137
- # @return [Hash] The schema for the model.
138
- def schema
139
- @schema ||= if defined?(@schema_definition) && @schema_definition
140
- build_schema(@schema_definition)
141
- else
142
- {}
143
- end
144
- end
145
-
146
- # Define the schema for the model using the provided block.
147
- # Unlike EasyTalk::Model, this does NOT apply any validations.
148
- #
149
- # @yield The block to define the schema.
150
- # @raise [ArgumentError] If the class does not have a name.
151
- def define_schema(&)
152
- raise ArgumentError, 'The class must have a name' unless name.present?
153
-
154
- @schema_definition = SchemaDefinition.new(name)
155
- @schema_definition.klass = self
156
- @schema_definition.instance_eval(&)
157
-
158
- # Define accessors for all properties
159
- defined_properties = (@schema_definition.schema[:properties] || {}).keys
160
- attr_accessor(*defined_properties)
161
-
162
- # NO validations are applied - this is schema-only
163
-
164
- @schema_definition
165
- end
166
-
167
- # Returns the schema definition for the model.
168
- #
169
- # @return [SchemaDefinition] The schema definition.
170
- def schema_definition
171
- @schema_definition ||= {}
172
- end
173
-
174
- # Check if additional properties are allowed.
175
- #
176
- # @return [Boolean] True if additional properties are allowed.
177
- def additional_properties_allowed?
178
- @schema_definition&.schema&.fetch(:additional_properties, false)
179
- end
180
-
181
- # Returns the property names defined in the schema.
182
- #
183
- # @return [Array<Symbol>] Array of property names as symbols.
184
- def properties
185
- (@schema_definition&.schema&.dig(:properties) || {}).keys
186
- end
187
-
188
- private
189
-
190
- # Builds the schema using the provided schema definition.
191
- #
192
- # @param schema_definition [SchemaDefinition] The schema definition.
193
- # @return [Hash] The built schema.
194
- def build_schema(schema_definition)
195
- Builders::ObjectBuilder.new(schema_definition).build
196
- end
86
+ include SchemaBase::ClassMethods
197
87
  end
198
88
  end
199
89
  end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require_relative 'builders/object_builder'
5
+ require_relative 'schema_definition'
6
+
7
+ module EasyTalk
8
+ # Shared foundation for both EasyTalk::Schema and EasyTalk::Model.
9
+ #
10
+ # This module extracts the common instance and class methods so that
11
+ # Schema (lightweight, no validations) and Model (full ActiveModel
12
+ # validations) stay in sync without code duplication.
13
+ module SchemaBase
14
+ # Instance methods shared by Schema and Model.
15
+ #
16
+ # Each including module provides its own `initialize` that:
17
+ # 1. Sets `@additional_properties = {}`
18
+ # 2. Performs attribute assignment (manually or via ActiveModel)
19
+ # 3. Calls `initialize_schema_properties(provided_keys)`
20
+ module InstanceMethods
21
+ private
22
+
23
+ def initialize_schema_properties(provided_keys)
24
+ schema_def = self.class.schema_definition
25
+ return unless schema_def.respond_to?(:schema) && schema_def.schema.is_a?(Hash)
26
+
27
+ (schema_def.schema[:properties] || {}).each do |prop_name, prop_definition|
28
+ process_property_initialization(prop_name, prop_definition, provided_keys)
29
+ end
30
+ end
31
+
32
+ def process_property_initialization(prop_name, prop_definition, provided_keys)
33
+ defined_type = prop_definition[:type]
34
+ nilable_type = defined_type.respond_to?(:nilable?) && defined_type.nilable?
35
+
36
+ apply_default_value(prop_name, prop_definition, provided_keys)
37
+
38
+ current_value = public_send(prop_name)
39
+ return if nilable_type && current_value.nil?
40
+
41
+ defined_type = T::Utils::Nilable.get_underlying_type(defined_type) if nilable_type
42
+ instantiate_nested_models(prop_name, defined_type, current_value)
43
+ end
44
+
45
+ def apply_default_value(prop_name, prop_definition, provided_keys)
46
+ return if provided_keys.include?(prop_name)
47
+
48
+ default_value = prop_definition.dig(:constraints, :default)
49
+ public_send("#{prop_name}=", EasyTalk.deep_dup(default_value)) unless default_value.nil?
50
+ end
51
+
52
+ def instantiate_nested_models(prop_name, defined_type, current_value)
53
+ if easy_talk_class?(defined_type) && current_value.is_a?(Hash)
54
+ public_send("#{prop_name}=", defined_type.new(current_value))
55
+ return
56
+ end
57
+
58
+ instantiate_array_items(prop_name, defined_type, current_value)
59
+ end
60
+
61
+ def easy_talk_class?(type)
62
+ type.is_a?(Class) && (
63
+ type.include?(EasyTalk::Model) || type.include?(EasyTalk::Schema)
64
+ )
65
+ end
66
+
67
+ def instantiate_array_items(prop_name, defined_type, current_value)
68
+ return unless defined_type.is_a?(T::Types::TypedArray) && current_value.is_a?(Array)
69
+
70
+ item_type = defined_type.type.respond_to?(:raw_type) ? defined_type.type.raw_type : nil
71
+ return unless easy_talk_class?(item_type)
72
+
73
+ instantiated = current_value.map { |item| item.is_a?(Hash) ? item_type.new(item) : item }
74
+ public_send("#{prop_name}=", instantiated)
75
+ end
76
+
77
+ public
78
+
79
+ def method_missing(method_name, *args)
80
+ method_string = method_name.to_s
81
+ if method_string.end_with?('=')
82
+ property_name = method_string.chomp('=')
83
+ if self.class.additional_properties_allowed?
84
+ @additional_properties[property_name] = args.first
85
+ else
86
+ super
87
+ end
88
+ elsif self.class.additional_properties_allowed? && @additional_properties.key?(method_string)
89
+ @additional_properties[method_string]
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ def respond_to_missing?(method_name, include_private = false)
96
+ return super unless self.class.additional_properties_allowed?
97
+
98
+ method_string = method_name.to_s
99
+ method_string.end_with?('=') || @additional_properties.key?(method_string) || super
100
+ end
101
+
102
+ def to_hash
103
+ properties_to_include = (self.class.schema_definition.schema[:properties] || {}).keys
104
+ return {} if properties_to_include.empty?
105
+
106
+ properties_to_include.to_h { |prop| [prop.to_s, send(prop)] }
107
+ end
108
+
109
+ def as_json(_options = {})
110
+ to_hash.merge(@additional_properties)
111
+ end
112
+
113
+ def to_h
114
+ to_hash.merge(@additional_properties)
115
+ end
116
+
117
+ def ==(other)
118
+ case other
119
+ when Hash
120
+ self_hash = (self.class.schema_definition.schema[:properties] || {}).keys.to_h { |prop| [prop, send(prop)] }
121
+ other_normalized = other.transform_keys(&:to_sym)
122
+ self_hash == other_normalized
123
+ else
124
+ super
125
+ end
126
+ end
127
+ end
128
+
129
+ # Class methods shared by Schema and Model.
130
+ module ClassMethods
131
+ include SchemaMethods
132
+
133
+ def schema
134
+ @schema ||= if defined?(@schema_definition) && @schema_definition
135
+ build_schema(@schema_definition)
136
+ else
137
+ {}
138
+ end
139
+ end
140
+
141
+ def define_schema(&)
142
+ raise ArgumentError, 'The class must have a name' unless name.present?
143
+
144
+ clear_schema_state!
145
+
146
+ @schema_definition = SchemaDefinition.new(name)
147
+ @schema_definition.klass = self
148
+ @schema_definition.instance_eval(&)
149
+
150
+ defined_properties = (@schema_definition.schema[:properties] || {}).keys
151
+ attr_accessor(*defined_properties)
152
+
153
+ @schema_definition
154
+ end
155
+
156
+ def schema_definition
157
+ @schema_definition ||= {}
158
+ end
159
+
160
+ def additional_properties_allowed?
161
+ ap = @schema_definition&.schema&.fetch(:additional_properties, false)
162
+ ap == true || ap.is_a?(Class) || ap.is_a?(Hash)
163
+ end
164
+
165
+ def properties
166
+ (@schema_definition&.schema&.dig(:properties) || {}).keys
167
+ end
168
+
169
+ private
170
+
171
+ def clear_schema_state!
172
+ @schema = nil
173
+ @json_schema = nil
174
+ end
175
+
176
+ def build_schema(schema_definition)
177
+ Builders::ObjectBuilder.new(schema_definition).build
178
+ end
179
+ end
180
+ end
181
+ end