infrawrench-sdk 0.1.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +94 -0
- data/infrawrench-sdk.gemspec +58 -0
- data/lib/infrawrench/client.rb +4112 -0
- data/lib/infrawrench/sdk.rb +17 -0
- data/lib/infrawrench/transport.rb +302 -0
- data/lib/infrawrench/version.rb +19 -0
- data/lib/infrawrench-sdk.rb +18 -0
- data/sig/infrawrench/sdk.rbs +2099 -0
- metadata +62 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# infrawrench-sdk v0.1.1 | MIT | Copyright (c) 2026 Infrawrench LLC
|
|
4
|
+
# https://github.com/Infrawrench/Infrawrench
|
|
5
|
+
#
|
|
6
|
+
# Generated from the Infrawrench API OpenAPI 3.1 spec (API version 0.1.1).
|
|
7
|
+
#
|
|
8
|
+
# DO NOT EDIT. Regenerate with:
|
|
9
|
+
# pnpm --filter @infrawrench/web generate:sdk
|
|
10
|
+
#
|
|
11
|
+
# Internal routes are absent by construction: the generator consumes the same
|
|
12
|
+
# published spec that /openapi.json serves, which drops every operation
|
|
13
|
+
# marked x-internal.
|
|
14
|
+
|
|
15
|
+
require_relative "version"
|
|
16
|
+
require_relative "transport"
|
|
17
|
+
require_relative "client"
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# infrawrench-sdk v0.1.1 | MIT | Copyright (c) 2026 Infrawrench LLC
|
|
4
|
+
# https://github.com/Infrawrench/Infrawrench
|
|
5
|
+
#
|
|
6
|
+
# Generated from the Infrawrench API OpenAPI 3.1 spec (API version 0.1.1).
|
|
7
|
+
#
|
|
8
|
+
# DO NOT EDIT. Regenerate with:
|
|
9
|
+
# pnpm --filter @infrawrench/web generate:sdk
|
|
10
|
+
#
|
|
11
|
+
# Internal routes are absent by construction: the generator consumes the same
|
|
12
|
+
# published spec that /openapi.json serves, which drops every operation
|
|
13
|
+
# marked x-internal.
|
|
14
|
+
require "json"
|
|
15
|
+
require "net/http"
|
|
16
|
+
require "securerandom"
|
|
17
|
+
require "uri"
|
|
18
|
+
|
|
19
|
+
module Infrawrench
|
|
20
|
+
# Replaced with the first server advertised by the spec.
|
|
21
|
+
DEFAULT_BASE_URL = "https://app.infrawrench.com"
|
|
22
|
+
|
|
23
|
+
# Replaced with the path parameter the client can carry as configuration
|
|
24
|
+
# (`orgId`), or `nil` if the API has no such parameter.
|
|
25
|
+
SCOPE_PARAM = "orgId"
|
|
26
|
+
|
|
27
|
+
# Base class for everything this gem raises, so `rescue Infrawrench::Error`
|
|
28
|
+
# catches transport misconfiguration and HTTP failures alike.
|
|
29
|
+
class Error < StandardError; end
|
|
30
|
+
|
|
31
|
+
# Raised before anything is sent: a required path parameter has no value from
|
|
32
|
+
# either the call site or the client's configuration.
|
|
33
|
+
class ConfigurationError < Error; end
|
|
34
|
+
|
|
35
|
+
# Raised for any non-2xx response.
|
|
36
|
+
#
|
|
37
|
+
# Branch on {#code} rather than on the message — it carries the API's
|
|
38
|
+
# machine-readable discriminator (e.g. `reauthentication_required` on a
|
|
39
|
+
# step-up 403) whenever the response body has one.
|
|
40
|
+
class ApiError < Error
|
|
41
|
+
# @return [Integer] HTTP status code.
|
|
42
|
+
attr_reader :status
|
|
43
|
+
# @return [String, nil] Machine-readable error code, when the API sent one.
|
|
44
|
+
attr_reader :code
|
|
45
|
+
# @return [Object] Parsed response body — a Hash for JSON, else the raw String.
|
|
46
|
+
attr_reader :body
|
|
47
|
+
# @return [String] HTTP method of the failed request, e.g. `"GET"`.
|
|
48
|
+
attr_reader :http_method
|
|
49
|
+
# @return [String] Fully resolved URL of the failed request.
|
|
50
|
+
attr_reader :url
|
|
51
|
+
|
|
52
|
+
def initialize(status:, message:, code:, body:, http_method:, url:)
|
|
53
|
+
super(message)
|
|
54
|
+
@status = status
|
|
55
|
+
@code = code
|
|
56
|
+
@body = body
|
|
57
|
+
@http_method = http_method
|
|
58
|
+
@url = url
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# A file to send in a `multipart/form-data` request.
|
|
63
|
+
#
|
|
64
|
+
# Call sites rarely need this: a plain String of bytes or any IO-like object
|
|
65
|
+
# is accepted wherever a file is expected, and {.coerce} wraps it. Construct
|
|
66
|
+
# one explicitly only to control the filename or content type the server sees.
|
|
67
|
+
class Upload
|
|
68
|
+
# @return [String] The file's bytes.
|
|
69
|
+
attr_reader :bytes
|
|
70
|
+
# @return [String] Filename sent in the part's Content-Disposition.
|
|
71
|
+
attr_reader :filename
|
|
72
|
+
# @return [String] Content-Type sent for the part.
|
|
73
|
+
attr_reader :content_type
|
|
74
|
+
|
|
75
|
+
def initialize(content, filename: "file", content_type: "application/octet-stream")
|
|
76
|
+
@bytes = (content.respond_to?(:read) ? content.read : content.to_s).b
|
|
77
|
+
@filename = filename
|
|
78
|
+
@content_type = content_type
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Wrap whatever a caller passed for a file field.
|
|
82
|
+
#
|
|
83
|
+
# A `File` keeps its basename, because a server that stores the upload under
|
|
84
|
+
# the name it was given should get the name the user actually chose.
|
|
85
|
+
#
|
|
86
|
+
# @param value [Upload, IO, String]
|
|
87
|
+
# @return [Upload]
|
|
88
|
+
def self.coerce(value)
|
|
89
|
+
return value if value.is_a?(Upload)
|
|
90
|
+
return new(value) unless value.respond_to?(:read)
|
|
91
|
+
|
|
92
|
+
path = value.respond_to?(:path) ? value.path : nil
|
|
93
|
+
path ? new(value, filename: File.basename(path)) : new(value)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Request plumbing shared by every namespace.
|
|
98
|
+
#
|
|
99
|
+
# The generated namespace classes each hold one of these; reach for
|
|
100
|
+
# {APIV1Client} instead unless you need the resolved {#base_url}.
|
|
101
|
+
class Transport
|
|
102
|
+
# Net::HTTP models each verb as its own request class, so the generated
|
|
103
|
+
# method strings have to be mapped rather than interpolated.
|
|
104
|
+
METHOD_CLASSES = {
|
|
105
|
+
"GET" => Net::HTTP::Get,
|
|
106
|
+
"POST" => Net::HTTP::Post,
|
|
107
|
+
"PUT" => Net::HTTP::Put,
|
|
108
|
+
"PATCH" => Net::HTTP::Patch,
|
|
109
|
+
"DELETE" => Net::HTTP::Delete
|
|
110
|
+
}.freeze
|
|
111
|
+
private_constant :METHOD_CLASSES
|
|
112
|
+
|
|
113
|
+
# Everything RFC 3986 lets a path segment carry unescaped.
|
|
114
|
+
PATH_SAFE = /[^a-zA-Z0-9\-._~]/n.freeze
|
|
115
|
+
private_constant :PATH_SAFE
|
|
116
|
+
|
|
117
|
+
# @return [String] Normalized base URL, without a trailing slash.
|
|
118
|
+
attr_reader :base_url
|
|
119
|
+
|
|
120
|
+
# @param base_url [String, nil] Base URL of the deployment. Defaults to the production API.
|
|
121
|
+
# @param api_key [String, nil] API key or WorkOS access token, sent as `Authorization: Bearer <key>`.
|
|
122
|
+
# @param org_id [String, nil] Default organization id, filled in for every org-scoped call.
|
|
123
|
+
# @param headers [Hash{String => String}] Headers merged into every request. Per-call headers win.
|
|
124
|
+
# @param timeout [Numeric, nil] Read timeout in seconds. Net::HTTP's default when nil.
|
|
125
|
+
# @param open_timeout [Numeric, nil] Connect timeout in seconds.
|
|
126
|
+
# @param http_handler [#call, nil] Replaces the network call entirely. Receives
|
|
127
|
+
# `(URI, Net::HTTPRequest)` and must return something that answers `code`,
|
|
128
|
+
# `body` and `[]`. This is the seam for tests and for routing through a
|
|
129
|
+
# pre-configured connection pool.
|
|
130
|
+
def initialize(base_url: nil, api_key: nil, org_id: nil, headers: {}, timeout: nil,
|
|
131
|
+
open_timeout: nil, http_handler: nil)
|
|
132
|
+
@base_url = (base_url || DEFAULT_BASE_URL).sub(%r{/+\z}, "")
|
|
133
|
+
@api_key = api_key
|
|
134
|
+
@defaults = SCOPE_PARAM.nil? ? {} : { SCOPE_PARAM => org_id }
|
|
135
|
+
@headers = headers.transform_keys(&:to_s)
|
|
136
|
+
@timeout = timeout
|
|
137
|
+
@open_timeout = open_timeout
|
|
138
|
+
@http_handler = http_handler
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Perform one call. The generated methods are thin wrappers around this.
|
|
142
|
+
#
|
|
143
|
+
# @param http_method [String] Uppercase verb.
|
|
144
|
+
# @param path [String] URL template with `{param}` placeholders.
|
|
145
|
+
# @param path_params [Hash{String => Object}, nil] Values for those placeholders, by wire name.
|
|
146
|
+
# @param query [Hash{String => Object}, nil] Query parameters; nil values are dropped, Arrays repeat.
|
|
147
|
+
# @param body [Object, nil] JSON request body. Omitted when nil.
|
|
148
|
+
# @param form [Hash{String => Object}, nil] `multipart/form-data` fields. Mutually exclusive with body.
|
|
149
|
+
# @param form_files [Array<String>] Which form fields are file parts rather than scalars.
|
|
150
|
+
# @param accept [Symbol] `:json`, `:binary` or `:empty` — what the endpoint returns.
|
|
151
|
+
# @param request_options [Hash, nil] Per-call `:headers`, `:timeout`, `:open_timeout`.
|
|
152
|
+
# @return [Object, nil] Parsed JSON, raw bytes, or nil.
|
|
153
|
+
# @raise [ApiError] On any non-2xx response.
|
|
154
|
+
# @raise [ConfigurationError] When a path parameter has no value.
|
|
155
|
+
def request(http_method:, path:, path_params: nil, query: nil, body: nil, form: nil,
|
|
156
|
+
form_files: [], accept: :json, request_options: nil)
|
|
157
|
+
options = request_options || {}
|
|
158
|
+
url = "#{@base_url}#{resolve_path(http_method, path, path_params)}#{serialize_query(query)}"
|
|
159
|
+
uri = URI.parse(url)
|
|
160
|
+
|
|
161
|
+
request_class = METHOD_CLASSES.fetch(http_method)
|
|
162
|
+
req = request_class.new(uri)
|
|
163
|
+
req["accept"] = accept == :binary ? "application/octet-stream" : "application/json"
|
|
164
|
+
@headers.each { |name, value| req[name] = value }
|
|
165
|
+
(options[:headers] || {}).each { |name, value| req[name.to_s] = value }
|
|
166
|
+
req["authorization"] = "Bearer #{@api_key}" unless @api_key.nil? || @api_key.empty?
|
|
167
|
+
|
|
168
|
+
if form
|
|
169
|
+
boundary = SecureRandom.hex(16)
|
|
170
|
+
req["content-type"] = "multipart/form-data; boundary=#{boundary}"
|
|
171
|
+
req.body = encode_multipart(form, form_files, boundary)
|
|
172
|
+
elsif !body.nil?
|
|
173
|
+
req["content-type"] = "application/json"
|
|
174
|
+
req.body = JSON.generate(body)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
response = perform(uri, req, options)
|
|
178
|
+
status = response.code.to_i
|
|
179
|
+
raise build_error(response, status, http_method, url) unless (200..299).cover?(status)
|
|
180
|
+
|
|
181
|
+
decode(response, status, accept)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def perform(uri, req, options)
|
|
187
|
+
return @http_handler.call(uri, req) if @http_handler
|
|
188
|
+
|
|
189
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
|
190
|
+
http.use_ssl = uri.scheme == "https"
|
|
191
|
+
read_timeout = options.fetch(:timeout, @timeout)
|
|
192
|
+
open_timeout = options.fetch(:open_timeout, @open_timeout)
|
|
193
|
+
http.read_timeout = read_timeout if read_timeout
|
|
194
|
+
http.open_timeout = open_timeout if open_timeout
|
|
195
|
+
http.start { |connection| connection.request(req) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Fill `{param}` placeholders from the call, falling back to client config.
|
|
199
|
+
def resolve_path(http_method, path, path_params)
|
|
200
|
+
path.gsub(/\{([^}]+)\}/) do
|
|
201
|
+
name = Regexp.last_match(1)
|
|
202
|
+
value = path_params && path_params.key?(name) ? path_params[name] : nil
|
|
203
|
+
value = @defaults[name] if value.nil?
|
|
204
|
+
if value.nil? || value.to_s.empty?
|
|
205
|
+
hint =
|
|
206
|
+
if name == SCOPE_PARAM
|
|
207
|
+
" — pass it, or set `org_id:` when constructing the client."
|
|
208
|
+
else
|
|
209
|
+
"."
|
|
210
|
+
end
|
|
211
|
+
raise ConfigurationError, "Missing path parameter \"#{name}\" for #{http_method} #{path}#{hint}"
|
|
212
|
+
end
|
|
213
|
+
value.to_s.b.gsub(PATH_SAFE) { |byte| format("%%%02X", byte.ord) }
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def serialize_query(query)
|
|
218
|
+
return "" if query.nil?
|
|
219
|
+
|
|
220
|
+
pairs = []
|
|
221
|
+
query.each do |name, value|
|
|
222
|
+
next if value.nil?
|
|
223
|
+
|
|
224
|
+
if value.is_a?(Array)
|
|
225
|
+
value.each { |item| pairs << [name.to_s, item.to_s] unless item.nil? }
|
|
226
|
+
else
|
|
227
|
+
pairs << [name.to_s, value.to_s]
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
pairs.empty? ? "" : "?#{URI.encode_www_form(pairs)}"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def encode_multipart(fields, form_files, boundary)
|
|
234
|
+
files = Array(form_files).map(&:to_s)
|
|
235
|
+
parts = []
|
|
236
|
+
fields.each do |name, value|
|
|
237
|
+
next if value.nil?
|
|
238
|
+
|
|
239
|
+
key = name.to_s
|
|
240
|
+
if files.include?(key)
|
|
241
|
+
upload = Upload.coerce(value)
|
|
242
|
+
parts << +"--#{boundary}\r\n" \
|
|
243
|
+
"Content-Disposition: form-data; name=\"#{escape_part(key)}\"; " \
|
|
244
|
+
"filename=\"#{escape_part(upload.filename)}\"\r\n" \
|
|
245
|
+
"Content-Type: #{upload.content_type}\r\n\r\n"
|
|
246
|
+
parts << upload.bytes
|
|
247
|
+
parts << "\r\n"
|
|
248
|
+
else
|
|
249
|
+
# Anything that isn't a file goes over as a scalar. A Hash or Array
|
|
250
|
+
# here would be ambiguous on the wire, so it is JSON-encoded rather
|
|
251
|
+
# than silently stringified into `{"a"=>1}`.
|
|
252
|
+
scalar = value.is_a?(Hash) || value.is_a?(Array) ? JSON.generate(value) : value.to_s
|
|
253
|
+
parts << +"--#{boundary}\r\n" \
|
|
254
|
+
"Content-Disposition: form-data; name=\"#{escape_part(key)}\"\r\n\r\n"
|
|
255
|
+
parts << scalar
|
|
256
|
+
parts << "\r\n"
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
parts << "--#{boundary}--\r\n"
|
|
260
|
+
parts.map(&:b).join
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Per WHATWG, a quote inside a multipart field name or filename is
|
|
264
|
+
# percent-escaped rather than backslash-escaped.
|
|
265
|
+
def escape_part(value)
|
|
266
|
+
value.to_s.gsub("\"", "%22").gsub(/[\r\n]/, "")
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def decode(response, status, accept)
|
|
270
|
+
return (response.body || "").b if accept == :binary
|
|
271
|
+
return nil if accept == :empty || status == 204 || status == 205
|
|
272
|
+
|
|
273
|
+
text = response.body
|
|
274
|
+
return nil if text.nil? || text.empty?
|
|
275
|
+
return text unless (response["content-type"] || "").include?("json")
|
|
276
|
+
|
|
277
|
+
JSON.parse(text)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def build_error(response, status, http_method, url)
|
|
281
|
+
text = response.body || ""
|
|
282
|
+
body = text
|
|
283
|
+
unless text.empty?
|
|
284
|
+
begin
|
|
285
|
+
body = JSON.parse(text)
|
|
286
|
+
rescue JSON::ParserError
|
|
287
|
+
# Not JSON — keep the raw text as the body.
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
record = body.is_a?(Hash) ? body : {}
|
|
291
|
+
detail = record["error"] || record["message"] || "#{status} Request failed"
|
|
292
|
+
ApiError.new(
|
|
293
|
+
status: status,
|
|
294
|
+
message: "#{http_method} #{url} failed: #{detail}",
|
|
295
|
+
code: record["code"].is_a?(String) ? record["code"] : nil,
|
|
296
|
+
body: body,
|
|
297
|
+
http_method: http_method,
|
|
298
|
+
url: url
|
|
299
|
+
)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# infrawrench-sdk v0.1.1 | MIT | Copyright (c) 2026 Infrawrench LLC
|
|
4
|
+
# https://github.com/Infrawrench/Infrawrench
|
|
5
|
+
#
|
|
6
|
+
# Generated from the Infrawrench API OpenAPI 3.1 spec (API version 0.1.1).
|
|
7
|
+
#
|
|
8
|
+
# DO NOT EDIT. Regenerate with:
|
|
9
|
+
# pnpm --filter @infrawrench/web generate:sdk
|
|
10
|
+
#
|
|
11
|
+
# Internal routes are absent by construction: the generator consumes the same
|
|
12
|
+
# published spec that /openapi.json serves, which drops every operation
|
|
13
|
+
# marked x-internal.
|
|
14
|
+
|
|
15
|
+
# Kept in its own file so the gemspec can read the version without loading the
|
|
16
|
+
# client — a gemspec that pulls in net/http is a gemspec that can fail to parse.
|
|
17
|
+
module Infrawrench
|
|
18
|
+
VERSION = "0.1.1"
|
|
19
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# infrawrench-sdk v0.1.1 | MIT | Copyright (c) 2026 Infrawrench LLC
|
|
4
|
+
# https://github.com/Infrawrench/Infrawrench
|
|
5
|
+
#
|
|
6
|
+
# Generated from the Infrawrench API OpenAPI 3.1 spec (API version 0.1.1).
|
|
7
|
+
#
|
|
8
|
+
# DO NOT EDIT. Regenerate with:
|
|
9
|
+
# pnpm --filter @infrawrench/web generate:sdk
|
|
10
|
+
#
|
|
11
|
+
# Internal routes are absent by construction: the generator consumes the same
|
|
12
|
+
# published spec that /openapi.json serves, which drops every operation
|
|
13
|
+
# marked x-internal.
|
|
14
|
+
|
|
15
|
+
# The gem is called "infrawrench-sdk" but its code lives under infrawrench/, so
|
|
16
|
+
# `require "infrawrench-sdk"` would otherwise fail for anyone who typed the
|
|
17
|
+
# gem name they installed.
|
|
18
|
+
require_relative "infrawrench/sdk"
|