Extract V2TypeCoercion module for bidirectional field encoding - #1926
Merged
Conversation
Consolidates request-side (encode: native → wire) and response-side
(decode: wire → native) type coercion into a shared module that handles
all V2 encoding kinds: int64_string, decimal_string, object, array,
nullable, and discriminated_union.
Previously, request_params.rb had its own coercion implementation and
stripe_object.rb only handled decimal_string with a flat equality check.
This left int64_string fields uncoerced on the response side (returning
String instead of Integer) and didn't support nested schemas like
{kind: :nullable, inner: :decimal_string}.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Remove nullable and discriminated_union cases — those belong in the DU coercion branch, not this base extraction. The module should only contain what already exists on master: int64_string, decimal_string, object, array. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
jar-stripe
marked this pull request as ready for review
August 6, 2026 19:13
jar-stripe
requested review from
xavdid
and
a lite review from Copilot
and removed request for
a team
August 6, 2026 19:13
jar-stripe
enabled auto-merge (squash)
August 6, 2026 19:14
xavdid
approved these changes
Aug 6, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (2)
lib/stripe/v2_type_coercion.rb:77
coerce_decimal_stringfalls through tonilif an unexpecteddirectionis passed (because thecasestatement has noelse). This can silently propagatenilvalues if the method is ever called with an invalid direction; consider raising (or returningvalue) to make failures explicit.
def coerce_decimal_string(value, direction)
case direction
when :encode
case value
when BigDecimal then value.to_s("F")
when Integer, Float then value.to_s
when Array then value.map { |v| coerce_decimal_string(v, direction) }
else value
end
when :decode
case value
when String then BigDecimal(value)
when Array then value.map { |v| coerce_decimal_string(v, direction) }
else value
end
end
end
lib/stripe/v2_type_coercion.rb:59
coerce_int64_stringfalls through tonilif an unexpecteddirectionis passed (because thecasestatement has noelse). That can silently turn values intonilif the caller ever passes something other than:encode/:decode. Consider raising (or at least returningvalue) on unknown directions to avoid surprising behavior.
This issue also appears on line 61 of the same file.
def coerce_int64_string(value, direction)
case direction
when :encode
case value
when Integer then value.to_s
when Array then value.map { |v| v.is_a?(Integer) ? v.to_s : v }
else value
end
when :decode
case value
when String then Integer(value)
when Array then value.map { |v| v.is_a?(String) ? Integer(v) : v }
else value
end
end
end
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (3)
lib/stripe/stripe_object.rb:163
V2TypeCoercion.coerce_valueis applied afterconvert_value_with_inner_types, butconvert_value_with_inner_typesconverts nested Hashes intoStripeObjectinstances (viaUtil.convert_to_stripe_object_with_params, which returns aStripeObjectfor any Hash). This means composite encodings like{ kind: :object, fields: ... }/{ kind: :array, element: ... }will not decode recursively becauseV2TypeCoerciononly descends intoHash/Arrayelements, notStripeObject. To make nested response coercion actually work, coerce the rawvbefore converting it to StripeObjects.
@values[k] = convert_value_with_inner_types(k, v, opts)
encoding = self.class.field_encodings[k.to_sym]
if encoding
@values[k] = V2TypeCoercion.coerce_value(@values[k], encoding, direction: :decode)
end
lib/stripe/v2_type_coercion.rb:57
Integer(value)will raiseArgumentErrorif the API ever returns an invalid int64 string (e.g., empty string). Sinceint64_stringresponse coercion is newly introduced, it would be safer to avoid raising during deserialization and instead fall back to the original value when parsing fails.
when :decode
case value
when String then Integer(value)
when Array then value.map { |v| v.is_a?(String) ? Integer(v) : v }
else value
lib/stripe/stripe_object.rb:163
- This change adds response-side
:int64_stringcoercion toStripeObject#update_attributes, but there isn't an integration test that exercisesStripeObject.construct_fromwith a resource definingfield_encodingsfor:int64_string(similar to the existing decimal_string coverage). Adding a focused test would help prevent regressions in the update_attributes integration (especially for nested schemas).
encoding = self.class.field_encodings[k.to_sym]
if encoding
@values[k] = V2TypeCoercion.coerce_value(@values[k], encoding, direction: :decode)
end
Mirrors the existing stripe_object_decimal_test.rb pattern for int64_string, and adds a nested-resource test proving that coercion works through each StripeObject's own field_encodings during update_attributes (not through V2TypeCoercion recursing into StripeObjects). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
Sorbet doesn't resolve implicit Kernel methods (Integer, BigDecimal, raise) on module_function contexts. Qualify each call with Kernel. to satisfy the type checker without changing runtime behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why?
Ruby's V2 type coercion was split across two locations with different capabilities:
request_params.rb) — handledint64_string,decimal_string,object,array. Converted native types → wire strings.stripe_object.rb) — only handleddecimal_stringwith a flat==check. Noint64_string, no nested schemas.This meant
int64_stringresponse fields (e.g. GA v2:v2.commerce.product_catalog_import) returnedStringinstead ofIntegeras specified in the Sorbet typeWhat?
Stripe::V2TypeCoercionmodule that handles existing encoding kinds (int64_string,decimal_string,object,array) in both directions (:encodefor request,:decodefor response)RequestParams.coerce_valuenow delegates to the module withdirection: :encodeStripeObject#update_attributesnow uses the module withdirection: :decode, fixing int64_string response coercionSee Also
Changelog
int64_stringresponse fields so they returnInteger(to match the field's Sorbet type) instead ofString