Class: SeatLayer::HTTPClient
- Inherits:
-
Object
- Object
- SeatLayer::HTTPClient
- Defined in:
- lib/seatlayer/http_client.rb
Overview
The transport: auth, idempotency, retry, and error mapping.
Built on net/http from the standard library rather than Faraday or HTTParty. A server SDK that drags in an HTTP stack becomes a supply-chain surface for every customer who installs it, and can conflict with whatever the host application already uses.
Constant Summary collapse
- DEFAULT_BASE_URL =
"https://api.seatlayer.io"- DEFAULT_MAX_RETRIES =
3- DEFAULT_TIMEOUT =
30.0- IDEMPOTENCY_KEY_PATTERN =
The API's own charset for Idempotency-Key.
/\A[A-Za-z0-9._:-]{1,128}\z/
Instance Attribute Summary collapse
-
#base_url ⇒ Object
readonly
Returns the value of attribute base_url.
-
#mode ⇒ String
readonly
"live", "test", or "unknown", derived from the key prefix.
Class Method Summary collapse
-
.encode(segment) ⇒ Object
Percent-encode a path segment, including slashes.
- .validate_idempotency_key!(key) ⇒ Object
Instance Method Summary collapse
- #delete(path, query = nil) ⇒ Object
- #get(path, query = nil) ⇒ Object
-
#initialize(secret_key:, base_url: DEFAULT_BASE_URL, max_retries: DEFAULT_MAX_RETRIES, timeout: DEFAULT_TIMEOUT, transport: nil) ⇒ HTTPClient
constructor
A new instance of HTTPClient.
- #patch(path, body) ⇒ Object
- #post(path, body = nil, idempotency_key: nil, retry_policy: :none) ⇒ Object
- #put(path, body) ⇒ Object
- #put_raw(path, raw_body, content_type: "application/octet-stream") ⇒ Object
- #request(method, path, query: nil, body: nil, raw_body: nil, content_type: nil, idempotency_key: nil, retry_policy: :none) ⇒ Object
Constructor Details
#initialize(secret_key:, base_url: DEFAULT_BASE_URL, max_retries: DEFAULT_MAX_RETRIES, timeout: DEFAULT_TIMEOUT, transport: nil) ⇒ HTTPClient
Returns a new instance of HTTPClient.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/seatlayer/http_client.rb', line 27 def initialize(secret_key:, base_url: DEFAULT_BASE_URL, max_retries: DEFAULT_MAX_RETRIES, timeout: DEFAULT_TIMEOUT, transport: nil) raise ArgumentError, "A SeatLayer secret key is required." if secret_key.nil? || secret_key.empty? # Caught here rather than as a 401 three round-trips later. The pk_ case # gets its own message: it is the one people paste by mistake. if secret_key.start_with?("pk_") raise ArgumentError, "That is a publishable key. The server SDK needs a secret key (sk_live_… or sk_test_…)." end unless secret_key.start_with?("sk_") raise ArgumentError, "A SeatLayer secret key starts with sk_live_ or sk_test_." end @secret_key = secret_key @base_url = base_url.sub(%r{/+\z}, "") @max_retries = max_retries @timeout = timeout @transport = transport @mode = if secret_key.start_with?("sk_test_") then "test" elsif secret_key.start_with?("sk_live_") then "live" else "unknown" end end |
Instance Attribute Details
#base_url ⇒ Object (readonly)
Returns the value of attribute base_url.
25 26 27 |
# File 'lib/seatlayer/http_client.rb', line 25 def base_url @base_url end |
#mode ⇒ String (readonly)
Returns "live", "test", or "unknown", derived from the key prefix.
24 25 26 |
# File 'lib/seatlayer/http_client.rb', line 24 def mode @mode end |
Class Method Details
.encode(segment) ⇒ Object
Percent-encode a path segment, including slashes.
61 62 63 |
# File 'lib/seatlayer/http_client.rb', line 61 def self.encode(segment) URI.encode_www_form_component(segment.to_s).gsub("+", "%20") end |
.validate_idempotency_key!(key) ⇒ Object
52 53 54 55 56 57 58 |
# File 'lib/seatlayer/http_client.rb', line 52 def self.validate_idempotency_key!(key) return if IDEMPOTENCY_KEY_PATTERN.match?(key) raise ArgumentError, "Invalid Idempotency-Key #{key.inspect}: allowed characters are " \ "A-Z a-z 0-9 . _ : - and the length must be 1-128." end |
Instance Method Details
#delete(path, query = nil) ⇒ Object
116 117 118 |
# File 'lib/seatlayer/http_client.rb', line 116 def delete(path, query = nil) request("DELETE", path, query: query) end |
#get(path, query = nil) ⇒ Object
96 97 98 |
# File 'lib/seatlayer/http_client.rb', line 96 def get(path, query = nil) request("GET", path, query: query) end |
#patch(path, body) ⇒ Object
112 113 114 |
# File 'lib/seatlayer/http_client.rb', line 112 def patch(path, body) request("PATCH", path, body: body) end |
#post(path, body = nil, idempotency_key: nil, retry_policy: :none) ⇒ Object
100 101 102 |
# File 'lib/seatlayer/http_client.rb', line 100 def post(path, body = nil, idempotency_key: nil, retry_policy: :none) request("POST", path, body: body, idempotency_key: idempotency_key, retry_policy: retry_policy) end |
#put(path, body) ⇒ Object
104 105 106 |
# File 'lib/seatlayer/http_client.rb', line 104 def put(path, body) request("PUT", path, body: body) end |
#put_raw(path, raw_body, content_type: "application/octet-stream") ⇒ Object
108 109 110 |
# File 'lib/seatlayer/http_client.rb', line 108 def put_raw(path, raw_body, content_type: "application/octet-stream") request("PUT", path, raw_body: raw_body, content_type: content_type) end |
#request(method, path, query: nil, body: nil, raw_body: nil, content_type: nil, idempotency_key: nil, retry_policy: :none) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/seatlayer/http_client.rb', line 65 def request(method, path, query: nil, body: nil, raw_body: nil, content_type: nil, idempotency_key: nil, retry_policy: :none) url = @base_url + path if query pairs = query.compact url += "?#{URI.encode_www_form(pairs)}" unless pairs.empty? end headers = { "Authorization" => "Bearer #{@secret_key}", "Accept" => "application/json", "User-Agent" => "seatlayer-ruby" } payload = build_payload(body, raw_body, content_type, headers) # Only operations with exact server-side response replay get an automatic # key. A caller key on any other mutation is forwarded, but cannot opt that # operation into automatic retries. unless %w[GET HEAD].include?(method) key = idempotency_key key ||= SecureRandom.uuid if retry_policy == :header_replay if key self.class.validate_idempotency_key!(key) headers["Idempotency-Key"] = key end end retry_allowed = %w[GET HEAD].include?(method) || retry_policy == :header_replay execute(method, url, headers, payload, retry_allowed: retry_allowed) end |