@@ -60,7 +60,7 @@ def fields
6060 [ OpenAI ::Internal ::Type ::Converter . type_info ( type_info ) , type_info ]
6161 end
6262
63- setter = "#{ name_sym } ="
63+ setter = : "#{ name_sym } ="
6464 api_name = info . fetch ( :api_name , name_sym )
6565 nilable = info . fetch ( :nil? , false )
6666 const = required && !nilable ? info . fetch ( :const , OpenAI ::Internal ::OMIT ) : OpenAI ::Internal ::OMIT
@@ -77,30 +77,61 @@ def fields
7777 type_fn : type_fn
7878 }
7979
80- define_method ( setter ) { @data . store ( name_sym , _1 ) }
80+ define_method ( setter ) do |value |
81+ target = type_fn . call
82+ state = OpenAI ::Internal ::Type ::Converter . new_coerce_state ( translate_names : false )
83+ coerced = OpenAI ::Internal ::Type ::Converter . coerce ( target , value , state : state )
84+ error = @coerced . store ( name_sym , state . fetch ( :error ) || true )
85+ stored =
86+ case [ target , error ]
87+ in [ OpenAI ::Internal ::Type ::Converter | Symbol , nil ]
88+ coerced
89+ else
90+ value
91+ end
92+ @data . store ( name_sym , stored )
93+ end
8194
95+ # rubocop:disable Style/CaseEquality
96+ # rubocop:disable Metrics/BlockLength
8297 define_method ( name_sym ) do
8398 target = type_fn . call
84- value = @data . fetch ( name_sym ) { const == OpenAI ::Internal ::OMIT ? nil : const }
85- state = { strictness : :strong , exactness : { yes : 0 , no : 0 , maybe : 0 } , branched : 0 }
86- if ( nilable || !required ) && value . nil?
87- nil
88- else
89- OpenAI ::Internal ::Type ::Converter . coerce (
90- target ,
91- value ,
92- state : state
99+
100+ case @coerced [ name_sym ]
101+ in true | false if OpenAI ::Internal ::Type ::Converter === target
102+ @data . fetch ( name_sym )
103+ in ::StandardError => e
104+ raise OpenAI ::Errors ::ConversionError . new (
105+ on : self . class ,
106+ method : __method__ ,
107+ target : target ,
108+ value : @data . fetch ( name_sym ) ,
109+ cause : e
93110 )
111+ else
112+ Kernel . then do
113+ value = @data . fetch ( name_sym ) { const == OpenAI ::Internal ::OMIT ? nil : const }
114+ state = OpenAI ::Internal ::Type ::Converter . new_coerce_state ( translate_names : false )
115+ if ( nilable || !required ) && value . nil?
116+ nil
117+ else
118+ OpenAI ::Internal ::Type ::Converter . coerce (
119+ target , value , state : state
120+ )
121+ end
122+ rescue StandardError => e
123+ raise OpenAI ::Errors ::ConversionError . new (
124+ on : self . class ,
125+ method : __method__ ,
126+ target : target ,
127+ value : value ,
128+ cause : e
129+ )
130+ end
94131 end
95- rescue StandardError => e
96- cls = self . class . name . split ( "::" ) . last
97- message = [
98- "Failed to parse #{ cls } .#{ __method__ } from #{ value . class } to #{ target . inspect } ." ,
99- "To get the unparsed API response, use #{ cls } [#{ __method__ . inspect } ]." ,
100- "Cause: #{ e . message } "
101- ] . join ( " " )
102- raise OpenAI ::Errors ::ConversionError . new ( message )
103132 end
133+ # rubocop:enable Metrics/BlockLength
134+ # rubocop:enable Style/CaseEquality
104135 end
105136
106137 # @api private
@@ -200,10 +231,14 @@ class << self
200231 #
201232 # @param state [Hash{Symbol=>Object}] .
202233 #
203- # @option state [Boolean, :strong] :strictness
234+ # @option state [Boolean] :translate_names
235+ #
236+ # @option state [Boolean] :strictness
204237 #
205238 # @option state [Hash{Symbol=>Object}] :exactness
206239 #
240+ # @option state [Class<StandardError>] :error
241+ #
207242 # @option state [Integer] :branched
208243 #
209244 # @return [self, Object]
@@ -217,20 +252,23 @@ def coerce(value, state:)
217252
218253 unless ( val = OpenAI ::Internal ::Util . coerce_hash ( value ) ) . is_a? ( Hash )
219254 exactness [ :no ] += 1
255+ state [ :error ] = TypeError . new ( "#{ value . class } can't be coerced into #{ Hash } " )
220256 return value
221257 end
222258 exactness [ :yes ] += 1
223259
224260 keys = val . keys . to_set
225261 instance = new
226262 data = instance . to_h
263+ viability = instance . instance_variable_get ( :@coerced )
227264
228265 # rubocop:disable Metrics/BlockLength
229266 fields . each do |name , field |
230267 mode , required , target = field . fetch_values ( :mode , :required , :type )
231268 api_name , nilable , const = field . fetch_values ( :api_name , :nilable , :const )
269+ src_name = state . fetch ( :translate_names ) ? api_name : name
232270
233- unless val . key? ( api_name )
271+ unless val . key? ( src_name )
234272 if required && mode != :dump && const == OpenAI ::Internal ::OMIT
235273 exactness [ nilable ? :maybe : :no ] += 1
236274 else
@@ -239,9 +277,10 @@ def coerce(value, state:)
239277 next
240278 end
241279
242- item = val . fetch ( api_name )
243- keys . delete ( api_name )
280+ item = val . fetch ( src_name )
281+ keys . delete ( src_name )
244282
283+ state [ :error ] = nil
245284 converted =
246285 if item . nil? && ( nilable || !required )
247286 exactness [ nilable ? :yes : :maybe ] += 1
@@ -255,6 +294,8 @@ def coerce(value, state:)
255294 item
256295 end
257296 end
297+
298+ viability . store ( name , state . fetch ( :error ) || true )
258299 data . store ( name , converted )
259300 end
260301 # rubocop:enable Metrics/BlockLength
@@ -430,7 +471,18 @@ def to_yaml(*a) = OpenAI::Internal::Type::Converter.dump(self.class, self).to_ya
430471 # Create a new instance of a model.
431472 #
432473 # @param data [Hash{Symbol=>Object}, self]
433- def initialize ( data = { } ) = ( @data = OpenAI ::Internal ::Util . coerce_hash! ( data ) . to_h )
474+ def initialize ( data = { } )
475+ @data = { }
476+ @coerced = { }
477+ OpenAI ::Internal ::Util . coerce_hash! ( data ) . each do
478+ if self . class . known_fields . key? ( _1 )
479+ public_send ( :"#{ _1 } =" , _2 )
480+ else
481+ @data . store ( _1 , _2 )
482+ @coerced . store ( _1 , false )
483+ end
484+ end
485+ end
434486
435487 class << self
436488 # @api private
0 commit comments