@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.
Files changed (110) hide show
  1. package/dist/adapters/csharp/index.d.ts +12 -0
  2. package/dist/adapters/csharp/index.d.ts.map +1 -0
  3. package/dist/adapters/csharp/index.js +99 -0
  4. package/dist/adapters/csharp/index.js.map +1 -0
  5. package/dist/adapters/go/index.d.ts +12 -0
  6. package/dist/adapters/go/index.d.ts.map +1 -0
  7. package/dist/adapters/go/index.js +135 -0
  8. package/dist/adapters/go/index.js.map +1 -0
  9. package/dist/adapters/kotlin/index.d.ts +13 -0
  10. package/dist/adapters/kotlin/index.d.ts.map +1 -0
  11. package/dist/adapters/kotlin/index.js +186 -0
  12. package/dist/adapters/kotlin/index.js.map +1 -0
  13. package/dist/adapters/php/index.d.ts +20 -0
  14. package/dist/adapters/php/index.d.ts.map +1 -0
  15. package/dist/adapters/php/index.js +123 -0
  16. package/dist/adapters/php/index.js.map +1 -0
  17. package/dist/adapters/python/index.d.ts +12 -0
  18. package/dist/adapters/python/index.d.ts.map +1 -0
  19. package/dist/adapters/python/index.js +145 -0
  20. package/dist/adapters/python/index.js.map +1 -0
  21. package/dist/adapters/ruby/index.d.ts +12 -0
  22. package/dist/adapters/ruby/index.d.ts.map +1 -0
  23. package/dist/adapters/ruby/index.js +86 -0
  24. package/dist/adapters/ruby/index.js.map +1 -0
  25. package/dist/adapters/rust/index.d.ts +13 -0
  26. package/dist/adapters/rust/index.d.ts.map +1 -0
  27. package/dist/adapters/rust/index.js +213 -0
  28. package/dist/adapters/rust/index.js.map +1 -0
  29. package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
  30. package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
  31. package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
  32. package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
  33. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
  34. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
  35. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +2 -2
  36. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
  37. package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
  38. package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
  39. package/dist/cli.js +2 -1
  40. package/dist/cli.js.map +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +14 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/pipeline/intake.js +23 -16
  45. package/dist/pipeline/intake.js.map +1 -1
  46. package/dist/types/ir.d.ts +1 -1
  47. package/dist/types/ir.d.ts.map +1 -1
  48. package/dist/types/sdk-module.d.ts +22 -2
  49. package/dist/types/sdk-module.d.ts.map +1 -1
  50. package/dist/types/sdk-module.js +1 -1
  51. package/package.json +1 -1
  52. package/templates/csharp/client.hbs +95 -0
  53. package/templates/csharp/csproj.hbs +18 -0
  54. package/templates/csharp/domain.hbs +183 -0
  55. package/templates/csharp/errors.hbs +45 -0
  56. package/templates/csharp/readme.hbs +76 -0
  57. package/templates/csharp/transport.hbs +291 -0
  58. package/templates/csharp/types.hbs +104 -0
  59. package/templates/go/client.hbs +158 -0
  60. package/templates/go/domain.hbs +89 -0
  61. package/templates/go/errors.hbs +38 -0
  62. package/templates/go/go-mod.hbs +6 -0
  63. package/templates/go/readme.hbs +73 -0
  64. package/templates/go/transport.hbs +379 -0
  65. package/templates/go/types.hbs +80 -0
  66. package/templates/go/utils.hbs +45 -0
  67. package/templates/kotlin/build-gradle.hbs +41 -0
  68. package/templates/kotlin/client.hbs +57 -0
  69. package/templates/kotlin/domain.hbs +73 -0
  70. package/templates/kotlin/errors.hbs +42 -0
  71. package/templates/kotlin/gradle-properties.hbs +2 -0
  72. package/templates/kotlin/readme.hbs +105 -0
  73. package/templates/kotlin/settings-gradle.hbs +1 -0
  74. package/templates/kotlin/transport.hbs +246 -0
  75. package/templates/kotlin/types.hbs +100 -0
  76. package/templates/php/client.hbs +74 -0
  77. package/templates/php/composer.hbs +19 -0
  78. package/templates/php/domain.hbs +173 -0
  79. package/templates/php/errors.hbs +131 -0
  80. package/templates/php/readme.hbs +89 -0
  81. package/templates/php/transport.hbs +285 -0
  82. package/templates/php/types.hbs +95 -0
  83. package/templates/python/client.hbs +102 -0
  84. package/templates/python/domain.hbs +139 -0
  85. package/templates/python/index.hbs +20 -0
  86. package/templates/python/pyproject.hbs +26 -0
  87. package/templates/python/readme.hbs +64 -0
  88. package/templates/python/resources-init.hbs +11 -0
  89. package/templates/python/transport.hbs +191 -0
  90. package/templates/python/types-errors.hbs +75 -0
  91. package/templates/python/types.hbs +79 -0
  92. package/templates/ruby/Gemfile.hbs +5 -0
  93. package/templates/ruby/client.hbs +86 -0
  94. package/templates/ruby/domain.hbs +110 -0
  95. package/templates/ruby/errors.hbs +150 -0
  96. package/templates/ruby/gemspec.hbs +30 -0
  97. package/templates/ruby/index.hbs +54 -0
  98. package/templates/ruby/readme.hbs +113 -0
  99. package/templates/ruby/transport.hbs +177 -0
  100. package/templates/ruby/types.hbs +126 -0
  101. package/templates/ruby/version.hbs +5 -0
  102. package/templates/rust/api-mod.hbs +3 -0
  103. package/templates/rust/cargo.hbs +22 -0
  104. package/templates/rust/client.hbs +114 -0
  105. package/templates/rust/domain.hbs +94 -0
  106. package/templates/rust/error.hbs +74 -0
  107. package/templates/rust/lib.hbs +85 -0
  108. package/templates/rust/readme.hbs +77 -0
  109. package/templates/rust/transport.hbs +287 -0
  110. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module {{pascalCase snakeModule}}
4
+ VERSION = "{{#if meta.packageVersion}}{{meta.packageVersion}}{{else}}{{meta.version}}{{/if}}"
5
+ end
@@ -0,0 +1,3 @@
1
+ {{#each groups}}
2
+ pub mod {{snakeCase fileName}};
3
+ {{/each}}
@@ -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
+ }