@@ -60,7 +60,7 @@ def fields
60
60
[ OpenAI ::Internal ::Type ::Converter . type_info ( type_info ) , type_info ]
61
61
end
62
62
63
- setter = "#{ name_sym } ="
63
+ setter = : "#{ name_sym } ="
64
64
api_name = info . fetch ( :api_name , name_sym )
65
65
nilable = info . fetch ( :nil? , false )
66
66
const = required && !nilable ? info . fetch ( :const , OpenAI ::Internal ::OMIT ) : OpenAI ::Internal ::OMIT
@@ -77,30 +77,61 @@ def fields
77
77
type_fn : type_fn
78
78
}
79
79
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
81
94
95
+ # rubocop:disable Style/CaseEquality
96
+ # rubocop:disable Metrics/BlockLength
82
97
define_method ( name_sym ) do
83
98
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
93
110
)
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
94
131
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 )
103
132
end
133
+ # rubocop:enable Metrics/BlockLength
134
+ # rubocop:enable Style/CaseEquality
104
135
end
105
136
106
137
# @api private
@@ -200,10 +231,14 @@ class << self
200
231
#
201
232
# @param state [Hash{Symbol=>Object}] .
202
233
#
203
- # @option state [Boolean, :strong] :strictness
234
+ # @option state [Boolean] :translate_names
235
+ #
236
+ # @option state [Boolean] :strictness
204
237
#
205
238
# @option state [Hash{Symbol=>Object}] :exactness
206
239
#
240
+ # @option state [Class<StandardError>] :error
241
+ #
207
242
# @option state [Integer] :branched
208
243
#
209
244
# @return [self, Object]
@@ -217,20 +252,23 @@ def coerce(value, state:)
217
252
218
253
unless ( val = OpenAI ::Internal ::Util . coerce_hash ( value ) ) . is_a? ( Hash )
219
254
exactness [ :no ] += 1
255
+ state [ :error ] = TypeError . new ( "#{ value . class } can't be coerced into #{ Hash } " )
220
256
return value
221
257
end
222
258
exactness [ :yes ] += 1
223
259
224
260
keys = val . keys . to_set
225
261
instance = new
226
262
data = instance . to_h
263
+ viability = instance . instance_variable_get ( :@coerced )
227
264
228
265
# rubocop:disable Metrics/BlockLength
229
266
fields . each do |name , field |
230
267
mode , required , target = field . fetch_values ( :mode , :required , :type )
231
268
api_name , nilable , const = field . fetch_values ( :api_name , :nilable , :const )
269
+ src_name = state . fetch ( :translate_names ) ? api_name : name
232
270
233
- unless val . key? ( api_name )
271
+ unless val . key? ( src_name )
234
272
if required && mode != :dump && const == OpenAI ::Internal ::OMIT
235
273
exactness [ nilable ? :maybe : :no ] += 1
236
274
else
@@ -239,9 +277,10 @@ def coerce(value, state:)
239
277
next
240
278
end
241
279
242
- item = val . fetch ( api_name )
243
- keys . delete ( api_name )
280
+ item = val . fetch ( src_name )
281
+ keys . delete ( src_name )
244
282
283
+ state [ :error ] = nil
245
284
converted =
246
285
if item . nil? && ( nilable || !required )
247
286
exactness [ nilable ? :yes : :maybe ] += 1
@@ -255,6 +294,8 @@ def coerce(value, state:)
255
294
item
256
295
end
257
296
end
297
+
298
+ viability . store ( name , state . fetch ( :error ) || true )
258
299
data . store ( name , converted )
259
300
end
260
301
# rubocop:enable Metrics/BlockLength
@@ -430,7 +471,18 @@ def to_yaml(*a) = OpenAI::Internal::Type::Converter.dump(self.class, self).to_ya
430
471
# Create a new instance of a model.
431
472
#
432
473
# @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
434
486
435
487
class << self
436
488
# @api private
0 commit comments