@@ -60,7 +60,7 @@ def fields
6060 [ Orb ::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 , Orb ::Internal ::OMIT ) : Orb ::Internal ::OMIT
@@ -77,30 +77,63 @@ 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 = Orb ::Internal ::Type ::Converter . new_coerce_state ( translate_names : false )
83+ coerced = Orb ::Internal ::Type ::Converter . coerce ( target , value , state : state )
84+ status = @coerced . store ( name_sym , state . fetch ( :error ) || true )
85+ stored =
86+ case [ target , status ]
87+ in [ Orb ::Internal ::Type ::Converter | Symbol , true ]
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 == Orb ::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- Orb ::Internal ::Type ::Converter . coerce (
90- target ,
91- value ,
92- state : state
99+
100+ case @coerced [ name_sym ]
101+ in true | false if Orb ::Internal ::Type ::Converter === target
102+ @data . fetch ( name_sym )
103+ in ::StandardError => e
104+ raise Orb ::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 == Orb ::Internal ::OMIT ? nil : const }
114+ state = Orb ::Internal ::Type ::Converter . new_coerce_state ( translate_names : false )
115+ if ( nilable || !required ) && value . nil?
116+ nil
117+ else
118+ Orb ::Internal ::Type ::Converter . coerce (
119+ target ,
120+ value ,
121+ state : state
122+ )
123+ end
124+ rescue StandardError => e
125+ raise Orb ::Errors ::ConversionError . new (
126+ on : self . class ,
127+ method : __method__ ,
128+ target : target ,
129+ value : value ,
130+ cause : e
131+ )
132+ end
94133 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 Orb ::Errors ::ConversionError . new ( message )
103134 end
135+ # rubocop:enable Metrics/BlockLength
136+ # rubocop:enable Style/CaseEquality
104137 end
105138
106139 # @api private
@@ -200,37 +233,44 @@ class << self
200233 #
201234 # @param state [Hash{Symbol=>Object}] .
202235 #
203- # @option state [Boolean, :strong] :strictness
236+ # @option state [Boolean] :translate_names
237+ #
238+ # @option state [Boolean] :strictness
204239 #
205240 # @option state [Hash{Symbol=>Object}] :exactness
206241 #
242+ # @option state [Class<StandardError>] :error
243+ #
207244 # @option state [Integer] :branched
208245 #
209246 # @return [self, Object]
210247 def coerce ( value , state :)
211248 exactness = state . fetch ( :exactness )
212249
213- if value . is_a? ( self . class )
250+ if value . is_a? ( self )
214251 exactness [ :yes ] += 1
215252 return value
216253 end
217254
218255 unless ( val = Orb ::Internal ::Util . coerce_hash ( value ) ) . is_a? ( Hash )
219256 exactness [ :no ] += 1
257+ state [ :error ] = TypeError . new ( "#{ value . class } can't be coerced into #{ Hash } " )
220258 return value
221259 end
222260 exactness [ :yes ] += 1
223261
224262 keys = val . keys . to_set
225263 instance = new
226264 data = instance . to_h
265+ status = instance . instance_variable_get ( :@coerced )
227266
228267 # rubocop:disable Metrics/BlockLength
229268 fields . each do |name , field |
230269 mode , required , target = field . fetch_values ( :mode , :required , :type )
231270 api_name , nilable , const = field . fetch_values ( :api_name , :nilable , :const )
271+ src_name = state . fetch ( :translate_names ) ? api_name : name
232272
233- unless val . key? ( api_name )
273+ unless val . key? ( src_name )
234274 if required && mode != :dump && const == Orb ::Internal ::OMIT
235275 exactness [ nilable ? :maybe : :no ] += 1
236276 else
@@ -239,9 +279,10 @@ def coerce(value, state:)
239279 next
240280 end
241281
242- item = val . fetch ( api_name )
243- keys . delete ( api_name )
282+ item = val . fetch ( src_name )
283+ keys . delete ( src_name )
244284
285+ state [ :error ] = nil
245286 converted =
246287 if item . nil? && ( nilable || !required )
247288 exactness [ nilable ? :yes : :maybe ] += 1
@@ -255,6 +296,8 @@ def coerce(value, state:)
255296 item
256297 end
257298 end
299+
300+ status . store ( name , state . fetch ( :error ) || true )
258301 data . store ( name , converted )
259302 end
260303 # rubocop:enable Metrics/BlockLength
@@ -430,7 +473,18 @@ def to_yaml(*a) = Orb::Internal::Type::Converter.dump(self.class, self).to_yaml(
430473 # Create a new instance of a model.
431474 #
432475 # @param data [Hash{Symbol=>Object}, self]
433- def initialize ( data = { } ) = ( @data = Orb ::Internal ::Util . coerce_hash! ( data ) . to_h )
476+ def initialize ( data = { } )
477+ @data = { }
478+ @coerced = { }
479+ Orb ::Internal ::Util . coerce_hash! ( data ) . each do
480+ if self . class . known_fields . key? ( _1 )
481+ public_send ( :"#{ _1 } =" , _2 )
482+ else
483+ @data . store ( _1 , _2 )
484+ @coerced . store ( _1 , false )
485+ end
486+ end
487+ end
434488
435489 class << self
436490 # @api private
0 commit comments