inttegro 4.2.2 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 898d14cfaa38b878c20565b4522fe67d87147f01850656bf5a7817da99c955a2
4
- data.tar.gz: 32a2dc36592ebdfa52c4076687d7b5c4512ac887de57285fccb843815a0e64e7
3
+ metadata.gz: 523d5315b309ba6414799b21dfb52eef979e45e4770a0056dff87e787d96159f
4
+ data.tar.gz: 92b8163c7f228c73dea65a03ec6680f70722744ad731137ddac6c2215c5e2dd1
5
5
  SHA512:
6
- metadata.gz: 5c3dff6b6e94712d541d1c4aabe3ee49227cd35ce1d4decc7656eff992be17ad0fec34177b5e7e3fd84514e0e79558f7ee552bb889f025cacb77b69381d9fbc2
7
- data.tar.gz: f55e5ed4ea3703b3ef98042add688bb97224dfcebe5c102455544a9984745bf7d855aa8bd3ae30f9971c3de2af1ecac68f11f5ebadafec2a4212c2b933c68ba1
6
+ metadata.gz: aef00a637c73a440acbd0d5a3150e93f63ea38b251a09a19830ddf6e372179a487d4ed817f3cb07f93f526abd4e7907ec7493b8994c8db74ce7a940115f07869
7
+ data.tar.gz: 71954cf59f4cc95a178b6f363bcb92e9c7cabb7123add536485089011c71b98e1b0f18cfe0efa5f865c0e4dd9cd8534334b17c6b99088a6436ad4a9cd6bdadd5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [4.3.0] - 2026-09-06
4
+
5
+ - Added opt-in, typed error reporting to application-owned collectors with privacy-safe payloads, stable fingerprints, isolated reporter failures, and no reporting work when unconfigured.
6
+
3
7
  ## [4.2.2] - 2026-09-04
4
8
 
5
9
  - Constrained OpenTelemetry dependencies to releases that preserve the SDK's Ruby 3.0 runtime support, while declaring `logger` for Ruby 4 compatibility.
data/README.md CHANGED
@@ -87,6 +87,21 @@ inttegro = Inttegro::Client.new(api_key: ENV.fetch("INTTEGRO_API_KEY"))
87
87
 
88
88
  Spans are named after logical operations such as `inttegro.orders.create`. HTTP attempts, response receipt, and decoding are span events. API keys, bodies, resource IDs, dynamic URLs, and exception messages are never recorded. See [SDK observability](https://studio.inttegro.com/sdk-observability) for the complete contract and set `telemetry_enabled: false` when needed.
89
89
 
90
+ ### Report SDK failures
91
+
92
+ Provide an application-owned reporter to receive one immutable, typed, privacy-safe report after an SDK operation finally fails. The default `:unexpected` policy reports transport, timeout, decoding, SDK, `unknown_error`, and server-side failures while leaving normal 4xx API errors alone:
93
+
94
+ ```ruby
95
+ inttegro = Inttegro::Client.new(
96
+ api_key: ENV.fetch("INTTEGRO_API_KEY"),
97
+ error_reporter: ->(report) { ErrorCollector.enqueue(report.serialize) }
98
+ )
99
+ ```
100
+
101
+ Use `error_reporting_policy: :all` to include expected API failures; cancellations are never reported. Reports contain the logical operation, static route, server host, status and request IDs when available, duration, safe API error codes, SDK identity, stable fingerprint, exception type, and trace IDs when tracing is active. They exclude credentials, headers, bodies, resource IDs, dynamic URLs, exception messages, and stack traces. Reporter failures are isolated and the original SDK error is still raised.
102
+
103
+ Error reporting is completely opt-in. Without `error_reporter`, the SDK does not calculate report metadata, create an event ID or timestamp, allocate a report, or serialize a payload.
104
+
90
105
  ## Work with the API
91
106
 
92
107
  The SDK covers orders and checkout, customers, products and prices, purchase intents, payment methods, balances, payouts and refunds, notifications, files, application settings, keys, and country specifications. Resources use snake-case readers such as `purchase_intents` and `payment_methods`.
@@ -111,7 +111,9 @@ module Inttegro
111
111
  open_timeout: T.nilable(Numeric),
112
112
  adapter: T.nilable(Types::Adapter),
113
113
  telemetry_enabled: T::Boolean,
114
- tracer_provider: T.nilable(OpenTelemetry::Trace::TracerProvider)
114
+ tracer_provider: T.nilable(OpenTelemetry::Trace::TracerProvider),
115
+ error_reporter: T.nilable(ErrorReporter),
116
+ error_reporting_policy: Symbol
115
117
  ).void
116
118
  end
117
119
  def initialize(
@@ -123,7 +125,9 @@ module Inttegro
123
125
  open_timeout: 10,
124
126
  adapter: nil,
125
127
  telemetry_enabled: true,
126
- tracer_provider: nil
128
+ tracer_provider: nil,
129
+ error_reporter: nil,
130
+ error_reporting_policy: :unexpected
127
131
  )
128
132
  provided_token = token || token_value
129
133
  api_key ||= provided_token
@@ -134,7 +138,9 @@ module Inttegro
134
138
  open_timeout: open_timeout,
135
139
  adapter: adapter,
136
140
  telemetry_enabled: telemetry_enabled,
137
- tracer_provider: tracer_provider
141
+ tracer_provider: tracer_provider,
142
+ error_reporter: error_reporter,
143
+ error_reporting_policy: error_reporting_policy
138
144
  ), HTTPClient)
139
145
 
140
146
  @orders = T.let(Resources::Orders.new(@http), Resources::Orders)
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Inttegro
7
+ # SDK identity included in an ErrorReport.
8
+ class SDKReportContext < T::Struct
9
+ const :language, String
10
+ const :version, String
11
+ end
12
+
13
+ # Bounded HTTP metadata included in an ErrorReport.
14
+ class HTTPReportContext < T::Struct
15
+ const :request_method, String
16
+ const :server_address, String
17
+ const :duration_ms, Integer
18
+ const :route, T.nilable(String), default: nil
19
+ const :status_code, T.nilable(Integer), default: nil
20
+ const :request_id, T.nilable(String), default: nil
21
+ end
22
+
23
+ # Machine-readable API error metadata included in an ErrorReport.
24
+ class APIErrorReportContext < T::Struct
25
+ const :type, T.nilable(String), default: nil
26
+ const :code, T.nilable(String), default: nil
27
+ const :fix_code, T.nilable(String), default: nil
28
+ end
29
+
30
+ # Trace identifiers included when application tracing is enabled.
31
+ class TraceReportContext < T::Struct
32
+ const :trace_id, String
33
+ const :span_id, String
34
+ end
35
+
36
+ # Privacy-safe description of a failed Inttegro SDK operation.
37
+ class ErrorReport < T::Struct
38
+ const :schema_version, Integer
39
+ const :event_id, String
40
+ const :occurred_at, String
41
+ const :severity, String
42
+ const :category, String
43
+ const :operation, String
44
+ const :sdk, SDKReportContext
45
+ const :http, HTTPReportContext
46
+ const :exception_type, String
47
+ const :fingerprint, String
48
+ const :api_error, T.nilable(APIErrorReportContext), default: nil
49
+ const :trace, T.nilable(TraceReportContext), default: nil
50
+ end
51
+
52
+ ErrorReporter = T.type_alias { T.proc.params(report: ErrorReport).void }
53
+ end
@@ -4,9 +4,21 @@
4
4
  require "sorbet-runtime"
5
5
 
6
6
  require_relative "types"
7
+ require_relative "error_reporting"
7
8
 
8
9
  module Inttegro
9
- class Error < StandardError; end
10
+ class Error < StandardError
11
+ extend T::Sig
12
+
13
+ sig { returns(T.nilable(ErrorReport)) }
14
+ attr_accessor :report
15
+
16
+ sig { params(message: String).void }
17
+ def initialize(message)
18
+ super(message)
19
+ @report = T.let(nil, T.nilable(ErrorReport))
20
+ end
21
+ end
10
22
 
11
23
  class NetworkError < Error
12
24
  extend T::Sig
@@ -30,7 +42,7 @@ module Inttegro
30
42
  attr_reader :status
31
43
 
32
44
  sig { returns(T.nilable(String)) }
33
- attr_reader :code, :type, :url, :detail, :fix_code, :cause, :body
45
+ attr_reader :code, :type, :url, :detail, :fix_code, :cause, :body, :request_id
34
46
 
35
47
  sig { returns(Types::WireValue) }
36
48
  attr_reader :data
@@ -46,7 +58,8 @@ module Inttegro
46
58
  fix_code: T.nilable(String),
47
59
  cause: T.nilable(String),
48
60
  body: T.nilable(String),
49
- data: Types::WireValue
61
+ data: Types::WireValue,
62
+ request_id: T.nilable(String)
50
63
  ).void
51
64
  end
52
65
  def initialize(
@@ -59,7 +72,8 @@ module Inttegro
59
72
  fix_code: nil,
60
73
  cause: nil,
61
74
  body: nil,
62
- data: nil
75
+ data: nil,
76
+ request_id: nil
63
77
  )
64
78
  super(message)
65
79
  @status = status
@@ -71,6 +85,7 @@ module Inttegro
71
85
  @cause = cause
72
86
  @body = body
73
87
  @data = data
88
+ @request_id = request_id
74
89
  end
75
90
  end
76
91
 
@@ -94,7 +109,8 @@ module Inttegro
94
109
  cause: T.nilable(String),
95
110
  body: T.nilable(String),
96
111
  data: Types::WireValue,
97
- retry_after: T.nilable(Integer)
112
+ retry_after: T.nilable(Integer),
113
+ request_id: T.nilable(String)
98
114
  ).void
99
115
  end
100
116
  def initialize(
@@ -108,7 +124,8 @@ module Inttegro
108
124
  cause: nil,
109
125
  body: nil,
110
126
  data: nil,
111
- retry_after: nil
127
+ retry_after: nil,
128
+ request_id: nil
112
129
  )
113
130
  super(
114
131
  message,
@@ -120,7 +137,8 @@ module Inttegro
120
137
  fix_code: fix_code,
121
138
  cause: cause,
122
139
  body: body,
123
- data: data
140
+ data: data,
141
+ request_id: request_id
124
142
  )
125
143
  @retry_after = retry_after
126
144
  end
@@ -37,7 +37,9 @@ module Inttegro
37
37
  open_timeout: T.nilable(Numeric),
38
38
  adapter: T.nilable(Types::Adapter),
39
39
  telemetry_enabled: T::Boolean,
40
- tracer_provider: T.nilable(OpenTelemetry::Trace::TracerProvider)
40
+ tracer_provider: T.nilable(OpenTelemetry::Trace::TracerProvider),
41
+ error_reporter: T.nilable(ErrorReporter),
42
+ error_reporting_policy: Symbol
41
43
  ).void
42
44
  end
43
45
  def initialize(
@@ -47,7 +49,9 @@ module Inttegro
47
49
  open_timeout: 10,
48
50
  adapter: nil,
49
51
  telemetry_enabled: true,
50
- tracer_provider: nil
52
+ tracer_provider: nil,
53
+ error_reporter: nil,
54
+ error_reporting_policy: :unexpected
51
55
  )
52
56
  @api_key = T.let(api_key || "", String)
53
57
  raise ArgumentError, "api_key is required" if @api_key.strip.empty?
@@ -60,7 +64,13 @@ module Inttegro
60
64
  @open_timeout = T.let(open_timeout, T.nilable(Numeric))
61
65
  @adapter = T.let(adapter, T.nilable(Types::Adapter))
62
66
  @telemetry = T.let(
63
- Telemetry.new(Inttegro::VERSION, enabled: telemetry_enabled, tracer_provider: tracer_provider),
67
+ Telemetry.new(
68
+ Inttegro::VERSION,
69
+ enabled: telemetry_enabled,
70
+ tracer_provider: tracer_provider,
71
+ error_reporter: error_reporter,
72
+ error_reporting_policy: error_reporting_policy
73
+ ),
64
74
  Telemetry
65
75
  )
66
76
  end
@@ -504,6 +514,7 @@ module Inttegro
504
514
  detail = payload[:detail]
505
515
  fix_code = payload[:fix_code]
506
516
  cause = payload[:cause]
517
+ request_id = response["x-request-id"] || response["X-Request-Id"]
507
518
 
508
519
  case status
509
520
  when 401
@@ -517,7 +528,8 @@ module Inttegro
517
528
  fix_code: fix_code,
518
529
  cause: cause,
519
530
  body: body,
520
- data: data
531
+ data: data,
532
+ request_id: request_id
521
533
  )
522
534
  when 429
523
535
  retry_after = response["Retry-After"]&.to_i
@@ -532,7 +544,8 @@ module Inttegro
532
544
  cause: cause,
533
545
  body: body,
534
546
  data: data,
535
- retry_after: retry_after
547
+ retry_after: retry_after,
548
+ request_id: request_id
536
549
  )
537
550
  else
538
551
  raise APIError.new(
@@ -545,7 +558,8 @@ module Inttegro
545
558
  fix_code: fix_code,
546
559
  cause: cause,
547
560
  body: body,
548
- data: data
561
+ data: data,
562
+ request_id: request_id
549
563
  )
550
564
  end
551
565
  end
@@ -3,9 +3,12 @@
3
3
 
4
4
  require "opentelemetry-api"
5
5
  require "sorbet-runtime"
6
+ require "securerandom"
7
+ require "time"
6
8
  require "uri"
7
9
 
8
10
  require_relative "errors"
11
+ require_relative "error_reporting"
9
12
 
10
13
  module Inttegro
11
14
  # Emits redacted SDK spans to the application's OpenTelemetry provider.
@@ -30,11 +33,17 @@ module Inttegro
30
33
  params(
31
34
  version: String,
32
35
  enabled: T::Boolean,
33
- tracer_provider: T.nilable(OpenTelemetry::Trace::TracerProvider)
36
+ tracer_provider: T.nilable(OpenTelemetry::Trace::TracerProvider),
37
+ error_reporter: T.nilable(ErrorReporter),
38
+ error_reporting_policy: Symbol
34
39
  ).void
35
40
  end
36
- def initialize(version, enabled: true, tracer_provider: nil)
41
+ def initialize(version, enabled: true, tracer_provider: nil, error_reporter: nil, error_reporting_policy: :unexpected)
37
42
  @enabled = T.let(enabled, T::Boolean)
43
+ raise ArgumentError, "error_reporting_policy must be :unexpected or :all" unless %i[unexpected all].include?(error_reporting_policy)
44
+
45
+ @error_reporter = T.let(error_reporter, T.nilable(ErrorReporter))
46
+ @error_reporting_policy = T.let(error_reporting_policy, Symbol)
38
47
  provider = tracer_provider || OpenTelemetry.tracer_provider
39
48
  @tracer = T.let(provider.tracer("inttegro", version), OpenTelemetry::Trace::Tracer)
40
49
  end
@@ -52,9 +61,18 @@ module Inttegro
52
61
  .returns(T.type_parameter(:Result))
53
62
  end
54
63
  def in_span(path_or_url, method, base_url, version, operation_override = nil, &block)
55
- return yield(nil) unless @enabled
64
+ return yield(nil) unless @enabled || @error_reporter
56
65
 
57
66
  operation, route, server_address = request_details(path_or_url, base_url, operation_override)
67
+ started_at = T.let(@error_reporter ? monotonic_milliseconds : nil, T.nilable(Float))
68
+ unless @enabled
69
+ begin
70
+ return yield(nil)
71
+ rescue StandardError => e
72
+ report_failure(e, operation, route, server_address, method, version, started_at, nil)
73
+ raise
74
+ end
75
+ end
58
76
  attributes = {
59
77
  "inttegro.operation.name" => operation,
60
78
  "inttegro.sdk.language" => "ruby",
@@ -72,6 +90,7 @@ module Inttegro
72
90
  span.set_attribute("error.type", error_type)
73
91
  span.status = OpenTelemetry::Trace::Status.error
74
92
  span.add_event("inttegro.request.failed", attributes: { "error.type" => error_type })
93
+ report_failure(e, operation, route, server_address, method, version, started_at, span)
75
94
  raise
76
95
  ensure
77
96
  span.finish
@@ -125,6 +144,71 @@ module Inttegro
125
144
 
126
145
  private
127
146
 
147
+ sig do
148
+ params(
149
+ error: StandardError,
150
+ operation: String,
151
+ route: T.nilable(String),
152
+ server_address: String,
153
+ method: T.any(String, Symbol),
154
+ version: String,
155
+ started_at: T.nilable(Float),
156
+ span: T.nilable(OpenTelemetry::Trace::Span)
157
+ ).void
158
+ end
159
+ def report_failure(error, operation, route, server_address, method, version, started_at, span)
160
+ reporter = @error_reporter
161
+ return unless reporter
162
+
163
+ begin
164
+ category = classify_error(error)
165
+ return if category == "canceled"
166
+ if @error_reporting_policy == :unexpected && error.is_a?(APIError)
167
+ return unless error.status >= 500 || error.type == "unknown_error"
168
+ end
169
+
170
+ api_error = T.let(error.is_a?(APIError) ? error : nil, T.nilable(APIError))
171
+ api_context = if api_error && (api_error.type || api_error.code || api_error.fix_code)
172
+ APIErrorReportContext.new(type: api_error.type, code: api_error.code, fix_code: api_error.fix_code)
173
+ end
174
+ span_context = span&.context
175
+ trace_context = if span_context&.valid?
176
+ TraceReportContext.new(trace_id: span_context.hex_trace_id, span_id: span_context.hex_span_id)
177
+ end
178
+ status_code = api_error&.status
179
+ report = ErrorReport.new(
180
+ schema_version: 1,
181
+ event_id: SecureRandom.uuid,
182
+ occurred_at: Time.now.utc.iso8601(6),
183
+ severity: "error",
184
+ category: category,
185
+ operation: operation,
186
+ sdk: SDKReportContext.new(language: "ruby", version: version),
187
+ http: HTTPReportContext.new(
188
+ request_method: method.to_s.upcase,
189
+ route: route,
190
+ server_address: server_address,
191
+ status_code: status_code,
192
+ request_id: api_error&.request_id,
193
+ duration_ms: [(monotonic_milliseconds - (started_at || monotonic_milliseconds)).round, 0].max
194
+ ),
195
+ api_error: api_context,
196
+ trace: trace_context,
197
+ exception_type: (error.class.name || "StandardError").split("::").last || "StandardError",
198
+ fingerprint: ["inttegro", "ruby", operation, category, status_code || "none"].join(":")
199
+ )
200
+ error.report = report if error.is_a?(Error)
201
+ reporter.call(report)
202
+ rescue StandardError
203
+ # Report preparation and delivery must never replace the original failure.
204
+ end
205
+ end
206
+
207
+ sig { returns(Float) }
208
+ def monotonic_milliseconds
209
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond).to_f
210
+ end
211
+
128
212
  sig do
129
213
  params(path_or_url: String, base_url: String, override: T.nilable(String))
130
214
  .returns([String, T.nilable(String), String])
@@ -2,5 +2,5 @@
2
2
  # typed: strict
3
3
 
4
4
  module Inttegro
5
- VERSION = "4.2.2"
5
+ VERSION = "4.3.0"
6
6
  end
data/lib/inttegro.rb CHANGED
@@ -6,6 +6,7 @@ require "sorbet-runtime"
6
6
  require_relative "inttegro/types"
7
7
  require_relative "inttegro/client"
8
8
  require_relative "inttegro/errors"
9
+ require_relative "inttegro/error_reporting"
9
10
  require_relative "inttegro/enums"
10
11
  require_relative "inttegro/models"
11
12
  require_relative "inttegro/version"
@@ -56,6 +56,9 @@ module OpenTelemetry
56
56
  end
57
57
 
58
58
  class Span
59
+ sig { returns(SpanContext) }
60
+ def context; end
61
+
59
62
  sig { params(key: String, value: Object).returns(Span) }
60
63
  def set_attribute(key, value); end
61
64
 
@@ -69,6 +72,17 @@ module OpenTelemetry
69
72
  def finish; end
70
73
  end
71
74
 
75
+ class SpanContext
76
+ sig { returns(T::Boolean) }
77
+ def valid?; end
78
+
79
+ sig { returns(String) }
80
+ def hex_trace_id; end
81
+
82
+ sig { returns(String) }
83
+ def hex_span_id; end
84
+ end
85
+
72
86
  class Status
73
87
  sig { params(description: String).returns(Status) }
74
88
  def self.error(description = ""); end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inttegro
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.2
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inttegro Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-04 00:00:00.000000000 Z
11
+ date: 2026-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-runtime
@@ -82,6 +82,7 @@ files:
82
82
  - lib/inttegro.rb
83
83
  - lib/inttegro/client.rb
84
84
  - lib/inttegro/enums.rb
85
+ - lib/inttegro/error_reporting.rb
85
86
  - lib/inttegro/errors.rb
86
87
  - lib/inttegro/file_download.rb
87
88
  - lib/inttegro/generated/enums.rb