@apollo-deploy/tesseract 0.9.0 → 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 (112) 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.d.ts +3 -3
  45. package/dist/pipeline/intake.d.ts.map +1 -1
  46. package/dist/pipeline/intake.js +194 -121
  47. package/dist/pipeline/intake.js.map +1 -1
  48. package/dist/types/ir.d.ts +1 -1
  49. package/dist/types/ir.d.ts.map +1 -1
  50. package/dist/types/sdk-module.d.ts +22 -2
  51. package/dist/types/sdk-module.d.ts.map +1 -1
  52. package/dist/types/sdk-module.js +1 -1
  53. package/package.json +1 -1
  54. package/templates/csharp/client.hbs +95 -0
  55. package/templates/csharp/csproj.hbs +18 -0
  56. package/templates/csharp/domain.hbs +183 -0
  57. package/templates/csharp/errors.hbs +45 -0
  58. package/templates/csharp/readme.hbs +76 -0
  59. package/templates/csharp/transport.hbs +291 -0
  60. package/templates/csharp/types.hbs +104 -0
  61. package/templates/go/client.hbs +158 -0
  62. package/templates/go/domain.hbs +89 -0
  63. package/templates/go/errors.hbs +38 -0
  64. package/templates/go/go-mod.hbs +6 -0
  65. package/templates/go/readme.hbs +73 -0
  66. package/templates/go/transport.hbs +379 -0
  67. package/templates/go/types.hbs +80 -0
  68. package/templates/go/utils.hbs +45 -0
  69. package/templates/kotlin/build-gradle.hbs +41 -0
  70. package/templates/kotlin/client.hbs +57 -0
  71. package/templates/kotlin/domain.hbs +73 -0
  72. package/templates/kotlin/errors.hbs +42 -0
  73. package/templates/kotlin/gradle-properties.hbs +2 -0
  74. package/templates/kotlin/readme.hbs +105 -0
  75. package/templates/kotlin/settings-gradle.hbs +1 -0
  76. package/templates/kotlin/transport.hbs +246 -0
  77. package/templates/kotlin/types.hbs +100 -0
  78. package/templates/php/client.hbs +74 -0
  79. package/templates/php/composer.hbs +19 -0
  80. package/templates/php/domain.hbs +173 -0
  81. package/templates/php/errors.hbs +131 -0
  82. package/templates/php/readme.hbs +89 -0
  83. package/templates/php/transport.hbs +285 -0
  84. package/templates/php/types.hbs +95 -0
  85. package/templates/python/client.hbs +102 -0
  86. package/templates/python/domain.hbs +139 -0
  87. package/templates/python/index.hbs +20 -0
  88. package/templates/python/pyproject.hbs +26 -0
  89. package/templates/python/readme.hbs +64 -0
  90. package/templates/python/resources-init.hbs +11 -0
  91. package/templates/python/transport.hbs +191 -0
  92. package/templates/python/types-errors.hbs +75 -0
  93. package/templates/python/types.hbs +79 -0
  94. package/templates/ruby/Gemfile.hbs +5 -0
  95. package/templates/ruby/client.hbs +86 -0
  96. package/templates/ruby/domain.hbs +110 -0
  97. package/templates/ruby/errors.hbs +150 -0
  98. package/templates/ruby/gemspec.hbs +30 -0
  99. package/templates/ruby/index.hbs +54 -0
  100. package/templates/ruby/readme.hbs +113 -0
  101. package/templates/ruby/transport.hbs +177 -0
  102. package/templates/ruby/types.hbs +126 -0
  103. package/templates/ruby/version.hbs +5 -0
  104. package/templates/rust/api-mod.hbs +3 -0
  105. package/templates/rust/cargo.hbs +22 -0
  106. package/templates/rust/client.hbs +114 -0
  107. package/templates/rust/domain.hbs +94 -0
  108. package/templates/rust/error.hbs +74 -0
  109. package/templates/rust/lib.hbs +85 -0
  110. package/templates/rust/readme.hbs +77 -0
  111. package/templates/rust/transport.hbs +287 -0
  112. package/templates/rust/types.hbs +142 -0
@@ -0,0 +1,74 @@
1
+ use thiserror::Error;
2
+
3
+ /// Primary error type for the {{crateName}} SDK.
4
+ ///
5
+ /// All API methods return `Result<T, SdkError>`.
6
+ #[derive(Error, Debug)]
7
+ pub enum SdkError {
8
+ /// The API returned an error response (4xx/5xx).
9
+ #[error("API error (status {status}): {message}")]
10
+ ApiError {
11
+ /// HTTP status code.
12
+ status: u16,
13
+ /// Machine-readable error code (if available).
14
+ code: Option<String>,
15
+ /// Human-readable error message.
16
+ message: String,
17
+ /// Raw response body for debugging.
18
+ body: String,
19
+ },
20
+
21
+ /// A network-level error occurred (DNS, TLS, connection refused, timeout).
22
+ #[error("network error: {0}")]
23
+ NetworkError(String),
24
+
25
+ /// Failed to deserialize the response body.
26
+ #[error("deserialization error: {0}")]
27
+ DeserializationError(String),
28
+
29
+ /// The request parameters failed client-side validation.
30
+ #[error("validation error: {0}")]
31
+ ValidationError(String),
32
+ }
33
+
34
+ impl SdkError {
35
+ /// Returns the HTTP status code, if this is an `ApiError`.
36
+ pub fn status(&self) -> Option<u16> {
37
+ match self {
38
+ Self::ApiError { status, .. } => Some(*status),
39
+ _ => None,
40
+ }
41
+ }
42
+
43
+ /// Returns `true` if this error was caused by the server (5xx).
44
+ pub fn is_server_error(&self) -> bool {
45
+ matches!(self, Self::ApiError { status, .. } if *status >= 500)
46
+ }
47
+
48
+ /// Returns `true` if this error was caused by the client (4xx).
49
+ pub fn is_client_error(&self) -> bool {
50
+ matches!(self, Self::ApiError { status, .. } if *status >= 400 && *status < 500)
51
+ }
52
+
53
+ /// Returns `true` if this is a network error (should be retried safely).
54
+ pub fn is_retryable(&self) -> bool {
55
+ matches!(
56
+ self,
57
+ Self::NetworkError(_)
58
+ | Self::ApiError {
59
+ status: 429 | 502 | 503 | 504,
60
+ ..
61
+ }
62
+ )
63
+ }
64
+
65
+ /// Returns the error message as a string.
66
+ pub fn message(&self) -> &str {
67
+ match self {
68
+ Self::ApiError { message, .. } => message.as_str(),
69
+ Self::NetworkError(msg) => msg.as_str(),
70
+ Self::DeserializationError(msg) => msg.as_str(),
71
+ Self::ValidationError(msg) => msg.as_str(),
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,85 @@
1
+ //! {{title}}
2
+ //!
3
+ //! {{#if description}}{{description}}{{else}}Auto-generated Rust SDK for the {{title}} API.{{/if}}
4
+ //!
5
+ //! # Quick Start
6
+ //!
7
+ //! Add to your `Cargo.toml`:
8
+ //!
9
+ //! ```toml
10
+ //! [dependencies]
11
+ //! {{crateName}} = "{{#if packageVersion}}{{packageVersion}}{{else}}0.1.0{{/if}}"
12
+ //! ```
13
+ //!
14
+ //! Then use it:
15
+ //!
16
+ //! ```rust,no_run
17
+ //! use {{crateName}}::Client;
18
+ //! use {{crateName}}::ClientConfig;
19
+ //!
20
+ //! #[tokio::main]
21
+ //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
22
+ //! let mut config = ClientConfig::default();
23
+ {{#each securitySchemes}}
24
+ {{#if (eq type "apiKey")}}
25
+ //! config.{{snakeCase configKey}} = Some("your-api-key".to_string());
26
+ {{/if}}
27
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
28
+ //! config.{{snakeCase configKey}} = Some("your-bearer-token".to_string());
29
+ {{/if}}
30
+ {{/each}}
31
+ //!
32
+ //! let client = Client::new(config);
33
+ //!
34
+ //! // Use domain clients
35
+ //! // let result = client.{{#each groups}}{{#if @first}}{{snakeCase name}}{{/if}}{{/each}}.some_method().await?;
36
+ //!
37
+ //! Ok(())
38
+ //! }
39
+ //! ```
40
+ //!
41
+ //! ## API Reference
42
+ //!
43
+ {{#each groups}}
44
+ //! ### `{{interfaceName}}`
45
+ //! {{#if description}}{{description}}{{/if}}
46
+ //!
47
+ {{#each operations}}
48
+ //! - `{{snakeCase name}}()` — {{#if summary}}{{summary}}{{else}}No summary{{/if}}
49
+ {{/each}}
50
+ //!
51
+ {{/each}}
52
+ //!
53
+ //! ## Error Handling
54
+ //!
55
+ //! All API methods return `Result<T, SdkError>`. The `SdkError` enum covers:
56
+ //!
57
+ //! - **ApiError** — server returned an error response (4xx/5xx)
58
+ //! - **NetworkError** — request failed due to network issues
59
+ //! - **DeserializationError** — response body could not be parsed
60
+ //! - **ValidationError** — invalid request parameters
61
+ //!
62
+ //! ```rust
63
+ //! match client.some_domain.some_method().await {
64
+ //! Ok(response) => println!("Success: {:?}", response),
65
+ //! Err(SdkError::ApiError { status, .. }) => eprintln!("API error ({}): {}", status, error),
66
+ //! Err(SdkError::NetworkError(e)) => eprintln!("Network error: {}", e),
67
+ //! Err(e) => eprintln!("Other error: {}", e),
68
+ //! }
69
+ //! ```
70
+
71
+ pub mod client;
72
+ pub mod transport;
73
+ pub mod error;
74
+ pub mod types;
75
+ pub mod api;
76
+
77
+ pub use client::*;
78
+ pub use error::SdkError;
79
+
80
+ /// Re-export API resource modules
81
+ pub mod resources {
82
+ {{#each groups}}
83
+ pub use super::api::{{snakeCase fileName}}::*;
84
+ {{/each}}
85
+ }
@@ -0,0 +1,77 @@
1
+ # {{title}}
2
+
3
+ {{#if description}}{{description}}{{/if}}
4
+
5
+ {{#unless description}}An auto-generated Rust SDK for the {{title}} API.{{/unless}}
6
+
7
+ ## Installation
8
+
9
+ Add this to your `Cargo.toml`:
10
+
11
+ ```toml
12
+ [dependencies]
13
+ {{crateName}} = "{{#if packageVersion}}{{packageVersion}}{{else}}0.1.0{{/if}}"
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```rust
19
+ use {{crateName}}::Client;
20
+ use {{crateName}}::ClientConfig;
21
+
22
+ #[tokio::main]
23
+ async fn main() -> Result<(), Box<dyn std::error::Error>> {
24
+ let mut config = ClientConfig::default();
25
+ {{#each securitySchemes}}
26
+ {{#if (eq type "apiKey")}}
27
+ config.{{snakeCase configKey}} = Some("your-api-key".to_string());
28
+ {{/if}}
29
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
30
+ config.{{snakeCase configKey}} = Some("your-bearer-token".to_string());
31
+ {{/if}}
32
+ {{/each}}
33
+
34
+ let client = Client::new(config);
35
+
36
+ // Use domain clients
37
+ {{#each groups}}
38
+ {{#if @first}}
39
+ // let result = client.{{snakeCase name}}.some_operation().await?;
40
+ {{/if}}
41
+ {{/each}}
42
+
43
+ Ok(())
44
+ }
45
+ ```
46
+
47
+ ## API Reference
48
+
49
+ {{#each groups}}
50
+ ### {{interfaceName}}
51
+
52
+ {{#if description}}
53
+ {{description}}
54
+ {{/if}}
55
+
56
+ | Method | HTTP | Description |
57
+ |--------|------|-------------|
58
+ {{#each operations}}
59
+ | `{{snakeCase name}}()` | `{{httpMethod}}` | {{#if summary}}{{summary}}{{else}}—{{/if}} |
60
+ {{/each}}
61
+
62
+ {{/each}}
63
+
64
+ ## Error Handling
65
+
66
+ All API methods return `Result<T, SdkError>`. The `SdkError` enum has these variants:
67
+
68
+ | Variant | Description |
69
+ |---------|-------------|
70
+ | `ApiError` | Server returned an error (4xx/5xx) |
71
+ | `NetworkError` | Request failed due to connectivity issues |
72
+ | `DeserializationError` | Response body could not be parsed |
73
+ | `ValidationError` | Invalid request parameters |
74
+
75
+ ## License
76
+
77
+ MIT
@@ -0,0 +1,287 @@
1
+ use crate::error::SdkError;
2
+ use reqwest::{Client as ReqwestClient, Method, RequestBuilder, Response, StatusCode};
3
+ use serde::de::DeserializeOwned;
4
+ use serde::Serialize;
5
+ use std::sync::Arc;
6
+ use std::time::Duration;
7
+ use url::Url;
8
+
9
+ /// Internal transport configuration.
10
+ #[derive(Debug, Clone)]
11
+ pub struct TransportConfig {
12
+ pub base_url: String,
13
+ {{#each securitySchemes}}
14
+ pub {{snakeCase configKey}}: Option<String>,
15
+ {{/each}}
16
+ pub timeout: Duration,
17
+ pub max_retries: u32,
18
+ }
19
+
20
+ /// HTTP transport layer for the SDK.
21
+ ///
22
+ /// Handles request building, authentication, retries, and error mapping.
23
+ /// Cloning a `Transport` produces a shallow clone that shares the underlying
24
+ /// reqwest client.
25
+ #[derive(Clone)]
26
+ pub struct Transport {
27
+ client: ReqwestClient,
28
+ config: Arc<TransportConfig>,
29
+ }
30
+
31
+ impl Transport {
32
+ /// Create a new transport with the given configuration.
33
+ pub fn new(config: TransportConfig) -> Self {
34
+ let client = ReqwestClient::builder()
35
+ .timeout(config.timeout)
36
+ .build()
37
+ .expect("failed to build reqwest client");
38
+
39
+ Self {
40
+ client,
41
+ config: Arc::new(config),
42
+ }
43
+ }
44
+
45
+ /// Execute a request, deserializing the response body as JSON.
46
+ pub async fn execute<T: DeserializeOwned>(
47
+ &self,
48
+ method: Method,
49
+ path: &str,
50
+ query: Option<&[(String, String)]>,
51
+ body: Option<&(impl Serialize + ?Sized)>,
52
+ headers: Option<&[(String, String)]>,
53
+ ) -> Result<T, SdkError> {
54
+ let url = self.build_url(path, query)?;
55
+
56
+ let mut req = self.client.request(method, url);
57
+
58
+ req = self.apply_auth(req);
59
+ req = self.apply_headers(req, headers);
60
+
61
+ if let Some(b) = body {
62
+ req = req.json(b);
63
+ }
64
+
65
+ let response = self.execute_with_retry(req).await?;
66
+
67
+ self.handle_response(response).await
68
+ }
69
+
70
+ /// Execute a request that returns the raw response text.
71
+ pub async fn execute_raw(
72
+ &self,
73
+ method: Method,
74
+ path: &str,
75
+ query: Option<&[(String, String)]>,
76
+ body: Option<&(impl Serialize + ?Sized)>,
77
+ headers: Option<&[(String, String)]>,
78
+ ) -> Result<String, SdkError> {
79
+ let url = self.build_url(path, query)?;
80
+
81
+ let mut req = self.client.request(method, url);
82
+
83
+ req = self.apply_auth(req);
84
+ req = self.apply_headers(req, headers);
85
+
86
+ if let Some(b) = body {
87
+ req = req.json(b);
88
+ }
89
+
90
+ let response = self.execute_with_retry(req).await?;
91
+
92
+ let status = response.status();
93
+ let text = response.text().await.map_err(|e| {
94
+ SdkError::DeserializationError(format!("failed to read response body: {}", e))
95
+ })?;
96
+
97
+ if status.is_success() {
98
+ Ok(text)
99
+ } else {
100
+ Err(self.build_api_error(status, &text))
101
+ }
102
+ }
103
+
104
+ // ── private helpers ─────────────────────────────────────────────────────
105
+
106
+ fn build_url(
107
+ &self,
108
+ path: &str,
109
+ query: Option<&[(String, String)]>,
110
+ ) -> Result<Url, SdkError> {
111
+ let base = Url::parse(&self.config.base_url).map_err(|e| {
112
+ SdkError::ValidationError(format!("invalid base URL: {}", e))
113
+ })?;
114
+
115
+ let joined = base.join(path).map_err(|e| {
116
+ SdkError::ValidationError(format!("invalid path '{}': {}", path, e))
117
+ })?;
118
+
119
+ if let Some(params) = query {
120
+ let mut url = joined;
121
+ {
122
+ let mut qp = url.query_pairs_mut();
123
+ for (k, v) in params {
124
+ qp.append_pair(k, v);
125
+ }
126
+ }
127
+ Ok(url)
128
+ } else {
129
+ Ok(joined)
130
+ }
131
+ }
132
+
133
+ fn apply_auth(&self, mut req: RequestBuilder) -> RequestBuilder {
134
+ {{#each securitySchemes}}
135
+ {{#if (eq type "apiKey")}}
136
+ {{#if (eq in "header")}}
137
+ if let Some(ref key) = self.config.{{snakeCase configKey}} {
138
+ req = req.header("{{paramName}}", key);
139
+ }
140
+ {{/if}}
141
+ {{#if (eq in "query")}}
142
+ // API key via query parameter handled during URL construction
143
+ {{/if}}
144
+ {{/if}}
145
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
146
+ if let Some(ref token) = self.config.{{snakeCase configKey}} {
147
+ req = req.header("Authorization", format!("Bearer {}", token));
148
+ }
149
+ {{/if}}
150
+ {{#if (eq type "oauth2")}}
151
+ if let Some(ref token) = self.config.{{snakeCase configKey}} {
152
+ req = req.header("Authorization", format!("Bearer {}", token));
153
+ }
154
+ {{/if}}
155
+ {{#if (eq type "openIdConnect")}}
156
+ if let Some(ref token) = self.config.{{snakeCase configKey}} {
157
+ req = req.header("Authorization", format!("Bearer {}", token));
158
+ }
159
+ {{/if}}
160
+ {{/each}}
161
+ req
162
+ }
163
+
164
+ fn apply_headers(
165
+ &self,
166
+ mut req: RequestBuilder,
167
+ headers: Option<&[(String, String)]>,
168
+ ) -> RequestBuilder {
169
+ if let Some(hdrs) = headers {
170
+ for (key, value) in hdrs {
171
+ req = req.header(key.as_str(), value.as_str());
172
+ }
173
+ }
174
+ req
175
+ }
176
+
177
+ async fn execute_with_retry(
178
+ &self,
179
+ req: RequestBuilder,
180
+ ) -> Result<Response, SdkError> {
181
+ let max_retries = self.config.max_retries;
182
+ let mut attempt = 0u32;
183
+
184
+ loop {
185
+ let request = req
186
+ .try_clone()
187
+ .ok_or_else(|| SdkError::NetworkError("failed to clone request for retry".to_string()))?;
188
+
189
+ match request.send().await {
190
+ Ok(response) => {
191
+ let status = response.status();
192
+
193
+ // Retry on server errors (5xx) and 429 Too Many Requests
194
+ if status.is_server_error() || status == StatusCode::TOO_MANY_REQUESTS {
195
+ if attempt < max_retries {
196
+ attempt += 1;
197
+ let backoff = Duration::from_millis(
198
+ 200u64 * 2u64.pow(attempt),
199
+ );
200
+ tokio::time::sleep(backoff).await;
201
+ continue;
202
+ }
203
+ }
204
+ return Ok(response);
205
+ }
206
+ Err(e) => {
207
+ if attempt < max_retries && e.is_connect() || e.is_timeout() {
208
+ attempt += 1;
209
+ let backoff = Duration::from_millis(
210
+ 200u64 * 2u64.pow(attempt),
211
+ );
212
+ tokio::time::sleep(backoff).await;
213
+ continue;
214
+ }
215
+ return Err(SdkError::NetworkError(e.to_string()));
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ async fn handle_response<T: DeserializeOwned>(
222
+ &self,
223
+ response: Response,
224
+ ) -> Result<T, SdkError> {
225
+ let status = response.status();
226
+ let text = response.text().await.map_err(|e| {
227
+ SdkError::DeserializationError(format!("failed to read response body: {}", e))
228
+ })?;
229
+
230
+ if status.is_success() {
231
+ serde_json::from_str(&text).map_err(|e| {
232
+ SdkError::DeserializationError(format!(
233
+ "failed to deserialize response (status {}): {}",
234
+ status, e
235
+ ))
236
+ })
237
+ } else {
238
+ Err(self.build_api_error(status, &text))
239
+ }
240
+ }
241
+
242
+ fn build_api_error(&self, status: StatusCode, body: &str) -> SdkError {
243
+ // Try to extract structured error info from the JSON body
244
+ let code = serde_json::from_str::<serde_json::Value>(body)
245
+ .ok()
246
+ .and_then(|v| {
247
+ v.get("code")
248
+ .or_else(|| v.get("error"))
249
+ .and_then(|c| c.as_str().map(String::from))
250
+ .or_else(|| {
251
+ v.get("error")
252
+ .and_then(|e| e.get("code"))
253
+ .and_then(|c| c.as_str().map(String::from))
254
+ })
255
+ });
256
+
257
+ let message = serde_json::from_str::<serde_json::Value>(body)
258
+ .ok()
259
+ .and_then(|v| {
260
+ v.get("message")
261
+ .or_else(|| v.get("error"))
262
+ .and_then(|m| {
263
+ if m.is_string() {
264
+ m.as_str().map(String::from)
265
+ } else {
266
+ m.get("message").and_then(|s| s.as_str().map(String::from))
267
+ }
268
+ })
269
+ })
270
+ .unwrap_or_else(|| body.to_string());
271
+
272
+ SdkError::ApiError {
273
+ status: status.as_u16(),
274
+ code,
275
+ message,
276
+ body: body.to_string(),
277
+ }
278
+ }
279
+ }
280
+
281
+ impl std::fmt::Debug for Transport {
282
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283
+ f.debug_struct("Transport")
284
+ .field("config", &self.config)
285
+ .finish()
286
+ }
287
+ }
@@ -0,0 +1,142 @@
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ {{#each schemas}}
4
+ {{#if (and (not ownership) (not isEnum) (not isTypeAlias) (not isUnionType) (not isIntersectionType))}}
5
+ {{#if (or externalImport (eq ownership.kind "external"))}}
6
+ // {{name}} is imported from an external schema package.
7
+ {{else}}
8
+ {{#if description}}
9
+ /// {{description}}
10
+ {{/if}}
11
+ #[derive(Debug, Clone, Serialize, Deserialize)]
12
+ {{#if additionalPropertiesType}}
13
+ {{! Additional properties means we need to use a flattened map }}
14
+ #[serde(rename_all = "camelCase")]
15
+ pub struct {{name}} {
16
+ {{#each properties}}
17
+ {{#if description}}
18
+ /// {{description}}
19
+ {{/if}}
20
+ {{#if deprecated}}
21
+ #[deprecated]
22
+ {{/if}}
23
+ {{#if (and nullable (not required))}}
24
+ #[serde(skip_serializing_if = "Option::is_none")]
25
+ pub {{snakeCase name}}: Option<{{{rustType type}}}>,
26
+ {{else if nullable}}
27
+ #[serde(skip_serializing_if = "Option::is_none")]
28
+ pub {{snakeCase name}}: Option<{{{rustType type}}}>,
29
+ {{else if required}}
30
+ pub {{snakeCase name}}: {{{rustType type}}},
31
+ {{else}}
32
+ #[serde(skip_serializing_if = "Option::is_none")]
33
+ pub {{snakeCase name}}: Option<{{{rustType type}}}>,
34
+ {{/if}}
35
+ {{/each}}
36
+ }
37
+ {{else}}
38
+ #[serde(rename_all = "camelCase")]
39
+ pub struct {{name}} {
40
+ {{#each properties}}
41
+ {{#if description}}
42
+ /// {{description}}
43
+ {{/if}}
44
+ {{#if deprecated}}
45
+ #[deprecated]
46
+ {{/if}}
47
+ {{#if (and nullable (not required))}}
48
+ #[serde(skip_serializing_if = "Option::is_none")]
49
+ pub {{snakeCase name}}: Option<{{{rustType type}}}>,
50
+ {{else if nullable}}
51
+ #[serde(skip_serializing_if = "Option::is_none")]
52
+ pub {{snakeCase name}}: Option<{{{rustType type}}}>,
53
+ {{else if required}}
54
+ pub {{snakeCase name}}: {{{rustType type}}},
55
+ {{else}}
56
+ #[serde(skip_serializing_if = "Option::is_none")]
57
+ pub {{snakeCase name}}: Option<{{{rustType type}}}>,
58
+ {{/if}}
59
+ {{/each}}
60
+ }
61
+ {{/if}}
62
+ {{/if}}
63
+ {{/if}}
64
+
65
+ {{#if isEnum}}
66
+ {{#if description}}
67
+ /// {{description}}
68
+ {{/if}}
69
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70
+ {{#if enumValues}}
71
+ #[serde(rename_all = "camelCase")]
72
+ {{/if}}
73
+ pub enum {{name}} {
74
+ {{#each enumValues}}
75
+ #[serde(rename = "{{this}}")]
76
+ {{pascalCase this}},
77
+ {{/each}}
78
+ }
79
+ {{/if}}
80
+
81
+ {{#if isTypeAlias}}
82
+ {{#if description}}
83
+ /// {{description}}
84
+ {{/if}}
85
+ {{#if properties.length}}
86
+ {{! Type alias with shape — emit as struct }}
87
+ #[derive(Debug, Clone, Serialize, Deserialize)]
88
+ #[serde(rename_all = "camelCase")]
89
+ pub struct {{name}} {
90
+ {{#each properties}}
91
+ {{#if description}}
92
+ /// {{description}}
93
+ {{/if}}
94
+ {{#if required}}
95
+ pub {{snakeCase name}}: {{{rustType type}}},
96
+ {{else}}
97
+ #[serde(skip_serializing_if = "Option::is_none")]
98
+ pub {{snakeCase name}}: Option<{{{rustType type}}}>,
99
+ {{/if}}
100
+ {{/each}}
101
+ }
102
+ {{else}}
103
+ {{! Simple type alias — skip, referenced by name }}
104
+ // {{name}} is a type alias — referenced inline.
105
+ {{/if}}
106
+ {{/if}}
107
+
108
+ {{#if isUnionType}}
109
+ {{#if description}}
110
+ /// {{description}}
111
+ {{/if}}
112
+ {{#if unionMembers.length}}
113
+ {{! Union type with named members — emit as enum }}
114
+ #[derive(Debug, Clone, Serialize, Deserialize)]
115
+ #[serde(untagged)]
116
+ pub enum {{name}} {
117
+ {{#each unionMembers}}
118
+ {{pascalCase this}}({{{rustType this}}}),
119
+ {{/each}}
120
+ }
121
+ {{/if}}
122
+ {{/if}}
123
+
124
+ {{#if isIntersectionType}}
125
+ {{#if description}}
126
+ /// {{description}}
127
+ {{/if}}
128
+ {{#if intersectionMembers.length}}
129
+ {{! Intersection type — flatten into a struct combining all members }}
130
+ #[derive(Debug, Clone, Serialize, Deserialize)]
131
+ #[serde(rename_all = "camelCase")]
132
+ pub struct {{name}} {
133
+ {{#each intersectionMembers}}
134
+ // Fields from {{this}} are expected to be inlined by the code generator.
135
+ {{/each}}
136
+ // Intersection types require manual field specification.
137
+ // TODO: inline fields from {{join intersectionMembers ", "}}
138
+ }
139
+ {{/if}}
140
+ {{/if}}
141
+
142
+ {{/each}}