apidepth 0.2.2 → 0.2.3

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: d1863cc44a0aa90d68dbae4bba31293e4d8154a3a92c1fa0030c3a0002cf5237
4
- data.tar.gz: 881062b88799903817b01067cfad0227499402073cd8e58702de8eb5cbf00f3e
3
+ metadata.gz: f499652eea585017c268ea82a997a9e11af46e36c912b6f672fa91fd7d354eb4
4
+ data.tar.gz: aa70866eaf7876101236b986472ad14b24ea68bcaa9a38a68ed901d88e1f06b5
5
5
  SHA512:
6
- metadata.gz: a3f798621d4d00f8c158ec0ed50b1c00fb8e215da5efaec04fe04a996c29eec97dcf746c355eb16336b98ac46655133f7a16f0c4325cbec7b20e3bff72b37fa3
7
- data.tar.gz: 88da8b454854604520dd0d025eb08ece7da634adb8951f59bc9c6b1421d6440b6ce7a0d9d82413f740025d9df361a80f530c18c8359a7484ca5ce4bf3d24d43e
6
+ metadata.gz: 22a04205584e05a111b2422209733b4c06b0008dada3840caa340915d2d9ffb7195dec530c4791b692990812f357b4148a714da904ada5fba15e5b20f854460e
7
+ data.tar.gz: 607dca2a4455e60fcf21bd3502382107a3840b02ca23102a0dac4fc4c903c62101bf46b099210591182aa6350f229ffc0209f650a71076060c1e482b83340cee
@@ -92,16 +92,21 @@ module Apidepth
92
92
  end
93
93
  end
94
94
 
95
+ # Canonical test cases live in apidepth-collector/tests/fixtures/private_host_cases.json.
96
+ # All SDK implementations load that fixture and must pass every case. Any change here
97
+ # must be accompanied by a fixture update and a matching change in every other SDK.
95
98
  PRIVATE_HOST_PATTERN = /
96
99
  \Alocalhost\z |
97
100
  \A127\. |
98
101
  \A0\.0\.0\.0\z |
102
+ \A0\z |
103
+
99
104
  \A169\.254\. |
100
105
  \A10\. |
101
106
  \A172\.(1[6-9]|2\d|3[01])\. |
102
107
  \A192\.168\. |
103
108
  \A\[?::1\]?\z |
104
- \A\[?fc |
109
+ \A\[?f[cd] |
105
110
  \A\[?fe80:
106
111
  /xi.freeze
107
112
 
@@ -287,7 +292,7 @@ module Apidepth
287
292
 
288
293
  if host.match?(/\A\d+\z/)
289
294
  int = host.to_i
290
- if int.positive? && int <= 0xFFFFFFFF
295
+ if int >= 0 && int <= 0xFFFFFFFF
291
296
  host = [int >> 24, (int >> 16) & 0xFF, (int >> 8) & 0xFF,
292
297
  int & 0xFF].join(".")
293
298
  end
@@ -57,6 +57,11 @@ module Apidepth
57
57
 
58
58
  registry = JSON.parse(res.body)
59
59
 
60
+ # Apply registry-managed customer vendors and emit developer warnings.
61
+ # Must run before replace() so the vendor list is complete when it lands.
62
+ apply_customer_vendors(registry)
63
+ emit_warnings(registry)
64
+
60
65
  # Warm the disk cache so the next cold-start skips the network fetch.
61
66
  begin
62
67
  validate_cache_path!(Apidepth.configuration.registry_cache_path)
@@ -79,6 +84,103 @@ module Apidepth
79
84
  Thread.current[:apidepth_skip] = false
80
85
  end
81
86
 
87
+ # Apply the collector-managed customer_vendors from the registry response.
88
+ #
89
+ # The collector is the source of truth after first declaration. Registry
90
+ # vendors are loaded on top of locally-declared extra_vendors; registry wins
91
+ # on any host conflict. Conflict warnings are emitted once per vendor per
92
+ # process lifetime (see emit_warnings).
93
+ def self.apply_customer_vendors(registry)
94
+ remote = registry["customer_vendors"]
95
+ return unless remote.is_a?(Hash) && !remote.empty?
96
+
97
+ local = Apidepth.configuration.extra_vendors || {}
98
+
99
+ # Filter to string key-value pairs before passing to load_extra_vendors.
100
+ # The server response is trusted but load_extra_vendors calls .to_s on
101
+ # everything — a non-string key like 42 would silently register as "42".
102
+ clean = {}
103
+ remote.each do |name, remote_host|
104
+ next unless name.is_a?(String) && remote_host.is_a?(String)
105
+
106
+ clean[name] = remote_host
107
+ local_host = local[name]
108
+ # Track conflicts before overwriting — emit_warnings reads this later.
109
+ @mutex.synchronize do
110
+ if local_host && local_host != remote_host
111
+ @conflict_vendors ||= {}
112
+ @conflict_vendors[name] = { local: local_host, remote: remote_host }
113
+ end
114
+ end
115
+ end
116
+
117
+ VendorRegistry.load_extra_vendors(clean)
118
+ end
119
+
120
+ # Emit developer-facing warnings from the registry response.
121
+ #
122
+ # Stale vendor warning: vendor exists in registry but no events in 7+ days.
123
+ # Conflict warning: local extra_vendors host differs from registry host.
124
+ #
125
+ # Both follow the warn-once pattern — an instance flag per vendor prevents
126
+ # log spam in long-running processes. Warnings fire on registry fetch, not
127
+ # on every event.
128
+ def self.emit_warnings(registry)
129
+ # Stale vendor warnings — sourced from the registry warnings block.
130
+ # Only present in responses from collector v0.3+; older cached responses skip.
131
+ warnings = registry["warnings"]
132
+ emit_stale_warnings(warnings["stale_vendors"]) if warnings.is_a?(Hash)
133
+
134
+ # Conflict warnings — collected by apply_customer_vendors, emitted here.
135
+ # Fires regardless of whether the registry has a warnings block, so that
136
+ # conflicts detected against a cached/older registry are still surfaced.
137
+ emit_conflict_warnings
138
+ end
139
+
140
+ def self.emit_stale_warnings(stale)
141
+ return unless stale.is_a?(Array)
142
+
143
+ to_warn = []
144
+ @mutex.synchronize do
145
+ @warned_stale ||= {}
146
+ stale.each do |name|
147
+ next unless name.is_a?(String)
148
+ next if @warned_stale[name]
149
+
150
+ @warned_stale[name] = true
151
+ to_warn << name
152
+ end
153
+ end
154
+
155
+ to_warn.each do |name|
156
+ Apidepth.logger&.warn(
157
+ "[Apidepth] No events received from '#{name}' in 7+ days — " \
158
+ "is it still declared in extra_vendors? If intentional, remove " \
159
+ "it at www.apidepth.io."
160
+ )
161
+ end
162
+ end
163
+
164
+ def self.emit_conflict_warnings
165
+ conflicts, = @mutex.synchronize do
166
+ c = @conflict_vendors || {}
167
+ @conflict_vendors = {}
168
+ @warned_conflict ||= {}
169
+ to_warn = c.reject { |name, _| @warned_conflict[name] }
170
+ to_warn.each_key { |name| @warned_conflict[name] = true }
171
+ [to_warn]
172
+ end
173
+
174
+ conflicts.each do |name, hosts|
175
+ Apidepth.logger&.warn(
176
+ "[Apidepth] extra_vendors conflict: '#{name}' is configured as " \
177
+ "'#{hosts[:local]}' locally but the registry has '#{hosts[:remote]}' " \
178
+ "— registry takes precedence. Update your initializer or remove " \
179
+ "the entry from your dashboard at www.apidepth.io."
180
+ )
181
+ end
182
+ end
183
+
82
184
  def self.load_from_disk
83
185
  path = Apidepth.configuration.registry_cache_path
84
186
 
@@ -115,6 +217,12 @@ module Apidepth
115
217
  # public class methods regardless of placement inside a private block.
116
218
  # private_class_method is the correct idiom.
117
219
  private_class_method :start_refresh_thread, :fetch_remote,
118
- :load_from_disk, :validate_cache_path!
220
+ :load_from_disk, :validate_cache_path!,
221
+ :apply_customer_vendors, :emit_warnings,
222
+ :emit_stale_warnings, :emit_conflict_warnings
223
+
224
+ # Mutex protecting @conflict_vendors, @warned_stale, and @warned_conflict.
225
+ # Initialized at require time like VendorRegistry's own @mutex.
226
+ @mutex = Mutex.new
119
227
  end
120
228
  end
@@ -1,5 +1,5 @@
1
1
  # lib/apidepth/version.rb
2
2
 
3
3
  module Apidepth
4
- VERSION = "0.2.2".freeze
4
+ VERSION = "0.2.3".freeze
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apidepth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Apidepth
@@ -93,8 +93,7 @@ dependencies:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '1.65'
96
- description: |
97
- Know if your API slowness is your code or the vendor's. Apidepth instruments
96
+ description: Know if your API slowness is your code or the vendor's. Apidepth instruments
98
97
  Net::HTTP to track real production latency to Stripe, OpenAI, Twilio and others
99
98
  — then benchmarks your p95 against anonymized fleet data so you can see if it's
100
99
  you, or everyone.
@@ -121,9 +120,9 @@ licenses:
121
120
  - MIT
122
121
  metadata:
123
122
  homepage_uri: https://apidepth.io
124
- source_code_uri: https://github.com/apidepth/apidepth-ruby
125
- changelog_uri: https://github.com/apidepth/apidepth-ruby/blob/main/CHANGELOG.md
126
- bug_tracker_uri: https://github.com/apidepth/apidepth-ruby/issues
123
+ source_code_uri: https://github.com/cmwright33/apidepth-ruby
124
+ changelog_uri: https://github.com/cmwright33/apidepth-ruby/blob/main/CHANGELOG.md
125
+ bug_tracker_uri: https://github.com/cmwright33/apidepth-ruby/issues
127
126
  rubygems_mfa_required: 'true'
128
127
  rdoc_options: []
129
128
  require_paths: