@apollo-deploy/tesseract 0.9.1 → 1.2.0
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.
- package/dist/adapters/csharp/index.d.ts +12 -0
- package/dist/adapters/csharp/index.d.ts.map +1 -0
- package/dist/adapters/csharp/index.js +99 -0
- package/dist/adapters/csharp/index.js.map +1 -0
- package/dist/adapters/go/index.d.ts +12 -0
- package/dist/adapters/go/index.d.ts.map +1 -0
- package/dist/adapters/go/index.js +135 -0
- package/dist/adapters/go/index.js.map +1 -0
- package/dist/adapters/kotlin/index.d.ts +13 -0
- package/dist/adapters/kotlin/index.d.ts.map +1 -0
- package/dist/adapters/kotlin/index.js +188 -0
- package/dist/adapters/kotlin/index.js.map +1 -0
- package/dist/adapters/php/index.d.ts +20 -0
- package/dist/adapters/php/index.d.ts.map +1 -0
- package/dist/adapters/php/index.js +123 -0
- package/dist/adapters/php/index.js.map +1 -0
- package/dist/adapters/python/index.d.ts +12 -0
- package/dist/adapters/python/index.d.ts.map +1 -0
- package/dist/adapters/python/index.js +145 -0
- package/dist/adapters/python/index.js.map +1 -0
- package/dist/adapters/ruby/index.d.ts +12 -0
- package/dist/adapters/ruby/index.d.ts.map +1 -0
- package/dist/adapters/ruby/index.js +86 -0
- package/dist/adapters/ruby/index.js.map +1 -0
- package/dist/adapters/rust/index.d.ts +13 -0
- package/dist/adapters/rust/index.d.ts.map +1 -0
- package/dist/adapters/rust/index.js +213 -0
- package/dist/adapters/rust/index.js.map +1 -0
- package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +5 -3
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
- package/dist/adapters/typescript/index.d.ts.map +1 -1
- package/dist/adapters/typescript/index.js +43 -2
- package/dist/adapters/typescript/index.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/intake.d.ts.map +1 -1
- package/dist/pipeline/intake.js +32 -21
- package/dist/pipeline/intake.js.map +1 -1
- package/dist/types/ir.d.ts +1 -1
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/sdk-module.d.ts +22 -2
- package/dist/types/sdk-module.d.ts.map +1 -1
- package/dist/types/sdk-module.js +1 -1
- package/package.json +1 -1
- package/templates/csharp/client.hbs +95 -0
- package/templates/csharp/csproj.hbs +18 -0
- package/templates/csharp/domain.hbs +183 -0
- package/templates/csharp/errors.hbs +45 -0
- package/templates/csharp/readme.hbs +76 -0
- package/templates/csharp/transport.hbs +291 -0
- package/templates/csharp/types.hbs +104 -0
- package/templates/go/client.hbs +158 -0
- package/templates/go/domain.hbs +89 -0
- package/templates/go/errors.hbs +38 -0
- package/templates/go/go-mod.hbs +6 -0
- package/templates/go/readme.hbs +73 -0
- package/templates/go/transport.hbs +379 -0
- package/templates/go/types.hbs +80 -0
- package/templates/go/utils.hbs +45 -0
- package/templates/kotlin/build-gradle.hbs +151 -0
- package/templates/kotlin/client.hbs +57 -0
- package/templates/kotlin/domain.hbs +73 -0
- package/templates/kotlin/errors.hbs +42 -0
- package/templates/kotlin/gradle-properties.hbs +2 -0
- package/templates/kotlin/readme.hbs +133 -0
- package/templates/kotlin/settings-gradle.hbs +1 -0
- package/templates/kotlin/transport.hbs +246 -0
- package/templates/kotlin/types.hbs +100 -0
- package/templates/php/client.hbs +74 -0
- package/templates/php/composer.hbs +19 -0
- package/templates/php/domain.hbs +173 -0
- package/templates/php/errors.hbs +131 -0
- package/templates/php/readme.hbs +89 -0
- package/templates/php/transport.hbs +285 -0
- package/templates/php/types.hbs +95 -0
- package/templates/python/client.hbs +102 -0
- package/templates/python/domain.hbs +139 -0
- package/templates/python/index.hbs +20 -0
- package/templates/python/pyproject.hbs +26 -0
- package/templates/python/readme.hbs +64 -0
- package/templates/python/resources-init.hbs +11 -0
- package/templates/python/transport.hbs +191 -0
- package/templates/python/types-errors.hbs +75 -0
- package/templates/python/types.hbs +79 -0
- package/templates/ruby/Gemfile.hbs +5 -0
- package/templates/ruby/client.hbs +86 -0
- package/templates/ruby/domain.hbs +110 -0
- package/templates/ruby/errors.hbs +150 -0
- package/templates/ruby/gemspec.hbs +30 -0
- package/templates/ruby/index.hbs +54 -0
- package/templates/ruby/readme.hbs +113 -0
- package/templates/ruby/transport.hbs +177 -0
- package/templates/ruby/types.hbs +126 -0
- package/templates/ruby/version.hbs +5 -0
- package/templates/rust/api-mod.hbs +3 -0
- package/templates/rust/cargo.hbs +22 -0
- package/templates/rust/client.hbs +114 -0
- package/templates/rust/domain.hbs +94 -0
- package/templates/rust/error.hbs +74 -0
- package/templates/rust/lib.hbs +85 -0
- package/templates/rust/readme.hbs +77 -0
- package/templates/rust/transport.hbs +287 -0
- package/templates/rust/types.hbs +142 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../transport"
|
|
4
|
+
require_relative "../errors"
|
|
5
|
+
|
|
6
|
+
module {{pascalCase meta.clientName}}
|
|
7
|
+
# {{#if description}}{{description}}{{else}}{{name}} API domain{{/if}}
|
|
8
|
+
class {{pascalCase name}}
|
|
9
|
+
# @return [Transport]
|
|
10
|
+
attr_reader :transport
|
|
11
|
+
|
|
12
|
+
# @param transport [Transport]
|
|
13
|
+
def initialize(transport)
|
|
14
|
+
@transport = transport
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
{{#each operations}}
|
|
18
|
+
{{#if deprecated}}
|
|
19
|
+
# @deprecated {{#if deprecationMessage}}{{deprecationMessage}}{{else}}Use a replacement method.{{/if}}
|
|
20
|
+
{{/if}}
|
|
21
|
+
{{#if (or summary description)}}
|
|
22
|
+
# {{#if summary}}{{summary}}{{#if description}} — {{description}}{{/if}}{{else}}{{description}}{{/if}}
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{#if pathParams.length}}
|
|
25
|
+
# @param {{#each pathParams}}{{snakeCase name}} [{{type}}]{{#unless @last}}, {{/unless}}{{/each}}
|
|
26
|
+
{{/if}}
|
|
27
|
+
{{#if requestBody}}
|
|
28
|
+
# @param body [{{requestBody.type}}{{#unless requestBody.required}}, nil{{/unless}}] request body
|
|
29
|
+
{{/if}}
|
|
30
|
+
{{#if (or queryParams.length queryType)}}
|
|
31
|
+
# @param query [Hash, nil] query parameters
|
|
32
|
+
{{/if}}
|
|
33
|
+
{{#if (and headerParams.length headerType)}}
|
|
34
|
+
# @param headers [Hash, nil] extra request headers
|
|
35
|
+
{{/if}}
|
|
36
|
+
{{#if (and cookieParams.length cookieType)}}
|
|
37
|
+
# @param cookies [Hash, nil] cookie values
|
|
38
|
+
{{/if}}
|
|
39
|
+
{{#if timeoutMs}}
|
|
40
|
+
# @param timeout [Integer, nil] per-request timeout override (seconds)
|
|
41
|
+
{{/if}}
|
|
42
|
+
# @raise [{{pascalCase ../meta.clientName}}::SDKError]
|
|
43
|
+
# @return [{{responseType}}]
|
|
44
|
+
def {{snakeCase name}}({{#if pathParams.length}}{{#each pathParams}}{{snakeCase name}}:{{#if (or ../requestBody (or ../queryType ../queryParams.length) (or ../headerType ../cookieType))}}, {{/if}}{{/each}}{{/if}}{{#if requestBody}}body: nil{{#if (or ../queryType ../queryParams.length (or ../headerType ../cookieType))}}, {{/if}}{{/if}}{{#if (or queryParams.length queryType)}}query: nil{{#if (or ../headerType ../cookieType)}}, {{/if}}{{/if}}{{#if (and headerParams.length headerType)}}headers: {}{{#if ../cookieType}}, {{/if}}{{/if}}{{#if (and cookieParams.length cookieType)}}cookies: nil{{/if}})
|
|
45
|
+
{{#if (or pathParams.length headerParams.length cookieParams.length)}}
|
|
46
|
+
# Build request headers
|
|
47
|
+
request_headers = {}
|
|
48
|
+
{{#if (and headerParams.length headerType)}}
|
|
49
|
+
{{#each headerParams}}
|
|
50
|
+
request_headers["{{originalName}}"] = headers[:{{snakeCase name}}].to_s if headers[:{{snakeCase name}}]
|
|
51
|
+
{{/each}}
|
|
52
|
+
{{/if}}
|
|
53
|
+
{{#if (and cookieParams.length cookieType)}}
|
|
54
|
+
if cookies
|
|
55
|
+
cookie_pairs = []
|
|
56
|
+
{{#each cookieParams}}
|
|
57
|
+
cookie_pairs << "{{originalName}}=#{::URI.encode_www_form_component(cookies[:{{snakeCase name}}].to_s)}" if cookies[:{{snakeCase name}}]
|
|
58
|
+
{{/each}}
|
|
59
|
+
request_headers["Cookie"] = cookie_pairs.join("; ") unless cookie_pairs.empty?
|
|
60
|
+
end
|
|
61
|
+
{{/if}}
|
|
62
|
+
{{/if}}
|
|
63
|
+
|
|
64
|
+
{{#if pathParams.length}}
|
|
65
|
+
# Path parameter substitution
|
|
66
|
+
path_params = {
|
|
67
|
+
{{#each pathParams}}
|
|
68
|
+
{{originalName}}: {{snakeCase name}},
|
|
69
|
+
{{/each}}
|
|
70
|
+
}
|
|
71
|
+
{{/if}}
|
|
72
|
+
|
|
73
|
+
{{#if queryParams.length}}
|
|
74
|
+
# Query parameters
|
|
75
|
+
query_params = {}
|
|
76
|
+
{{#each queryParams}}
|
|
77
|
+
query_params["{{originalName}}"] = query[:{{snakeCase name}}] if query && query.key?(:{{snakeCase name}})
|
|
78
|
+
{{/each}}
|
|
79
|
+
query_params = nil if query_params.empty?
|
|
80
|
+
{{else if queryType}}
|
|
81
|
+
# Free-form query parameters
|
|
82
|
+
query_params = query
|
|
83
|
+
{{/if}}
|
|
84
|
+
|
|
85
|
+
transport.request(
|
|
86
|
+
method: :{{httpMethod}},
|
|
87
|
+
path: "{{path}}",
|
|
88
|
+
{{#if pathParams.length}}
|
|
89
|
+
path_params: path_params,
|
|
90
|
+
{{/if}}
|
|
91
|
+
{{#if requestBody}}
|
|
92
|
+
body: body,
|
|
93
|
+
{{/if}}
|
|
94
|
+
{{#if (or queryParams.length queryType)}}
|
|
95
|
+
query: query_params,
|
|
96
|
+
{{/if}}
|
|
97
|
+
{{#if (or (and headerParams.length headerType) (and cookieParams.length cookieType))}}
|
|
98
|
+
headers: request_headers,
|
|
99
|
+
{{/if}}
|
|
100
|
+
{{#if (neq requestBody.contentType "application/json")}}
|
|
101
|
+
{{#if requestBody.contentType}}
|
|
102
|
+
content_type: "{{requestBody.contentType}}",
|
|
103
|
+
{{/if}}
|
|
104
|
+
{{/if}}
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
{{/each}}
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module {{pascalCase meta.clientName}}
|
|
4
|
+
# Structured error raised on API and network failures.
|
|
5
|
+
#
|
|
6
|
+
# Inherits from StandardError and provides structured access to error
|
|
7
|
+
# details including status code, error code, request ID, and convenience
|
|
8
|
+
# methods for common error classifications.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic error handling
|
|
11
|
+
# begin
|
|
12
|
+
# client.users.get_user(id: "123")
|
|
13
|
+
# rescue {{pascalCase meta.clientName}}::SDKError => e
|
|
14
|
+
# puts "Error #{e.status}: #{e.message} (#{e.code})"
|
|
15
|
+
# puts "Request ID: #{e.request_id}" if e.request_id
|
|
16
|
+
#
|
|
17
|
+
# if e.rate_limited?
|
|
18
|
+
# sleep 1
|
|
19
|
+
# retry
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# if e.server_error?
|
|
23
|
+
# # Log and alert
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
class SDKError < StandardError
|
|
27
|
+
# @return [String] machine-readable error code (e.g. "not_found", "rate_limit_exceeded")
|
|
28
|
+
attr_reader :code
|
|
29
|
+
|
|
30
|
+
# @return [Integer] HTTP status code (0 for network errors)
|
|
31
|
+
attr_reader :status
|
|
32
|
+
|
|
33
|
+
# @return [String, nil] unique request identifier from the server
|
|
34
|
+
attr_reader :request_id
|
|
35
|
+
|
|
36
|
+
# @return [String, nil] ISO 8601 timestamp of error occurrence
|
|
37
|
+
attr_reader :timestamp
|
|
38
|
+
|
|
39
|
+
# @return [String, nil] request path that caused the error
|
|
40
|
+
attr_reader :path
|
|
41
|
+
|
|
42
|
+
# @return [String, nil] HTTP method that caused the error
|
|
43
|
+
attr_reader :method
|
|
44
|
+
|
|
45
|
+
# @return [String, nil] helpful hint for resolving the error
|
|
46
|
+
attr_reader :hint
|
|
47
|
+
|
|
48
|
+
# @return [Hash, nil] structured error details
|
|
49
|
+
attr_reader :details
|
|
50
|
+
|
|
51
|
+
# Create a new SDKError.
|
|
52
|
+
#
|
|
53
|
+
# @param message [String] human-readable error message
|
|
54
|
+
# @param status [Integer] HTTP status code (0 for network errors)
|
|
55
|
+
# @param code [String] machine-readable error code
|
|
56
|
+
# @param request_id [String, nil] server-provided request ID
|
|
57
|
+
# @param timestamp [String, nil] error timestamp
|
|
58
|
+
# @param path [String, nil] request path
|
|
59
|
+
# @param method [String, nil] HTTP method
|
|
60
|
+
# @param hint [String, nil] resolution hint
|
|
61
|
+
# @param details [Hash, nil] structured error details
|
|
62
|
+
def initialize(
|
|
63
|
+
message,
|
|
64
|
+
status = 0,
|
|
65
|
+
code = "unknown_error",
|
|
66
|
+
request_id: nil,
|
|
67
|
+
timestamp: nil,
|
|
68
|
+
path: nil,
|
|
69
|
+
method: nil,
|
|
70
|
+
hint: nil,
|
|
71
|
+
details: nil
|
|
72
|
+
)
|
|
73
|
+
super(message)
|
|
74
|
+
|
|
75
|
+
@code = code
|
|
76
|
+
@status = status
|
|
77
|
+
@request_id = request_id
|
|
78
|
+
@timestamp = timestamp
|
|
79
|
+
@path = path
|
|
80
|
+
@method = method
|
|
81
|
+
@hint = hint
|
|
82
|
+
@details = details
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Boolean] true if the error is a rate limit (HTTP 429)
|
|
86
|
+
def rate_limited?
|
|
87
|
+
@code == "rate_limit_exceeded" || @status == 429
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Boolean] true if the error is authentication-related
|
|
91
|
+
def auth_error?
|
|
92
|
+
%w[unauthorized credential_expired credential_revoked].include?(@code)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [Boolean] true if the error is a permission error (HTTP 403)
|
|
96
|
+
def forbidden?
|
|
97
|
+
@code == "forbidden" || @status == 403
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @return [Boolean] true if the resource was not found (HTTP 404)
|
|
101
|
+
def not_found?
|
|
102
|
+
@code == "not_found" || @status == 404
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [Boolean] true if the error is a validation error
|
|
106
|
+
def validation_error?
|
|
107
|
+
%w[validation_failed request_validation_failed bad_request unprocessable_entity].include?(@code) ||
|
|
108
|
+
@status == 422
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @return [Boolean] true if status is 5xx (server error)
|
|
112
|
+
def server_error?
|
|
113
|
+
@status >= 500
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @return [Boolean] true if status is 4xx (client error)
|
|
117
|
+
def client_error?
|
|
118
|
+
@status >= 400 && @status < 500
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Boolean] true if this error is safe to retry
|
|
122
|
+
def retryable?
|
|
123
|
+
@code == "network_error" ||
|
|
124
|
+
@code == "gateway_timeout" ||
|
|
125
|
+
@status == 429 ||
|
|
126
|
+
@status >= 500
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @return [Hash] a hash representation for logging and serialization
|
|
130
|
+
def to_h
|
|
131
|
+
{
|
|
132
|
+
name: "SDKError",
|
|
133
|
+
message: message,
|
|
134
|
+
status: @status,
|
|
135
|
+
code: @code,
|
|
136
|
+
request_id: @request_id,
|
|
137
|
+
timestamp: @timestamp,
|
|
138
|
+
path: @path,
|
|
139
|
+
method: @method,
|
|
140
|
+
hint: @hint,
|
|
141
|
+
details: @details
|
|
142
|
+
}.compact
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @return [String] JSON representation for logging
|
|
146
|
+
def to_json(*args)
|
|
147
|
+
to_h.to_json(*args)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/{{snakeModule}}/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "{{gemName}}"
|
|
7
|
+
spec.version = {{pascalCase snakeModule}}::VERSION
|
|
8
|
+
spec.authors = ["API SDK Generator"]
|
|
9
|
+
spec.email = ["sdk@example.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "{{#if meta.description}}{{meta.description}}{{else}}Ruby SDK for {{meta.title}}{{/if}}"
|
|
12
|
+
spec.description = "{{#if meta.description}}{{meta.description}}{{else}}A Ruby client library for the {{meta.title}} API.{{/if}}"
|
|
13
|
+
spec.homepage = "https://github.com/example/{{gemName}}"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/example/{{gemName}}"
|
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/example/{{gemName}}/blob/main/CHANGELOG.md"
|
|
20
|
+
|
|
21
|
+
spec.files = Dir.glob("lib/**/*.rb") + %w[README.md LICENSE Gemfile]
|
|
22
|
+
spec.require_paths = ["lib"]
|
|
23
|
+
|
|
24
|
+
spec.add_dependency "faraday", "~> 2.0"
|
|
25
|
+
spec.add_dependency "json"
|
|
26
|
+
|
|
27
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
29
|
+
spec.add_development_dependency "webmock", "~> 3.0"
|
|
30
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "{{snakeModule}}/version"
|
|
4
|
+
require_relative "{{snakeModule}}/errors"
|
|
5
|
+
require_relative "{{snakeModule}}/transport"
|
|
6
|
+
require_relative "{{snakeModule}}/types"
|
|
7
|
+
require_relative "{{snakeModule}}/client"
|
|
8
|
+
{{#each groups}}
|
|
9
|
+
require_relative "{{../snakeModule}}/resources/{{fileName}}"
|
|
10
|
+
{{/each}}
|
|
11
|
+
|
|
12
|
+
module {{pascalCase snakeModule}}
|
|
13
|
+
# Factory method to create a new client instance.
|
|
14
|
+
#
|
|
15
|
+
# @param base_url [String] the base URL for the API
|
|
16
|
+
# @param timeout [Integer] request timeout in seconds (default: 15)
|
|
17
|
+
# @param retries [Hash] retry configuration
|
|
18
|
+
# @option retries [Integer] :attempts (3)
|
|
19
|
+
# @option retries [Float] :backoff (0.4)
|
|
20
|
+
# @option retries [Boolean] :jitter (true)
|
|
21
|
+
# @option retries [Float] :max_backoff (30.0)
|
|
22
|
+
{{#each securitySchemes}}
|
|
23
|
+
# @param {{camelCase name}} [String, nil] {{#if description}}{{description}}{{else}}credential for {{name}}{{/if}}
|
|
24
|
+
{{/each}}
|
|
25
|
+
{{#if hasSSE}}
|
|
26
|
+
# @param sse_client [Object, nil] optional SSE client implementation
|
|
27
|
+
{{/if}}
|
|
28
|
+
# @return [{{pascalCase meta.clientName}}::Client]
|
|
29
|
+
def self.create_client(
|
|
30
|
+
base_url: '{{meta.baseUrl}}',
|
|
31
|
+
timeout: 15,
|
|
32
|
+
retries: { attempts: 3, backoff: 0.4, jitter: true, max_backoff: 30.0 },
|
|
33
|
+
{{#each securitySchemes}}
|
|
34
|
+
{{camelCase name}}: nil,
|
|
35
|
+
{{/each}}
|
|
36
|
+
{{#if hasSSE}}
|
|
37
|
+
sse_client: nil,
|
|
38
|
+
{{/if}}
|
|
39
|
+
**extra
|
|
40
|
+
)
|
|
41
|
+
{{pascalCase meta.clientName}}::Client.new(
|
|
42
|
+
base_url: base_url,
|
|
43
|
+
timeout: timeout,
|
|
44
|
+
retries: retries,
|
|
45
|
+
{{#each securitySchemes}}
|
|
46
|
+
{{camelCase name}}: {{camelCase name}},
|
|
47
|
+
{{/each}}
|
|
48
|
+
{{#if hasSSE}}
|
|
49
|
+
sse_client: sse_client,
|
|
50
|
+
{{/if}}
|
|
51
|
+
**extra
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# {{meta.title}} Ruby SDK
|
|
2
|
+
|
|
3
|
+
{{#if meta.description}}
|
|
4
|
+
{{meta.description}}
|
|
5
|
+
|
|
6
|
+
{{/if}}
|
|
7
|
+
> This SDK was automatically generated by [Tesseract](https://www.npmjs.com/package/@apollo-deploy/tesseract).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add this line to your application's Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem '{{gemName}}'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
And then execute:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install it yourself as:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem install {{gemName}}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require '{{gemName}}'
|
|
33
|
+
|
|
34
|
+
client = {{pascalCase meta.clientName}}::Client.new(
|
|
35
|
+
base_url: '{{meta.baseUrl}}',
|
|
36
|
+
{{#each securitySchemes}}
|
|
37
|
+
{{camelCase name}}: 'your-{{kebabCase name}}-credential',
|
|
38
|
+
{{/each}}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Make API calls
|
|
42
|
+
result = client.{{groups.[0].name}}.{{groups.[0].operations.[0].name}}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
client = {{pascalCase meta.clientName}}::Client.new(
|
|
49
|
+
base_url: '{{meta.baseUrl}}',
|
|
50
|
+
{{#each securitySchemes}}
|
|
51
|
+
{{camelCase name}}: ENV['{{upperCase (snakeCase name)}}'],
|
|
52
|
+
{{/each}}
|
|
53
|
+
timeout: 30, # seconds
|
|
54
|
+
retries: { attempts: 3, backoff: 0.4, jitter: true }
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
{{#each groups}}
|
|
61
|
+
### {{pascalCase name}}
|
|
62
|
+
|
|
63
|
+
{{#if description}}
|
|
64
|
+
{{description}}
|
|
65
|
+
|
|
66
|
+
{{/if}}
|
|
67
|
+
| Method | HTTP | Path | Description |
|
|
68
|
+
|--------|------|------|-------------|
|
|
69
|
+
{{#each operations}}
|
|
70
|
+
| `{{snakeCase name}}` | {{httpMethod}} | `{{path}}` | {{#if summary}}{{summary}}{{else}}-{{/if}} |
|
|
71
|
+
{{/each}}
|
|
72
|
+
|
|
73
|
+
{{/each}}
|
|
74
|
+
|
|
75
|
+
## Types
|
|
76
|
+
|
|
77
|
+
{{#each schemas}}
|
|
78
|
+
{{#if isEnum}}
|
|
79
|
+
- `{{pascalCase name}}` — module with constants: {{#each enumValues}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}
|
|
80
|
+
{{else if isTypeAlias}}
|
|
81
|
+
- `{{pascalCase name}}` — type alias
|
|
82
|
+
{{else}}
|
|
83
|
+
- `{{pascalCase name}}`{{#if description}} — {{description}}{{/if}}
|
|
84
|
+
{{/if}}
|
|
85
|
+
{{/each}}
|
|
86
|
+
|
|
87
|
+
## Error Handling
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
begin
|
|
91
|
+
result = client.some_api.some_method
|
|
92
|
+
rescue {{pascalCase meta.clientName}}::SDKError => e
|
|
93
|
+
puts "Error #{e.status}: #{e.message} (#{e.code})"
|
|
94
|
+
puts "Request ID: #{e.request_id}" if e.request_id
|
|
95
|
+
|
|
96
|
+
if e.rate_limited?
|
|
97
|
+
sleep 1
|
|
98
|
+
retry
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
raise
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
|
108
|
+
Then, run `rake spec` to run the tests.
|
|
109
|
+
You can also run `bin/console` for an interactive prompt.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "errors"
|
|
6
|
+
|
|
7
|
+
module {{pascalCase meta.clientName}}
|
|
8
|
+
# HTTP transport layer built on Faraday.
|
|
9
|
+
class Transport
|
|
10
|
+
attr_reader :config
|
|
11
|
+
attr_reader :connection
|
|
12
|
+
|
|
13
|
+
def initialize(config)
|
|
14
|
+
@config = config
|
|
15
|
+
@connection = Faraday.new(url: config.base_url) do |f|
|
|
16
|
+
f.request :json
|
|
17
|
+
f.response :raise_error
|
|
18
|
+
f.options.timeout = config.timeout
|
|
19
|
+
f.options.open_timeout = [config.timeout / 3, 2].max
|
|
20
|
+
config.default_headers.each do |key, value|
|
|
21
|
+
f.headers[key] = value
|
|
22
|
+
end
|
|
23
|
+
f.adapter Faraday.default_adapter
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def request(method:, path:, path_params: {}, query: nil, body: nil, headers: {}, content_type: nil)
|
|
28
|
+
url = build_url(path, path_params)
|
|
29
|
+
attempts = 0
|
|
30
|
+
max_attempts = [(@config.retries[:attempts] || 3), 1].max
|
|
31
|
+
backoff = @config.retries[:backoff] || 0.4
|
|
32
|
+
jitter = @config.retries.fetch(:jitter, true)
|
|
33
|
+
max_backoff = @config.retries[:max_backoff] || 30.0
|
|
34
|
+
begin
|
|
35
|
+
attempts += 1
|
|
36
|
+
response = @connection.run_request(
|
|
37
|
+
method.to_sym, url, body, build_headers(headers, content_type)
|
|
38
|
+
) do |req|
|
|
39
|
+
req.params.merge!(query) if query && !query.empty?
|
|
40
|
+
end
|
|
41
|
+
parse_response(response)
|
|
42
|
+
rescue Faraday::Error => e
|
|
43
|
+
raise map_error(e, method: method, path: path) unless should_retry?(e, attempts, max_attempts)
|
|
44
|
+
sleep(retry_delay(backoff, attempts, jitter, max_backoff))
|
|
45
|
+
retry
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def get(path:, path_params: {}, query: nil, headers: {})
|
|
50
|
+
request(method: :get, path: path, path_params: path_params, query: query, headers: headers)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def post(path:, body: nil, path_params: {}, query: nil, headers: {}, content_type: nil)
|
|
54
|
+
request(method: :post, path: path, body: body, path_params: path_params,
|
|
55
|
+
query: query, headers: headers, content_type: content_type)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def put(path:, body: nil, path_params: {}, query: nil, headers: {}, content_type: nil)
|
|
59
|
+
request(method: :put, path: path, body: body, path_params: path_params,
|
|
60
|
+
query: query, headers: headers, content_type: content_type)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def patch(path:, body: nil, path_params: {}, query: nil, headers: {}, content_type: nil)
|
|
64
|
+
request(method: :patch, path: path, body: body, path_params: path_params,
|
|
65
|
+
query: query, headers: headers, content_type: content_type)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def delete(path:, path_params: {}, query: nil, headers: {})
|
|
69
|
+
request(method: :delete, path: path, path_params: path_params, query: query, headers: headers)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def build_url(path, path_params)
|
|
75
|
+
return path if path_params.empty?
|
|
76
|
+
path.gsub(/\{(\w+)\}/) do |_match|
|
|
77
|
+
key = ::Regexp.last_match(1)
|
|
78
|
+
value = path_params[key.to_sym] || path_params[key]
|
|
79
|
+
value.nil? ? _match : value.to_s
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_headers(extra_headers = {}, content_type = nil)
|
|
84
|
+
headers = {}
|
|
85
|
+
if content_type
|
|
86
|
+
headers["Content-Type"] = content_type
|
|
87
|
+
end
|
|
88
|
+
{{#each securitySchemes}}
|
|
89
|
+
{{#if (eq type "apiKey")}}
|
|
90
|
+
{{#if (eq in "header")}}
|
|
91
|
+
headers["{{paramName}}"] = @config.{{camelCase name}} if @config.{{camelCase name}}
|
|
92
|
+
{{/if}}
|
|
93
|
+
{{/if}}
|
|
94
|
+
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
95
|
+
headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
|
|
96
|
+
{{/if}}
|
|
97
|
+
{{#if (eq type "oauth2")}}
|
|
98
|
+
headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
|
|
99
|
+
{{/if}}
|
|
100
|
+
{{#if (eq type "openIdConnect")}}
|
|
101
|
+
headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
|
|
102
|
+
{{/if}}
|
|
103
|
+
{{/each}}
|
|
104
|
+
headers.merge!(extra_headers)
|
|
105
|
+
headers
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def parse_response(response)
|
|
109
|
+
body = response.body
|
|
110
|
+
return nil if body.nil? || (body.is_a?(String) && body.strip.empty?)
|
|
111
|
+
if body.is_a?(String)
|
|
112
|
+
begin
|
|
113
|
+
JSON.parse(body)
|
|
114
|
+
rescue JSON::ParserError
|
|
115
|
+
body
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
body
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def map_error(error, method:, path:)
|
|
123
|
+
case error
|
|
124
|
+
when Faraday::TimeoutError
|
|
125
|
+
SDKError.new("Request timed out", 0, "network_error", method: method, path: path)
|
|
126
|
+
when Faraday::ConnectionFailed
|
|
127
|
+
SDKError.new("Connection failed: " + error.message, 0, "network_error", method: method, path: path)
|
|
128
|
+
when Faraday::ResourceNotFound
|
|
129
|
+
SDKError.new("Not found: " + error.message, 404, "not_found", method: method, path: path)
|
|
130
|
+
when Faraday::ClientError
|
|
131
|
+
status = error.response&.[](:status) || 400
|
|
132
|
+
begin
|
|
133
|
+
body = error.response&.[](:body)
|
|
134
|
+
envelope = body.is_a?(String) ? JSON.parse(body) : body
|
|
135
|
+
if envelope.is_a?(Hash)
|
|
136
|
+
SDKError.new(
|
|
137
|
+
envelope["message"] || envelope["error"] || "Request failed",
|
|
138
|
+
status,
|
|
139
|
+
envelope["error"] || envelope["code"] || "unknown_error",
|
|
140
|
+
request_id: envelope["request_id"],
|
|
141
|
+
method: method, path: path
|
|
142
|
+
)
|
|
143
|
+
else
|
|
144
|
+
SDKError.new("Request failed with status " + status.to_s, status, "unknown_error", method: method, path: path)
|
|
145
|
+
end
|
|
146
|
+
rescue
|
|
147
|
+
SDKError.new("Request failed with status " + status.to_s, status, "unknown_error", method: method, path: path)
|
|
148
|
+
end
|
|
149
|
+
when Faraday::ServerError
|
|
150
|
+
status = error.response&.[](:status) || 500
|
|
151
|
+
SDKError.new("Server error: " + error.message, status, "internal_server_error", method: method, path: path)
|
|
152
|
+
else
|
|
153
|
+
SDKError.new(error.message, 0, "unknown_error", method: method, path: path)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def should_retry?(_error, attempts, max_attempts)
|
|
158
|
+
return false if attempts >= max_attempts
|
|
159
|
+
case _error
|
|
160
|
+
when Faraday::TimeoutError, Faraday::ConnectionFailed, Faraday::ServerError
|
|
161
|
+
true
|
|
162
|
+
when Faraday::ClientError
|
|
163
|
+
status = _error.response&.[](:status)
|
|
164
|
+
status == 429
|
|
165
|
+
else
|
|
166
|
+
false
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def retry_delay(base, attempt, jitter, max_backoff)
|
|
171
|
+
delay = base * (2**(attempt - 1))
|
|
172
|
+
delay = max_backoff if delay > max_backoff
|
|
173
|
+
delay *= (0.5 + rand * 0.5) if jitter
|
|
174
|
+
delay
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|