Class: Bitfab::HttpClient

Inherits:
Object
  • Object
show all
Defined in:
lib/bitfab/http_client.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, service_url: nil, timeout: 120) ⇒ HttpClient

Returns a new instance of HttpClient.



14
15
16
17
18
# File 'lib/bitfab/http_client.rb', line 14

def initialize(api_key:, service_url: nil, timeout: 120)
  @api_key = api_key
  @service_url = (service_url || DEFAULT_SERVICE_URL).chomp("/")
  @timeout = timeout
end

Instance Attribute Details

#service_urlObject (readonly)

Returns the value of attribute service_url.



12
13
14
# File 'lib/bitfab/http_client.rb', line 12

def service_url
  @service_url
end

Instance Method Details

#complete_replay(test_run_id) ⇒ Object

Mark a replay test run as completed. Blocking call.



141
142
143
# File 'lib/bitfab/http_client.rb', line 141

def complete_replay(test_run_id)
  request("/api/sdk/replay/complete", {"testRunId" => test_run_id}, timeout: 30)
end

#get(endpoint, timeout: nil) ⇒ Object

Make a GET request to the Bitfab API. Returns parsed JSON response hash.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/bitfab/http_client.rb', line 72

def get(endpoint, timeout: nil)
  uri = URI("#{@service_url}#{endpoint}")
  request_timeout = timeout || @timeout

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == "https"
  http.open_timeout = request_timeout
  http.read_timeout = request_timeout

  req = Net::HTTP::Get.new(uri.path, headers)
  response = http.request(req)

  unless response.is_a?(Net::HTTPSuccess)
    raise Net::HTTPError.new("HTTP #{response.code}: #{response.body}", response)
  end

  result = JSON.parse(response.body)

  if result["error"]
    msg = result["error"]
    msg = "#{msg} Configure it at: #{@service_url}#{result["url"]}" if result["url"]
    raise StandardError, msg
  end

  result
end

#get_external_span(span_id) ⇒ Object

Fetch an external span by ID. Blocking GET request.



125
126
127
# File 'lib/bitfab/http_client.rb', line 125

def get_external_span(span_id)
  get("/api/sdk/externalSpans/#{span_id}", timeout: 30)
end

#get_span_tree(external_span_id) ⇒ Object

Fetch the span tree rooted at an external span. Blocking GET request. Used by replay when a mock strategy is active so child spans can be matched against their historical outputs.

Returns a hash shaped { “root” => SpanTreeNode } where each node has sourceSpanId, traceFunctionKey, spanName, type, output, optional outputMeta, and children.



136
137
138
# File 'lib/bitfab/http_client.rb', line 136

def get_span_tree(external_span_id)
  get("/api/sdk/replay/spanTree/#{external_span_id}", timeout: 30)
end

#request(endpoint, payload, timeout: nil, max_retries: 1, retry_delay: 0.1) ⇒ Object

Make a POST request to the Bitfab API. Returns parsed JSON response hash.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/bitfab/http_client.rb', line 22

def request(endpoint, payload, timeout: nil, max_retries: 1, retry_delay: 0.1)
  uri = URI("#{@service_url}#{endpoint}")
  request_timeout = timeout || @timeout

  last_error = nil

  max_retries.times do |attempt|
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == "https"
    http.open_timeout = request_timeout
    http.read_timeout = request_timeout

    req = Net::HTTP::Post.new(uri.path, headers)
    req.body = JSON.generate(payload)

    response = http.request(req)

    unless response.is_a?(Net::HTTPSuccess)
      raise Net::HTTPError.new("HTTP #{response.code}: #{response.body}", response)
    end

    result = JSON.parse(response.body)

    if result["error"]
      msg = result["error"]
      msg = "#{msg} Configure it at: #{@service_url}#{result["url"]}" if result["url"]
      raise StandardError, msg
    end

    return result
  rescue => e
    last_error = e
    sleep(retry_delay) if attempt < max_retries - 1
  end

  raise last_error
end

#send_external_span(payload) ⇒ Object

Send an external span in a background thread. Returns the thread for callers that need to await completion.



62
63
64
65
66
67
68
# File 'lib/bitfab/http_client.rb', line 62

def send_external_span(payload)
  merged = payload.merge("sdkVersion" => VERSION)

  Bitfab._run_in_background do
    request("/api/sdk/externalSpans", merged, timeout: 30)
  end
end

#send_external_trace(payload) ⇒ Object

Send an external trace (fire-and-forget in background thread).



146
147
148
149
150
151
152
# File 'lib/bitfab/http_client.rb', line 146

def send_external_trace(payload)
  merged = payload.merge("sdkVersion" => VERSION)

  Bitfab._run_in_background do
    request("/api/sdk/externalTraces", merged, timeout: 10)
  end
end

#start_replay(trace_function_key, limit, trace_ids: nil, code_change_description: nil, code_change_files: nil, experiment_group_id: nil) ⇒ Object

Start a replay session by fetching historical traces. Blocking call. Returns hash with testRunId, testRunUrl, and items array.

Parameters:

  • code_change_description (String, nil) (defaults to: nil)

    optional rationale for the code change being tested in this replay

  • code_change_files (Array<Hash>, nil) (defaults to: nil)

    optional list of edited files, each as { path:, before:, after: } (use “” for new/deleted files)

  • experiment_group_id (String, nil) (defaults to: nil)

    optional UUID grouping multiple replay runs into a single experiment batch



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/bitfab/http_client.rb', line 108

def start_replay(trace_function_key, limit, trace_ids: nil, code_change_description: nil,
  code_change_files: nil, experiment_group_id: nil)
  payload = {
    "traceFunctionKey" => trace_function_key
  }
  # limit is only meaningful without trace_ids (an explicit ID list
  # already determines the count), so it's omitted when nil.
  payload["limit"] = limit unless limit.nil?
  payload["traceIds"] = trace_ids if trace_ids
  payload["codeChangeDescription"] = code_change_description unless code_change_description.nil?
  payload["codeChangeFiles"] = normalize_code_change_files(code_change_files) unless code_change_files.nil?
  payload["experimentGroupId"] = experiment_group_id unless experiment_group_id.nil?

  request("/api/sdk/replay/start", payload, timeout: 30)
end