Module: Apidepth::VendorRegistry

Defined in:
lib/apidepth/vendor_registry.rb

Constant Summary collapse

BUNDLED_BASELINE =
{
  "version" => "bundled",
  "vendors" => {
    "stripe" => {
      "hosts" => ["api.stripe.com"],
      "patterns" => [
        { "match" => '/v1/charges/ch_\w+', "replace" => "/v1/charges/:id" },
        { "match" => '/v1/customers/cus_\w+',          "replace" => "/v1/customers/:id" },
        { "match" => '/v1/payment_intents/pi_\w+',     "replace" => "/v1/payment_intents/:id" },
        { "match" => '/v1/subscriptions/sub_\w+',      "replace" => "/v1/subscriptions/:id" },
        { "match" => '/v1/invoices/in_\w+',            "replace" => "/v1/invoices/:id" },
        { "match" => '/v1/refunds/re_\w+',             "replace" => "/v1/refunds/:id" }
      ]
    },
    "openai" => {
      "hosts" => ["api.openai.com"],
      "patterns" => [
        { "match" => "/v1/chat/completions",           "replace" => "/v1/chat/completions" },
        { "match" => "/v1/embeddings",                 "replace" => "/v1/embeddings" },
        { "match" => "/v1/images/generations",         "replace" => "/v1/images/generations" },
        { "match" => '/v1/files/file-\w+',             "replace" => "/v1/files/:id" }
      ]
    },
    "anthropic" => {
      "hosts" => ["api.anthropic.com"],
      "patterns" => [
        { "match" => "/v1/messages",                   "replace" => "/v1/messages" }
      ]
    },
    "twilio" => {
      "hosts" => ["api.twilio.com"],
      "patterns" => [
        { "match" => '/2010-04-01/Accounts/AC\w+/Messages/SM\w+', "replace" => "/Accounts/:id/Messages/:id" },
        { "match" => '/2010-04-01/Accounts/AC\w+/Messages',       "replace" => "/Accounts/:id/Messages" },
        { "match" => '/2010-04-01/Accounts/AC\w+/Calls/CA\w+',    "replace" => "/Accounts/:id/Calls/:id" },
        { "match" => '/2010-04-01/Accounts/AC\w+/Calls',          "replace" => "/Accounts/:id/Calls" }
      ]
    },
    "resend" => {
      "hosts" => ["api.resend.com"],
      "patterns" => [
        { "match" => "/emails/[0-9a-f-]{36}", "replace" => "/emails/:id" }
      ]
    },
    "github" => {
      "hosts" => ["api.github.com"],
      "patterns" => [
        { "match" => '/repos/[^/]+/[^/]+/pulls/\d+',  "replace" => "/repos/:owner/:repo/pulls/:number" },
        { "match" => '/repos/[^/]+/[^/]+/issues/\d+', "replace" => "/repos/:owner/:repo/issues/:number" },
        { "match" => "/repos/[^/]+/[^/]+",            "replace" => "/repos/:owner/:repo" },
        { "match" => "/users/[^/]+", "replace" => "/users/:username" }
      ]
    }
  }
}.freeze
GENERIC_PATTERNS =
[
  [%r{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}, "/:uuid"],
  [%r{/\d{4,}},        "/:id"],
  [%r{/[a-z0-9]{24,}}, "/:token"]
].freeze

Class Method Summary collapse

Class Method Details

.identify(host, raw_path) ⇒ Object



68
69
70
71
72
73
74
75
76
77
# File 'lib/apidepth/vendor_registry.rb', line 68

def identify(host, raw_path)
  hosts, patterns = @mutex.synchronize { [@hosts, @patterns] }
  vendor = hosts[host]
  return nil unless vendor

  path = strip_query_string(raw_path)
  path = apply_vendor_normalizers(patterns[vendor] || [], path)
  path = apply_generic_normalizers(path)
  [vendor, path]
end

.load_extra_vendors(extra_vendors) ⇒ Object

Merge customer-defined host→vendor mappings from config.extra_vendors. Called once at Railtie boot after the user’s configure block has run. Does not touch @patterns — custom vendors use generic path normalization only.



82
83
84
85
86
87
88
# File 'lib/apidepth/vendor_registry.rb', line 82

def load_extra_vendors(extra_vendors)
  return if extra_vendors.nil? || extra_vendors.empty?

  @mutex.synchronize do
    extra_vendors.each { |name, host| @hosts[host.to_s] = name.to_s }
  end
end

.replace(registry_json) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/apidepth/vendor_registry.rb', line 90

def replace(registry_json)
  new_hosts    = build_hosts(registry_json)
  new_patterns = build_patterns(registry_json)

  # Re-apply extra_vendors so a registry refresh never wipes customer-defined
  # host mappings. The config value wins over any registry entry for the same host.
  (Apidepth.configuration.extra_vendors || {}).each do |name, host|
    new_hosts[host.to_s] = name.to_s
  end

  @mutex.synchronize do
    @hosts    = new_hosts
    @patterns = new_patterns
    @version  = registry_json["version"]
  end

  Apidepth.logger&.debug(
    "[Apidepth] Registry updated — version=#{Apidepth.sanitize_log(registry_json['version'])} " \
    "vendors=#{new_hosts.values.uniq.count}"
  )
end

.vendor_countObject



116
117
118
# File 'lib/apidepth/vendor_registry.rb', line 116

def vendor_count
  @mutex.synchronize { @hosts.values.uniq.count }
end

.versionObject



112
113
114
# File 'lib/apidepth/vendor_registry.rb', line 112

def version
  @mutex.synchronize { @version }
end