@apollo-deploy/tesseract 0.9.1 → 1.0.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 +186 -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 +2 -2
- 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/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.js +23 -16
- 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 +41 -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 +105 -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,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
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module {{pascalCase meta.clientName}}
|
|
4
|
+
{{#each schemas}}
|
|
5
|
+
{{#if isEnum}}
|
|
6
|
+
# {{#if description}}{{description}}{{else}}Enum for {{name}} values.{{/if}}
|
|
7
|
+
module {{pascalCase name}}
|
|
8
|
+
{{#each enumValues}}
|
|
9
|
+
{{#if (isString this)}}
|
|
10
|
+
{{upperCase (snakeCase this)}} = "{{this}}"
|
|
11
|
+
{{else}}
|
|
12
|
+
{{upperCase name}}_{{this}} = {{this}}
|
|
13
|
+
{{/if}}
|
|
14
|
+
{{/each}}
|
|
15
|
+
|
|
16
|
+
# All valid enum values.
|
|
17
|
+
# @return [Array]
|
|
18
|
+
ALL = [
|
|
19
|
+
{{#each enumValues}}
|
|
20
|
+
{{#if (isString this)}}
|
|
21
|
+
{{upperCase (snakeCase this)}},
|
|
22
|
+
{{else}}
|
|
23
|
+
{{upperCase ../name}}_{{this}},
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{/each}}
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
# Check if a value is valid for this enum.
|
|
29
|
+
# @param value [String, Integer]
|
|
30
|
+
# @return [Boolean]
|
|
31
|
+
def self.valid?(value)
|
|
32
|
+
ALL.include?(value)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
{{else if isTypeAlias}}
|
|
37
|
+
{{#if unionMembers}}
|
|
38
|
+
# {{pascalCase name}} — type alias for: {{#each unionMembers}}{{this}}{{#unless @last}} | {{/unless}}{{/each}}
|
|
39
|
+
{{pascalCase name}} = Class.new
|
|
40
|
+
{{else}}
|
|
41
|
+
# {{pascalCase name}} — schema type alias
|
|
42
|
+
{{pascalCase name}} = Class.new
|
|
43
|
+
{{/if}}
|
|
44
|
+
|
|
45
|
+
{{else if isUnionType}}
|
|
46
|
+
# {{pascalCase name}} — union type: {{#each unionMembers}}{{this}}{{#unless @last}} | {{/unless}}{{/each}}
|
|
47
|
+
module {{pascalCase name}}
|
|
48
|
+
# Union members are typed schemas. Use `is_a?` or pattern matching at runtime.
|
|
49
|
+
{{#each unionMembers}}
|
|
50
|
+
# @see {{this}}
|
|
51
|
+
{{/each}}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
{{else}}
|
|
55
|
+
{{#if (and properties.length (not isUnionType))}}
|
|
56
|
+
# {{#if description}}{{description}}{{else}}Schema type: {{name}}{{/if}}
|
|
57
|
+
class {{pascalCase name}}
|
|
58
|
+
{{#each properties}}
|
|
59
|
+
# @return [{{type}}{{#if nullable}}, nil{{/if}}]{{#if description}} {{description}}{{/if}}
|
|
60
|
+
attr_accessor :{{snakeCase name}}
|
|
61
|
+
{{/each}}
|
|
62
|
+
|
|
63
|
+
# @param {{#each properties}}{{snakeCase name}} [{{type}}{{#if nullable}}, nil{{/if}}]{{#unless @last}}, {{/unless}}{{/each}}
|
|
64
|
+
def initialize({{#each properties}}{{snakeCase name}}: {{^required}}nil{{/required}}{{#unless @last}}, {{/unless}}{{/each}})
|
|
65
|
+
{{#each properties}}
|
|
66
|
+
@{{snakeCase name}} = {{snakeCase name}}
|
|
67
|
+
{{/each}}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [Hash] a hash representation of this object
|
|
71
|
+
def to_h
|
|
72
|
+
{
|
|
73
|
+
{{#each properties}}
|
|
74
|
+
{{#if (eq type "DateTime")}}
|
|
75
|
+
{{snakeCase name}}: @{{snakeCase name}}&.iso8601,
|
|
76
|
+
{{else}}
|
|
77
|
+
{{snakeCase name}}: @{{snakeCase name}},
|
|
78
|
+
{{/if}}
|
|
79
|
+
{{/each}}
|
|
80
|
+
}.compact
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Create an instance from a parsed JSON hash.
|
|
84
|
+
# @param attributes [Hash]
|
|
85
|
+
# @return [{{pascalCase name}}]
|
|
86
|
+
def self.from_json(attributes)
|
|
87
|
+
return nil unless attributes.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
new(
|
|
90
|
+
{{#each properties}}
|
|
91
|
+
{{snakeCase name}}: attributes["{{snakeCase name}}"] || attributes["{{originalName}}"] || attributes[:{{snakeCase name}}],
|
|
92
|
+
{{/each}}
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
{{else}}
|
|
98
|
+
# {{#if description}}{{description}}{{else}}Schema type without properties: {{name}}{{/if}}
|
|
99
|
+
class {{pascalCase name}}
|
|
100
|
+
# @return [Hash] raw attributes
|
|
101
|
+
attr_reader :attributes
|
|
102
|
+
|
|
103
|
+
# @param attributes [Hash]
|
|
104
|
+
def initialize(attributes = {})
|
|
105
|
+
@attributes = attributes
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @return [Hash]
|
|
109
|
+
def to_h
|
|
110
|
+
@attributes.dup
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Create an instance from a parsed JSON hash.
|
|
114
|
+
# @param attributes [Hash]
|
|
115
|
+
# @return [{{pascalCase name}}]
|
|
116
|
+
def self.from_json(attributes)
|
|
117
|
+
return nil unless attributes.is_a?(Hash)
|
|
118
|
+
|
|
119
|
+
new(attributes)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
{{/if}}
|
|
124
|
+
{{/if}}
|
|
125
|
+
{{/each}}
|
|
126
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "{{crateName}}"
|
|
3
|
+
version = "{{#if packageVersion}}{{packageVersion}}{{else}}0.1.0{{/if}}"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "{{#if description}}{{description}}{{else}}Rust SDK for {{title}}{{/if}}"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
repository = "https://github.com/{{#if packageName}}{{packageName}}{{else}}example/{{crateName}}{{/if}}"
|
|
8
|
+
|
|
9
|
+
[dependencies]
|
|
10
|
+
serde = { version = "1", features = ["derive"] }
|
|
11
|
+
serde_json = "1"
|
|
12
|
+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
|
13
|
+
tokio = { version = "1", features = ["full"] }
|
|
14
|
+
thiserror = "2"
|
|
15
|
+
url = "2"
|
|
16
|
+
{{#if hasSSE}}
|
|
17
|
+
futures-core = "0.3"
|
|
18
|
+
tokio-stream = "0.1"
|
|
19
|
+
{{/if}}
|
|
20
|
+
|
|
21
|
+
[dev-dependencies]
|
|
22
|
+
tokio-test = "0.4"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
use crate::transport::{Transport, TransportConfig};
|
|
2
|
+
{{#each groups}}
|
|
3
|
+
use crate::api::{{snakeCase fileName}}::{{interfaceName}};
|
|
4
|
+
{{/each}}
|
|
5
|
+
use std::time::Duration;
|
|
6
|
+
|
|
7
|
+
/// Configuration for the {{title}} API client.
|
|
8
|
+
#[derive(Debug, Clone)]
|
|
9
|
+
pub struct ClientConfig {
|
|
10
|
+
/// Base URL for the API.
|
|
11
|
+
pub base_url: String,
|
|
12
|
+
{{#each securitySchemes}}
|
|
13
|
+
{{#if (eq type "apiKey")}}
|
|
14
|
+
/// API key for the `{{name}}` security scheme.
|
|
15
|
+
pub {{snakeCase configKey}}: Option<String>,
|
|
16
|
+
{{/if}}
|
|
17
|
+
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
18
|
+
/// Bearer token for the `{{name}}` security scheme.
|
|
19
|
+
pub {{snakeCase configKey}}: Option<String>,
|
|
20
|
+
{{/if}}
|
|
21
|
+
{{#if (eq type "oauth2")}}
|
|
22
|
+
/// OAuth2 access token for the `{{name}}` security scheme.
|
|
23
|
+
pub {{snakeCase configKey}}: Option<String>,
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{#if (eq type "openIdConnect")}}
|
|
26
|
+
/// OpenID Connect access token for the `{{name}}` security scheme.
|
|
27
|
+
pub {{snakeCase configKey}}: Option<String>,
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{/each}}
|
|
30
|
+
/// Request timeout.
|
|
31
|
+
pub timeout: Duration,
|
|
32
|
+
/// Maximum number of retry attempts.
|
|
33
|
+
pub max_retries: u32,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl Default for ClientConfig {
|
|
37
|
+
fn default() -> Self {
|
|
38
|
+
Self {
|
|
39
|
+
base_url: "{{baseUrl}}".to_string(),
|
|
40
|
+
{{#each securitySchemes}}
|
|
41
|
+
{{snakeCase configKey}}: None,
|
|
42
|
+
{{/each}}
|
|
43
|
+
timeout: Duration::from_secs(15),
|
|
44
|
+
max_retries: 3,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Primary API client for {{title}}.
|
|
50
|
+
///
|
|
51
|
+
/// Provides access to all domain-specific API groups.
|
|
52
|
+
pub struct Client {
|
|
53
|
+
transport: Transport,
|
|
54
|
+
{{#each groups}}
|
|
55
|
+
/// {{#if description}}{{description}}{{else}}{{interfaceName}} operations.{{/if}}
|
|
56
|
+
pub {{snakeCase name}}: {{interfaceName}},
|
|
57
|
+
{{/each}}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl Client {
|
|
61
|
+
/// Create a new {{title}} API client with the given configuration.
|
|
62
|
+
///
|
|
63
|
+
/// # Example
|
|
64
|
+
///
|
|
65
|
+
/// ```rust,no_run
|
|
66
|
+
/// use {{crateName}}::Client;
|
|
67
|
+
/// use {{crateName}}::ClientConfig;
|
|
68
|
+
///
|
|
69
|
+
/// let config = ClientConfig {
|
|
70
|
+
{{#each securitySchemes}}
|
|
71
|
+
{{#if (or (eq type "apiKey") (and (eq type "http") (eq scheme "bearer")))}}
|
|
72
|
+
/// {{snakeCase configKey}}: Some("your-key".to_string()),
|
|
73
|
+
{{/if}}
|
|
74
|
+
{{/each}}
|
|
75
|
+
/// ..Default::default()
|
|
76
|
+
/// };
|
|
77
|
+
/// let client = Client::new(config);
|
|
78
|
+
/// ```
|
|
79
|
+
pub fn new(config: ClientConfig) -> Self {
|
|
80
|
+
let mut transport_config = TransportConfig {
|
|
81
|
+
base_url: config.base_url,
|
|
82
|
+
{{#each securitySchemes}}
|
|
83
|
+
{{snakeCase configKey}}: config.{{snakeCase configKey}},
|
|
84
|
+
{{/each}}
|
|
85
|
+
timeout: config.timeout,
|
|
86
|
+
max_retries: config.max_retries,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let transport = Transport::new(transport_config);
|
|
90
|
+
|
|
91
|
+
Self {
|
|
92
|
+
{{#each groups}}
|
|
93
|
+
{{snakeCase name}}: {{interfaceName}}::new(transport.clone()),
|
|
94
|
+
{{/each}}
|
|
95
|
+
transport,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Return a reference to the underlying transport layer.
|
|
100
|
+
pub fn transport(&self) -> &Transport {
|
|
101
|
+
&self.transport
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
impl std::fmt::Debug for Client {
|
|
106
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
107
|
+
f.debug_struct("Client")
|
|
108
|
+
.field("transport", &"<Transport>")
|
|
109
|
+
{{#each groups}}
|
|
110
|
+
.field("{{snakeCase name}}", &"<{{interfaceName}}>")
|
|
111
|
+
{{/each}}
|
|
112
|
+
.finish()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
use crate::transport::Transport;
|
|
2
|
+
use crate::error::SdkError;
|
|
3
|
+
use reqwest::Method;
|
|
4
|
+
{{#if imports.length}}
|
|
5
|
+
use crate::types::{ {{join imports ", "}} };
|
|
6
|
+
{{/if}}
|
|
7
|
+
|
|
8
|
+
/// {{#if description}}{{description}}{{else}}{{interfaceName}} API operations.{{/if}}
|
|
9
|
+
pub struct {{interfaceName}} {
|
|
10
|
+
transport: Transport,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl {{interfaceName}} {
|
|
14
|
+
pub(crate) fn new(transport: Transport) -> Self {
|
|
15
|
+
Self { transport }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
{{#each operations}}
|
|
19
|
+
{{#if summary}}
|
|
20
|
+
/// {{summary}}{{#if description}}
|
|
21
|
+
///
|
|
22
|
+
/// {{description}}{{/if}}
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{#if deprecated}}
|
|
25
|
+
#[deprecated{{#if deprecationMessage}}(note = "{{deprecationMessage}}"){{/if}}]
|
|
26
|
+
{{/if}}
|
|
27
|
+
pub async fn {{snakeCase name}}(
|
|
28
|
+
&self,
|
|
29
|
+
{{#each pathParams}}
|
|
30
|
+
{{snakeCase name}}: {{{rustParamType type}}},
|
|
31
|
+
{{/each}}
|
|
32
|
+
{{#if requestBody}}
|
|
33
|
+
body: &{{{requestBody.rustType}}},
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{#if queryType}}
|
|
36
|
+
query: Option<&{{{rustQueryType queryType}}}>,
|
|
37
|
+
{{/if}}
|
|
38
|
+
) -> Result<{{#if isEventStream}}Vec<{{#if eventSchema}}{{{rustType eventSchema}}}{{else}}serde_json::Value{{/if}}>{{else}}{{{rustResponseType}}}{{/if}}, SdkError> {
|
|
39
|
+
let path = format!("{{path}}"{{#each pathParams}}, {{snakeCase name}} = {{#if (isRustCopyType type)}}{{snakeCase name}}{{else}}{{snakeCase name}}{{/if}}{{/each}});
|
|
40
|
+
{{#if (hasQueryParams this)}}
|
|
41
|
+
let mut query_params: Vec<(String, String)> = Vec::new();
|
|
42
|
+
if let Some(q) = query {
|
|
43
|
+
{{#each _queryParams}}
|
|
44
|
+
if let Some(ref v) = q.{{snakeCase name}} {
|
|
45
|
+
query_params.push(("{{originalName}}".to_string(), v.to_string()));
|
|
46
|
+
}
|
|
47
|
+
{{/each}}
|
|
48
|
+
}
|
|
49
|
+
let query_slice: Option<&[(String, String)]> = if query_params.is_empty() { None } else { Some(&query_params) };
|
|
50
|
+
{{else if queryType}}
|
|
51
|
+
// Query params from typed query object
|
|
52
|
+
let query_slice: Option<&[(String, String)]> = None; // TODO: serialize query type
|
|
53
|
+
{{else}}
|
|
54
|
+
let query_slice: Option<&[(String, String)]> = None;
|
|
55
|
+
{{/if}}
|
|
56
|
+
|
|
57
|
+
{{#if (or (hasRequestBody this) (needsRequestHeaders this))}}
|
|
58
|
+
let mut headers: Vec<(String, String)> = Vec::new();
|
|
59
|
+
{{#if (hasRequestBody this)}}
|
|
60
|
+
{{#if (and (neq requestBody.contentType "application/json") (neq requestBody.contentType "multipart/form-data"))}}
|
|
61
|
+
headers.push(("Content-Type".to_string(), "{{requestBody.contentType}}".to_string()));
|
|
62
|
+
{{/if}}
|
|
63
|
+
{{/if}}
|
|
64
|
+
{{#if (and headerParams.length headerType)}}
|
|
65
|
+
// Header params injected from header options
|
|
66
|
+
{{/if}}
|
|
67
|
+
{{#each headerParams}}
|
|
68
|
+
if let Some(ref h) = header_options.{{snakeCase name}} {
|
|
69
|
+
headers.push(("{{originalName}}".to_string(), h.to_string()));
|
|
70
|
+
}
|
|
71
|
+
{{/each}}
|
|
72
|
+
{{#if cookieParams}}
|
|
73
|
+
// Cookie params
|
|
74
|
+
{{/if}}
|
|
75
|
+
let headers_slice: Option<&[(String, String)]> = if headers.is_empty() { None } else { Some(&headers) };
|
|
76
|
+
{{else}}
|
|
77
|
+
let headers_slice: Option<&[(String, String)]> = None;
|
|
78
|
+
{{/if}}
|
|
79
|
+
|
|
80
|
+
self.transport.execute::<{{#if isEventStream}}Vec<{{#if eventSchema}}{{{rustType eventSchema}}}{{else}}serde_json::Value{{/if}}>{{else}}{{{rustResponseType}}}{{/if}}>(
|
|
81
|
+
Method::{{httpMethod}},
|
|
82
|
+
&path,
|
|
83
|
+
query_slice,
|
|
84
|
+
{{#if requestBody}}
|
|
85
|
+
Some(body),
|
|
86
|
+
{{else}}
|
|
87
|
+
None::<&serde_json::Value>,
|
|
88
|
+
{{/if}}
|
|
89
|
+
headers_slice,
|
|
90
|
+
).await
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
{{/each}}
|
|
94
|
+
}
|