@@ -60,7 +60,7 @@ def fields
6060 [ BrandDev ::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 = if required && !nilable
@@ -84,30 +84,61 @@ def fields
8484 type_fn : type_fn
8585 }
8686
87- define_method ( setter ) { @data . store ( name_sym , _1 ) }
87+ define_method ( setter ) do |value |
88+ target = type_fn . call
89+ state = BrandDev ::Internal ::Type ::Converter . new_coerce_state ( translate_names : false )
90+ coerced = BrandDev ::Internal ::Type ::Converter . coerce ( target , value , state : state )
91+ status = @coerced . store ( name_sym , state . fetch ( :error ) || true )
92+ stored =
93+ case [ target , status ]
94+ in [ BrandDev ::Internal ::Type ::Converter | Symbol , true ]
95+ coerced
96+ else
97+ value
98+ end
99+ @data . store ( name_sym , stored )
100+ end
88101
102+ # rubocop:disable Style/CaseEquality
103+ # rubocop:disable Metrics/BlockLength
89104 define_method ( name_sym ) do
90105 target = type_fn . call
91- value = @data . fetch ( name_sym ) { const == BrandDev ::Internal ::OMIT ? nil : const }
92- state = { strictness : :strong , exactness : { yes : 0 , no : 0 , maybe : 0 } , branched : 0 }
93- if ( nilable || !required ) && value . nil?
94- nil
95- else
96- BrandDev ::Internal ::Type ::Converter . coerce (
97- target ,
98- value ,
99- state : state
106+
107+ case @coerced [ name_sym ]
108+ in true | false if BrandDev ::Internal ::Type ::Converter === target
109+ @data . fetch ( name_sym )
110+ in ::StandardError => e
111+ raise BrandDev ::Errors ::ConversionError . new (
112+ on : self . class ,
113+ method : __method__ ,
114+ target : target ,
115+ value : @data . fetch ( name_sym ) ,
116+ cause : e
100117 )
118+ else
119+ Kernel . then do
120+ value = @data . fetch ( name_sym ) { const == BrandDev ::Internal ::OMIT ? nil : const }
121+ state = BrandDev ::Internal ::Type ::Converter . new_coerce_state ( translate_names : false )
122+ if ( nilable || !required ) && value . nil?
123+ nil
124+ else
125+ BrandDev ::Internal ::Type ::Converter . coerce (
126+ target , value , state : state
127+ )
128+ end
129+ rescue StandardError => e
130+ raise BrandDev ::Errors ::ConversionError . new (
131+ on : self . class ,
132+ method : __method__ ,
133+ target : target ,
134+ value : value ,
135+ cause : e
136+ )
137+ end
101138 end
102- rescue StandardError => e
103- cls = self . class . name . split ( "::" ) . last
104- message = [
105- "Failed to parse #{ cls } .#{ __method__ } from #{ value . class } to #{ target . inspect } ." ,
106- "To get the unparsed API response, use #{ cls } [#{ __method__ . inspect } ]." ,
107- "Cause: #{ e . message } "
108- ] . join ( " " )
109- raise BrandDev ::Errors ::ConversionError . new ( message )
110139 end
140+ # rubocop:enable Metrics/BlockLength
141+ # rubocop:enable Style/CaseEquality
111142 end
112143
113144 # @api private
@@ -207,37 +238,44 @@ class << self
207238 #
208239 # @param state [Hash{Symbol=>Object}] .
209240 #
210- # @option state [Boolean, :strong] :strictness
241+ # @option state [Boolean] :translate_names
242+ #
243+ # @option state [Boolean] :strictness
211244 #
212245 # @option state [Hash{Symbol=>Object}] :exactness
213246 #
247+ # @option state [Class<StandardError>] :error
248+ #
214249 # @option state [Integer] :branched
215250 #
216251 # @return [self, Object]
217252 def coerce ( value , state :)
218253 exactness = state . fetch ( :exactness )
219254
220- if value . is_a? ( self . class )
255+ if value . is_a? ( self )
221256 exactness [ :yes ] += 1
222257 return value
223258 end
224259
225260 unless ( val = BrandDev ::Internal ::Util . coerce_hash ( value ) ) . is_a? ( Hash )
226261 exactness [ :no ] += 1
262+ state [ :error ] = TypeError . new ( "#{ value . class } can't be coerced into #{ Hash } " )
227263 return value
228264 end
229265 exactness [ :yes ] += 1
230266
231267 keys = val . keys . to_set
232268 instance = new
233269 data = instance . to_h
270+ status = instance . instance_variable_get ( :@coerced )
234271
235272 # rubocop:disable Metrics/BlockLength
236273 fields . each do |name , field |
237274 mode , required , target = field . fetch_values ( :mode , :required , :type )
238275 api_name , nilable , const = field . fetch_values ( :api_name , :nilable , :const )
276+ src_name = state . fetch ( :translate_names ) ? api_name : name
239277
240- unless val . key? ( api_name )
278+ unless val . key? ( src_name )
241279 if required && mode != :dump && const == BrandDev ::Internal ::OMIT
242280 exactness [ nilable ? :maybe : :no ] += 1
243281 else
@@ -246,9 +284,10 @@ def coerce(value, state:)
246284 next
247285 end
248286
249- item = val . fetch ( api_name )
250- keys . delete ( api_name )
287+ item = val . fetch ( src_name )
288+ keys . delete ( src_name )
251289
290+ state [ :error ] = nil
252291 converted =
253292 if item . nil? && ( nilable || !required )
254293 exactness [ nilable ? :yes : :maybe ] += 1
@@ -262,6 +301,8 @@ def coerce(value, state:)
262301 item
263302 end
264303 end
304+
305+ status . store ( name , state . fetch ( :error ) || true )
265306 data . store ( name , converted )
266307 end
267308 # rubocop:enable Metrics/BlockLength
@@ -437,7 +478,18 @@ def to_yaml(*a) = BrandDev::Internal::Type::Converter.dump(self.class, self).to_
437478 # Create a new instance of a model.
438479 #
439480 # @param data [Hash{Symbol=>Object}, self]
440- def initialize ( data = { } ) = ( @data = BrandDev ::Internal ::Util . coerce_hash! ( data ) . to_h )
481+ def initialize ( data = { } )
482+ @data = { }
483+ @coerced = { }
484+ BrandDev ::Internal ::Util . coerce_hash! ( data ) . each do
485+ if self . class . known_fields . key? ( _1 )
486+ public_send ( :"#{ _1 } =" , _2 )
487+ else
488+ @data . store ( _1 , _2 )
489+ @coerced . store ( _1 , false )
490+ end
491+ end
492+ end
441493
442494 class << self
443495 # @api private
0 commit comments