Class: SeatLayer::HTTPClient

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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.

Raises:

  • (ArgumentError)


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_urlObject (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

#modeString (readonly)

Returns "live", "test", or "unknown", derived from the key prefix.

Returns:

  • (String)

    "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

Raises:

  • (ArgumentError)


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) ⇒ Object



111
112
113
# File 'lib/seatlayer/http_client.rb', line 111

def delete(path)
  request("DELETE", path)
end

#get(path, query = nil) ⇒ Object



95
96
97
# File 'lib/seatlayer/http_client.rb', line 95

def get(path, query = nil)
  request("GET", path, query: query)
end

#patch(path, body) ⇒ Object



107
108
109
# File 'lib/seatlayer/http_client.rb', line 107

def patch(path, body)
  request("PATCH", path, body: body)
end

#post(path, body = nil, idempotency_key: nil) ⇒ Object



99
100
101
# File 'lib/seatlayer/http_client.rb', line 99

def post(path, body = nil, idempotency_key: nil)
  request("POST", path, body: body, idempotency_key: idempotency_key)
end

#put(path, body) ⇒ Object



103
104
105
# File 'lib/seatlayer/http_client.rb', line 103

def put(path, body)
  request("PUT", path, body: body)
end

#request(method, path, query: nil, body: nil, idempotency_key: nil) ⇒ 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
# File 'lib/seatlayer/http_client.rb', line 65

def request(method, path, query: nil, body: nil, idempotency_key: nil)
  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 = nil
  if body
    payload = JSON.generate(body)
    headers["Content-Type"] = "application/json"
  end

  # Every mutation carries one. A retried POST that creates a second hold is
  # worse than a failed POST, and the caller cannot tell from outside — so
  # the SDK, which knows it retried, is the right place to guarantee it.
  unless %w[GET HEAD].include?(method)
    key = idempotency_key || SecureRandom.uuid
    self.class.validate_idempotency_key!(key)
    headers["Idempotency-Key"] = key
  end

  execute(method, url, headers, payload)
end