Skip to content

Extract V2TypeCoercion module for bidirectional field encoding - #1926

Merged
jar-stripe merged 9 commits into
masterfrom
jar/v2-type-coercion
Aug 11, 2026
Merged

Extract V2TypeCoercion module for bidirectional field encoding#1926
jar-stripe merged 9 commits into
masterfrom
jar/v2-type-coercion

Conversation

@jar-stripe

@jar-stripe jar-stripe commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Why?

Ruby's V2 type coercion was split across two locations with different capabilities:

  1. Request side (request_params.rb) — handled int64_string, decimal_string, object, array. Converted native types → wire strings.
  2. Response side (stripe_object.rb) — only handled decimal_string with a flat == check. No int64_string, no nested schemas.

This meant int64_string response fields (e.g. GA v2: v2.commerce.product_catalog_import) returned String instead of Integer as specified in the Sorbet type

What?

  • Extracts a shared Stripe::V2TypeCoercion module that handles existing encoding kinds (int64_string, decimal_string, object, array) in both directions (:encode for request, :decode for response)
  • RequestParams.coerce_value now delegates to the module with direction: :encode
  • StripeObject#update_attributes now uses the module with direction: :decode, fixing int64_string response coercion
  • Adds test coverage for the module (29 tests covering both directions)

See Also

Changelog

  • Fixes V2 int64_string response fields so they return Integer (to match the field's Sorbet type) instead of String

jar-stripe and others added 2 commits August 6, 2026 11:57
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
jar-stripe marked this pull request as ready for review August 6, 2026 19:13
@jar-stripe
jar-stripe requested a review from a team as a code owner August 6, 2026 19:13
@jar-stripe
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
jar-stripe enabled auto-merge (squash) August 6, 2026 19:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@jar-stripe
jar-stripe requested a lite review from Copilot August 6, 2026 19:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_string falls through to nil if an unexpected direction is passed (because the case statement has no else). This can silently propagate nil values if the method is ever called with an invalid direction; consider raising (or returning value) 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_string falls through to nil if an unexpected direction is passed (because the case statement has no else). That can silently turn values into nil if the caller ever passes something other than :encode/:decode. Consider raising (or at least returning value) 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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_value is applied after convert_value_with_inner_types, but convert_value_with_inner_types converts nested Hashes into StripeObject instances (via Util.convert_to_stripe_object_with_params, which returns a StripeObject for any Hash). This means composite encodings like { kind: :object, fields: ... } / { kind: :array, element: ... } will not decode recursively because V2TypeCoercion only descends into Hash/Array elements, not StripeObject. To make nested response coercion actually work, coerce the raw v before 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 raise ArgumentError if the API ever returns an invalid int64 string (e.g., empty string). Since int64_string response 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_string coercion to StripeObject#update_attributes, but there isn't an integration test that exercises StripeObject.construct_from with a resource defining field_encodings for :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

jar-stripe and others added 4 commits August 10, 2026 19:42
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
@jar-stripe
jar-stripe merged commit fcedaaa into master Aug 11, 2026
14 checks passed
@jar-stripe
jar-stripe deleted the jar/v2-type-coercion branch August 11, 2026 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants