@apollo-deploy/tesseract 1.8.0 → 2.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/README.md +218 -110
- package/dist/adapters/csharp/index.d.ts +5 -8
- package/dist/adapters/csharp/index.d.ts.map +1 -1
- package/dist/adapters/csharp/index.js +314 -74
- package/dist/adapters/csharp/index.js.map +1 -1
- package/dist/adapters/documentation.d.ts +17 -0
- package/dist/adapters/documentation.d.ts.map +1 -0
- package/dist/adapters/documentation.js +268 -0
- package/dist/adapters/documentation.js.map +1 -0
- package/dist/adapters/go/index.d.ts +5 -8
- package/dist/adapters/go/index.d.ts.map +1 -1
- package/dist/adapters/go/index.js +197 -101
- package/dist/adapters/go/index.js.map +1 -1
- package/dist/adapters/kotlin/index.d.ts +5 -9
- package/dist/adapters/kotlin/index.d.ts.map +1 -1
- package/dist/adapters/kotlin/index.js +280 -212
- package/dist/adapters/kotlin/index.js.map +1 -1
- package/dist/adapters/php/index.d.ts +5 -15
- package/dist/adapters/php/index.d.ts.map +1 -1
- package/dist/adapters/php/index.js +362 -90
- package/dist/adapters/php/index.js.map +1 -1
- package/dist/adapters/python/index.d.ts +5 -8
- package/dist/adapters/python/index.d.ts.map +1 -1
- package/dist/adapters/python/index.js +240 -109
- package/dist/adapters/python/index.js.map +1 -1
- package/dist/adapters/ruby/index.d.ts +3 -3
- package/dist/adapters/ruby/index.d.ts.map +1 -1
- package/dist/adapters/ruby/index.js +235 -46
- package/dist/adapters/ruby/index.js.map +1 -1
- package/dist/adapters/rust/index.d.ts +5 -9
- package/dist/adapters/rust/index.d.ts.map +1 -1
- package/dist/adapters/rust/index.js +237 -169
- package/dist/adapters/rust/index.js.map +1 -1
- package/dist/adapters/shared.d.ts +7 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +68 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/type-utils.d.ts +13 -0
- package/dist/adapters/type-utils.d.ts.map +1 -0
- package/dist/adapters/type-utils.js +105 -0
- package/dist/adapters/type-utils.js.map +1 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitSchema.js +39 -27
- 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.js +15 -15
- package/dist/adapters/typescript/emitter/importCollector.d.ts +2 -4
- package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/importCollector.js +40 -50
- package/dist/adapters/typescript/emitter/importCollector.js.map +1 -1
- package/dist/adapters/typescript/emitter/parsers.d.ts +1 -1
- package/dist/adapters/typescript/emitter/parsers.js +13 -13
- package/dist/adapters/typescript/emitter/typeHoister.d.ts +1 -1
- package/dist/adapters/typescript/emitter/typeHoister.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/typeHoister.js +12 -20
- package/dist/adapters/typescript/emitter/typeHoister.js.map +1 -1
- package/dist/adapters/typescript/index.d.ts +3 -3
- package/dist/adapters/typescript/index.d.ts.map +1 -1
- package/dist/adapters/typescript/index.js +148 -166
- package/dist/adapters/typescript/index.js.map +1 -1
- package/dist/adapters/typescript/shared.d.ts +2 -2
- package/dist/adapters/typescript/shared.js +12 -12
- package/dist/cli.js +50 -37
- package/dist/cli.js.map +1 -1
- package/dist/collector.d.ts +77 -3
- package/dist/collector.d.ts.map +1 -1
- package/dist/collector.js +198 -20
- package/dist/collector.js.map +1 -1
- package/dist/elysia.js +2 -2
- package/dist/elysia.js.map +1 -1
- package/dist/express.d.ts.map +1 -1
- package/dist/express.js.map +1 -1
- package/dist/fastify.d.ts +8 -9
- package/dist/fastify.d.ts.map +1 -1
- package/dist/fastify.js +105 -164
- package/dist/fastify.js.map +1 -1
- package/dist/helpers/handlebars.d.ts +7 -16
- package/dist/helpers/handlebars.d.ts.map +1 -1
- package/dist/helpers/handlebars.js +127 -177
- package/dist/helpers/handlebars.js.map +1 -1
- package/dist/hono.d.ts.map +1 -1
- package/dist/hono.js.map +1 -1
- package/dist/index.d.ts +14 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -30
- package/dist/index.js.map +1 -1
- package/dist/koa.d.ts.map +1 -1
- package/dist/koa.js.map +1 -1
- package/dist/nestjs.d.ts.map +1 -1
- package/dist/nestjs.js +41 -14
- package/dist/nestjs.js.map +1 -1
- package/dist/pipeline/enrich.d.ts +2 -13
- package/dist/pipeline/enrich.d.ts.map +1 -1
- package/dist/pipeline/enrich.js +157 -212
- package/dist/pipeline/enrich.js.map +1 -1
- package/dist/pipeline/intake.d.ts +3 -3
- package/dist/pipeline/intake.d.ts.map +1 -1
- package/dist/pipeline/intake.js +342 -238
- package/dist/pipeline/intake.js.map +1 -1
- package/dist/pipeline/write.d.ts +1 -14
- package/dist/pipeline/write.d.ts.map +1 -1
- package/dist/pipeline/write.js +73 -113
- package/dist/pipeline/write.js.map +1 -1
- package/dist/types/config.d.ts +14 -26
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +16 -12
- package/dist/types/config.js.map +1 -1
- package/dist/types/ir.d.ts +12 -8
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/manifest.d.ts +10 -11
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/manifest.js +54 -14
- package/dist/types/manifest.js.map +1 -1
- package/dist/types/sdk-module.d.ts +2 -2
- package/dist/types/sdk-module.js +1 -1
- package/dist/utils/format.d.ts +1 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +3 -10
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/naming.d.ts.map +1 -1
- package/dist/utils/naming.js +174 -184
- package/dist/utils/naming.js.map +1 -1
- package/dist/utils/npm-token.d.ts +1 -17
- package/dist/utils/npm-token.d.ts.map +1 -1
- package/dist/utils/npm-token.js +27 -76
- package/dist/utils/npm-token.js.map +1 -1
- package/dist/utils/version.d.ts +14 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +60 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +4 -3
- package/templates/csharp/client.hbs +26 -49
- package/templates/csharp/csproj.hbs +7 -0
- package/templates/csharp/domain.hbs +48 -130
- package/templates/csharp/errors.hbs +17 -25
- package/templates/csharp/readme.hbs +31 -44
- package/templates/csharp/transport.hbs +238 -136
- package/templates/csharp/types.hbs +30 -76
- package/templates/go/client.hbs +44 -109
- package/templates/go/domain.hbs +83 -25
- package/templates/go/go-mod.hbs +1 -4
- package/templates/go/readme.hbs +32 -29
- package/templates/go/transport.hbs +174 -173
- package/templates/go/types.hbs +17 -53
- package/templates/go/utils.hbs +3 -2
- package/templates/kotlin/build-gradle.hbs +1 -1
- package/templates/kotlin/client.hbs +18 -15
- package/templates/kotlin/domain.hbs +54 -51
- package/templates/kotlin/errors.hbs +1 -1
- package/templates/kotlin/readme.hbs +35 -137
- package/templates/kotlin/transport.hbs +140 -123
- package/templates/kotlin/types.hbs +30 -81
- package/templates/php/client.hbs +8 -46
- package/templates/php/composer.hbs +2 -1
- package/templates/php/domain.hbs +86 -117
- package/templates/php/errors.hbs +1 -1
- package/templates/php/readme.hbs +42 -49
- package/templates/php/transport.hbs +192 -145
- package/templates/php/types.hbs +63 -60
- package/templates/python/client.hbs +15 -2
- package/templates/python/domain.hbs +50 -108
- package/templates/python/pyproject.hbs +5 -0
- package/templates/python/readme.hbs +47 -30
- package/templates/python/resources-init.hbs +1 -1
- package/templates/python/transport.hbs +167 -53
- package/templates/python/types.hbs +22 -58
- package/templates/ruby/client.hbs +10 -6
- package/templates/ruby/domain.hbs +48 -67
- package/templates/ruby/errors.hbs +3 -3
- package/templates/ruby/gemspec.hbs +3 -10
- package/templates/ruby/index.hbs +3 -3
- package/templates/ruby/readme.hbs +33 -70
- package/templates/ruby/transport.hbs +129 -103
- package/templates/ruby/types.hbs +11 -16
- package/templates/ruby/version.hbs +1 -1
- package/templates/rust/cargo.hbs +2 -0
- package/templates/rust/client.hbs +10 -1
- package/templates/rust/domain.hbs +69 -52
- package/templates/rust/error.hbs +3 -1
- package/templates/rust/lib.hbs +4 -5
- package/templates/rust/readme.hbs +33 -46
- package/templates/rust/transport.hbs +170 -159
- package/templates/rust/types.hbs +34 -111
- package/templates/typescript/domain-class.hbs +14 -6
- package/templates/typescript/domain.hbs +13 -5
- package/templates/typescript/package-json.hbs +3 -1
- package/templates/typescript/readme.hbs +42 -42
- package/templates/typescript/transport.hbs +46 -8
- package/templates/typescript/types-errors.hbs +1 -1
- package/dist/adapters/typescript/model-emitter.d.ts +0 -13
- package/dist/adapters/typescript/model-emitter.d.ts.map +0 -1
- package/dist/adapters/typescript/model-emitter.js +0 -415
- package/dist/adapters/typescript/model-emitter.js.map +0 -1
|
@@ -1,76 +1,63 @@
|
|
|
1
|
-
# {{title}}
|
|
1
|
+
# {{title}} Rust SDK
|
|
2
2
|
|
|
3
|
-
{{#if description}}{{description}}{{/if}}
|
|
3
|
+
{{#if description}}{{description}}{{else}}Rust client for the {{title}} API.{{/if}}
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> Generated by Tesseract. Generated source files should not be edited manually.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
Add this to your `Cargo.toml`:
|
|
10
|
-
|
|
11
9
|
```toml
|
|
12
10
|
[dependencies]
|
|
13
|
-
{{crateName}} = "{{
|
|
11
|
+
{{crateName}} = "{{packageVersion}}"
|
|
14
12
|
```
|
|
15
13
|
|
|
16
|
-
## Quick
|
|
14
|
+
## Quick start
|
|
17
15
|
|
|
18
16
|
```rust
|
|
19
|
-
use {{crateName}}::Client;
|
|
20
|
-
use {{crateName}}::ClientConfig;
|
|
17
|
+
use {{crateName}}::{Client, ClientConfig};
|
|
21
18
|
|
|
22
19
|
#[tokio::main]
|
|
23
20
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
24
|
-
let
|
|
25
|
-
{{#
|
|
26
|
-
{{
|
|
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());
|
|
21
|
+
let client = Client::new(ClientConfig::default());
|
|
22
|
+
{{#if groups.length}}
|
|
23
|
+
let _api = &client.{{snakeCase groups.[0].name}};
|
|
31
24
|
{{/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
25
|
Ok(())
|
|
44
26
|
}
|
|
45
27
|
```
|
|
46
28
|
|
|
47
|
-
##
|
|
29
|
+
## Configuration and reliability
|
|
48
30
|
|
|
49
|
-
|
|
50
|
-
### {{interfaceName}}
|
|
31
|
+
The generated client supports bounded timeouts, exponential-backoff retries, `Retry-After` handling, structured `SdkError` values, and safe client reuse. Unsafe requests are not retried unless they are explicitly made idempotent or unsafe retries are enabled.
|
|
51
32
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{{/if}}
|
|
33
|
+
```rust
|
|
34
|
+
use std::time::Duration;
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
let config = ClientConfig {
|
|
37
|
+
timeout: Duration::from_secs(30),
|
|
38
|
+
max_retries: 3,
|
|
39
|
+
..Default::default()
|
|
40
|
+
};
|
|
41
|
+
```
|
|
61
42
|
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
- [Documentation index](./docs/README.md)
|
|
46
|
+
- [Generated types](./docs/types.md)
|
|
47
|
+
|
|
48
|
+
| Domain | Description |
|
|
49
|
+
|---|---|
|
|
50
|
+
{{#each groups}}
|
|
51
|
+
| [{{interfaceName}}](./docs/domains/{{fileName}}.md) | {{#if description}}{{description}}{{else}}{{name}} operations{{/if}} |
|
|
62
52
|
{{/each}}
|
|
63
53
|
|
|
64
|
-
## Error
|
|
54
|
+
## Error handling
|
|
55
|
+
|
|
56
|
+
All operations return `Result<T, SdkError>`. API errors retain the HTTP status, machine-readable code, request ID, and response body where available.
|
|
65
57
|
|
|
66
|
-
|
|
58
|
+
## Requirements
|
|
67
59
|
|
|
68
|
-
|
|
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 |
|
|
60
|
+
- Rust 1.75 or later
|
|
74
61
|
|
|
75
62
|
## License
|
|
76
63
|
|
|
@@ -2,11 +2,11 @@ use crate::error::SdkError;
|
|
|
2
2
|
use reqwest::{Client as ReqwestClient, Method, RequestBuilder, Response, StatusCode};
|
|
3
3
|
use serde::de::DeserializeOwned;
|
|
4
4
|
use serde::Serialize;
|
|
5
|
+
use std::collections::HashMap;
|
|
5
6
|
use std::sync::Arc;
|
|
6
|
-
use std::time::Duration;
|
|
7
|
+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
7
8
|
use url::Url;
|
|
8
9
|
|
|
9
|
-
/// Internal transport configuration.
|
|
10
10
|
#[derive(Debug, Clone)]
|
|
11
11
|
pub struct TransportConfig {
|
|
12
12
|
pub base_url: String,
|
|
@@ -15,13 +15,10 @@ pub struct TransportConfig {
|
|
|
15
15
|
{{/each}}
|
|
16
16
|
pub timeout: Duration,
|
|
17
17
|
pub max_retries: u32,
|
|
18
|
+
pub retry_unsafe_requests: bool,
|
|
19
|
+
pub default_headers: HashMap<String, String>,
|
|
18
20
|
}
|
|
19
21
|
|
|
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
22
|
#[derive(Clone)]
|
|
26
23
|
pub struct Transport {
|
|
27
24
|
client: ReqwestClient,
|
|
@@ -29,20 +26,15 @@ pub struct Transport {
|
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
impl Transport {
|
|
32
|
-
/// Create a new transport with the given configuration.
|
|
33
29
|
pub fn new(config: TransportConfig) -> Self {
|
|
34
30
|
let client = ReqwestClient::builder()
|
|
35
31
|
.timeout(config.timeout)
|
|
32
|
+
.user_agent("{{crateName}}-rust-sdk/{{packageVersion}}")
|
|
36
33
|
.build()
|
|
37
34
|
.expect("failed to build reqwest client");
|
|
38
|
-
|
|
39
|
-
Self {
|
|
40
|
-
client,
|
|
41
|
-
config: Arc::new(config),
|
|
42
|
-
}
|
|
35
|
+
Self { client, config: Arc::new(config) }
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
/// Execute a request, deserializing the response body as JSON.
|
|
46
38
|
pub async fn execute<T: DeserializeOwned>(
|
|
47
39
|
&self,
|
|
48
40
|
method: Method,
|
|
@@ -51,23 +43,11 @@ impl Transport {
|
|
|
51
43
|
body: Option<&(impl Serialize + ?Sized)>,
|
|
52
44
|
headers: Option<&[(String, String)]>,
|
|
53
45
|
) -> Result<T, SdkError> {
|
|
54
|
-
let
|
|
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
|
-
|
|
46
|
+
let request = self.build_request(method.clone(), path, query, body, headers)?;
|
|
47
|
+
let response = self.execute_with_retry(request, &method, headers).await?;
|
|
67
48
|
self.handle_response(response).await
|
|
68
49
|
}
|
|
69
50
|
|
|
70
|
-
/// Execute a request that returns the raw response text.
|
|
71
51
|
pub async fn execute_raw(
|
|
72
52
|
&self,
|
|
73
53
|
method: Method,
|
|
@@ -76,212 +56,243 @@ impl Transport {
|
|
|
76
56
|
body: Option<&(impl Serialize + ?Sized)>,
|
|
77
57
|
headers: Option<&[(String, String)]>,
|
|
78
58
|
) -> Result<String, SdkError> {
|
|
79
|
-
let
|
|
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
|
-
|
|
59
|
+
let request = self.build_request(method.clone(), path, query, body, headers)?;
|
|
60
|
+
let response = self.execute_with_retry(request, &method, headers).await?;
|
|
92
61
|
let status = response.status();
|
|
93
|
-
let
|
|
94
|
-
|
|
62
|
+
let request_id = response
|
|
63
|
+
.headers()
|
|
64
|
+
.get("x-request-id")
|
|
65
|
+
.or_else(|| response.headers().get("request-id"))
|
|
66
|
+
.and_then(|value| value.to_str().ok())
|
|
67
|
+
.map(String::from);
|
|
68
|
+
let text = response.text().await.map_err(|error| {
|
|
69
|
+
SdkError::DeserializationError(format!("failed to read response body: {}", error))
|
|
95
70
|
})?;
|
|
96
|
-
|
|
97
71
|
if status.is_success() {
|
|
98
72
|
Ok(text)
|
|
99
73
|
} else {
|
|
100
|
-
Err(
|
|
74
|
+
Err(Self::build_api_error(status, request_id, &text))
|
|
101
75
|
}
|
|
102
76
|
}
|
|
103
77
|
|
|
104
|
-
|
|
78
|
+
fn build_request(
|
|
79
|
+
&self,
|
|
80
|
+
method: Method,
|
|
81
|
+
path: &str,
|
|
82
|
+
query: Option<&[(String, String)]>,
|
|
83
|
+
body: Option<&(impl Serialize + ?Sized)>,
|
|
84
|
+
headers: Option<&[(String, String)]>,
|
|
85
|
+
) -> Result<RequestBuilder, SdkError> {
|
|
86
|
+
let url = self.build_url(path, query)?;
|
|
87
|
+
let mut request = self.client.request(method, url);
|
|
88
|
+
request = self.apply_auth(request);
|
|
89
|
+
request = self.apply_headers(request, headers);
|
|
90
|
+
if let Some(body) = body {
|
|
91
|
+
request = request.json(body);
|
|
92
|
+
}
|
|
93
|
+
Ok(request)
|
|
94
|
+
}
|
|
105
95
|
|
|
106
96
|
fn build_url(
|
|
107
97
|
&self,
|
|
108
98
|
path: &str,
|
|
109
99
|
query: Option<&[(String, String)]>,
|
|
110
100
|
) -> Result<Url, SdkError> {
|
|
111
|
-
let base = Url::parse(&self.config.base_url)
|
|
112
|
-
SdkError::ValidationError(format!("invalid base URL: {}",
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
SdkError::ValidationError(format!("invalid path '{}': {}", path, e))
|
|
117
|
-
})?;
|
|
118
|
-
|
|
101
|
+
let base = Url::parse(&self.config.base_url)
|
|
102
|
+
.map_err(|error| SdkError::ValidationError(format!("invalid base URL: {}", error)))?;
|
|
103
|
+
let mut url = base
|
|
104
|
+
.join(path)
|
|
105
|
+
.map_err(|error| SdkError::ValidationError(format!("invalid path '{}': {}", path, error)))?;
|
|
119
106
|
if let Some(params) = query {
|
|
120
|
-
let mut
|
|
121
|
-
{
|
|
122
|
-
|
|
123
|
-
for (k, v) in params {
|
|
124
|
-
qp.append_pair(k, v);
|
|
125
|
-
}
|
|
107
|
+
let mut pairs = url.query_pairs_mut();
|
|
108
|
+
for (key, value) in params {
|
|
109
|
+
pairs.append_pair(key, value);
|
|
126
110
|
}
|
|
127
|
-
Ok(url)
|
|
128
|
-
} else {
|
|
129
|
-
Ok(joined)
|
|
130
111
|
}
|
|
112
|
+
Ok(url)
|
|
131
113
|
}
|
|
132
114
|
|
|
133
|
-
fn apply_auth(&self, mut
|
|
115
|
+
fn apply_auth(&self, mut request: RequestBuilder) -> RequestBuilder {
|
|
134
116
|
{{#each securitySchemes}}
|
|
135
117
|
{{#if (eq type "apiKey")}}
|
|
136
118
|
{{#if (eq in "header")}}
|
|
137
119
|
if let Some(ref key) = self.config.{{snakeCase configKey}} {
|
|
138
|
-
|
|
120
|
+
request = request.header("{{paramName}}", key);
|
|
139
121
|
}
|
|
140
122
|
{{/if}}
|
|
141
|
-
{{#if (eq in "query")}}
|
|
142
|
-
// API key via query parameter handled during URL construction
|
|
143
123
|
{{/if}}
|
|
144
|
-
{{
|
|
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")}}
|
|
124
|
+
{{#if (or (and (eq type "http") (eq scheme "bearer")) (eq type "oauth2") (eq type "openIdConnect"))}}
|
|
151
125
|
if let Some(ref token) = self.config.{{snakeCase configKey}} {
|
|
152
|
-
|
|
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));
|
|
126
|
+
request = request.bearer_auth(token);
|
|
158
127
|
}
|
|
159
128
|
{{/if}}
|
|
160
129
|
{{/each}}
|
|
161
|
-
|
|
130
|
+
request
|
|
162
131
|
}
|
|
163
132
|
|
|
164
133
|
fn apply_headers(
|
|
165
134
|
&self,
|
|
166
|
-
mut
|
|
135
|
+
mut request: RequestBuilder,
|
|
167
136
|
headers: Option<&[(String, String)]>,
|
|
168
137
|
) -> RequestBuilder {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
138
|
+
request = request.header("Accept", "application/json");
|
|
139
|
+
for (key, value) in &self.config.default_headers {
|
|
140
|
+
request = request.header(key, value);
|
|
141
|
+
}
|
|
142
|
+
if let Some(headers) = headers {
|
|
143
|
+
for (key, value) in headers {
|
|
144
|
+
request = request.header(key, value);
|
|
172
145
|
}
|
|
173
146
|
}
|
|
174
|
-
|
|
147
|
+
request
|
|
175
148
|
}
|
|
176
149
|
|
|
177
150
|
async fn execute_with_retry(
|
|
178
151
|
&self,
|
|
179
|
-
|
|
152
|
+
request: RequestBuilder,
|
|
153
|
+
method: &Method,
|
|
154
|
+
headers: Option<&[(String, String)]>,
|
|
180
155
|
) -> Result<Response, SdkError> {
|
|
181
|
-
let
|
|
156
|
+
let can_retry = self.can_retry(method, headers);
|
|
182
157
|
let mut attempt = 0u32;
|
|
183
158
|
|
|
184
159
|
loop {
|
|
185
|
-
let
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
match request.send().await {
|
|
160
|
+
let current = request.try_clone().ok_or_else(|| {
|
|
161
|
+
SdkError::NetworkError("request body cannot be cloned for execution".to_string())
|
|
162
|
+
})?;
|
|
163
|
+
match current.send().await {
|
|
190
164
|
Ok(response) => {
|
|
191
165
|
let status = response.status();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
let backoff = Duration::from_millis(
|
|
198
|
-
200u64 * 2u64.pow(attempt),
|
|
199
|
-
);
|
|
200
|
-
tokio::time::sleep(backoff).await;
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
166
|
+
if can_retry && is_retryable_status(status) && attempt < self.config.max_retries {
|
|
167
|
+
let delay = retry_delay(response.headers().get("retry-after"), attempt);
|
|
168
|
+
attempt += 1;
|
|
169
|
+
tokio::time::sleep(delay).await;
|
|
170
|
+
continue;
|
|
203
171
|
}
|
|
204
172
|
return Ok(response);
|
|
205
173
|
}
|
|
206
|
-
Err(
|
|
207
|
-
if
|
|
174
|
+
Err(error) => {
|
|
175
|
+
if can_retry
|
|
176
|
+
&& attempt < self.config.max_retries
|
|
177
|
+
&& (error.is_connect() || error.is_timeout() || error.is_request())
|
|
178
|
+
{
|
|
179
|
+
let delay = retry_delay(None, attempt);
|
|
208
180
|
attempt += 1;
|
|
209
|
-
|
|
210
|
-
200u64 * 2u64.pow(attempt),
|
|
211
|
-
);
|
|
212
|
-
tokio::time::sleep(backoff).await;
|
|
181
|
+
tokio::time::sleep(delay).await;
|
|
213
182
|
continue;
|
|
214
183
|
}
|
|
215
|
-
return Err(SdkError::NetworkError(
|
|
184
|
+
return Err(SdkError::NetworkError(error.to_string()));
|
|
216
185
|
}
|
|
217
186
|
}
|
|
218
187
|
}
|
|
219
188
|
}
|
|
220
189
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
190
|
+
fn can_retry(&self, method: &Method, headers: Option<&[(String, String)]>) -> bool {
|
|
191
|
+
if self.config.retry_unsafe_requests
|
|
192
|
+
|| method == Method::GET
|
|
193
|
+
|| method == Method::HEAD
|
|
194
|
+
|| method == Method::OPTIONS
|
|
195
|
+
|| method == Method::PUT
|
|
196
|
+
|| method == Method::DELETE
|
|
197
|
+
{
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
self.config
|
|
202
|
+
.default_headers
|
|
203
|
+
.iter()
|
|
204
|
+
.chain(headers.unwrap_or_default().iter())
|
|
205
|
+
.any(|(name, value)| name.eq_ignore_ascii_case("x-idempotency-key") && !value.is_empty())
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async fn handle_response<T: DeserializeOwned>(&self, response: Response) -> Result<T, SdkError> {
|
|
225
209
|
let status = response.status();
|
|
226
|
-
let
|
|
227
|
-
|
|
210
|
+
let request_id = response
|
|
211
|
+
.headers()
|
|
212
|
+
.get("x-request-id")
|
|
213
|
+
.or_else(|| response.headers().get("request-id"))
|
|
214
|
+
.and_then(|value| value.to_str().ok())
|
|
215
|
+
.map(String::from);
|
|
216
|
+
let text = response.text().await.map_err(|error| {
|
|
217
|
+
SdkError::DeserializationError(format!("failed to read response body: {}", error))
|
|
228
218
|
})?;
|
|
229
|
-
|
|
230
219
|
if status.is_success() {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
220
|
+
if text.trim().is_empty() {
|
|
221
|
+
serde_json::from_str("null").map_err(|error| {
|
|
222
|
+
SdkError::DeserializationError(format!("empty response could not be decoded: {}", error))
|
|
223
|
+
})
|
|
224
|
+
} else {
|
|
225
|
+
serde_json::from_str(&text).map_err(|error| {
|
|
226
|
+
SdkError::DeserializationError(format!(
|
|
227
|
+
"failed to deserialize response (status {}): {}",
|
|
228
|
+
status, error
|
|
229
|
+
))
|
|
230
|
+
})
|
|
231
|
+
}
|
|
237
232
|
} else {
|
|
238
|
-
Err(
|
|
233
|
+
Err(Self::build_api_error(status, request_id, &text))
|
|
239
234
|
}
|
|
240
235
|
}
|
|
241
236
|
|
|
242
|
-
fn build_api_error(
|
|
243
|
-
|
|
244
|
-
let
|
|
245
|
-
|
|
246
|
-
.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
.
|
|
259
|
-
.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
237
|
+
fn build_api_error(status: StatusCode, request_id: Option<String>, body: &str) -> SdkError {
|
|
238
|
+
let parsed = serde_json::from_str::<serde_json::Value>(body).ok();
|
|
239
|
+
let nested = parsed.as_ref().and_then(|value| value.get("error"));
|
|
240
|
+
let code = parsed
|
|
241
|
+
.as_ref()
|
|
242
|
+
.and_then(|value| value.get("code"))
|
|
243
|
+
.or_else(|| nested.and_then(|value| value.get("code")))
|
|
244
|
+
.or_else(|| nested.filter(|value| value.is_string()))
|
|
245
|
+
.and_then(|value| value.as_str())
|
|
246
|
+
.map(String::from);
|
|
247
|
+
let message = parsed
|
|
248
|
+
.as_ref()
|
|
249
|
+
.and_then(|value| value.get("message"))
|
|
250
|
+
.or_else(|| nested.and_then(|value| value.get("message")))
|
|
251
|
+
.or_else(|| nested.filter(|value| value.is_string()))
|
|
252
|
+
.and_then(|value| value.as_str())
|
|
253
|
+
.unwrap_or(body)
|
|
254
|
+
.to_string();
|
|
255
|
+
let body_request_id = parsed
|
|
256
|
+
.as_ref()
|
|
257
|
+
.and_then(|value| value.get("request_id"))
|
|
258
|
+
.and_then(|value| value.as_str())
|
|
259
|
+
.map(String::from);
|
|
272
260
|
SdkError::ApiError {
|
|
273
261
|
status: status.as_u16(),
|
|
274
262
|
code,
|
|
275
263
|
message,
|
|
264
|
+
request_id: body_request_id.or(request_id),
|
|
276
265
|
body: body.to_string(),
|
|
277
266
|
}
|
|
278
267
|
}
|
|
279
268
|
}
|
|
280
269
|
|
|
270
|
+
fn is_retryable_status(status: StatusCode) -> bool {
|
|
271
|
+
matches!(status.as_u16(), 408 | 425 | 429 | 500 | 502 | 503 | 504)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
fn retry_delay(retry_after: Option<&reqwest::header::HeaderValue>, attempt: u32) -> Duration {
|
|
275
|
+
if let Some(value) = retry_after.and_then(|value| value.to_str().ok()) {
|
|
276
|
+
if let Ok(seconds) = value.parse::<f64>() {
|
|
277
|
+
return Duration::from_secs_f64(seconds.max(0.0).min(30.0));
|
|
278
|
+
}
|
|
279
|
+
if let Ok(time) = httpdate::parse_http_date(value) {
|
|
280
|
+
if let Ok(delay) = time.duration_since(SystemTime::now()) {
|
|
281
|
+
return delay.min(Duration::from_secs(30));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
let base = (400u64.saturating_mul(2u64.saturating_pow(attempt))).min(30_000);
|
|
286
|
+
let jitter = SystemTime::now()
|
|
287
|
+
.duration_since(UNIX_EPOCH)
|
|
288
|
+
.unwrap_or_default()
|
|
289
|
+
.subsec_nanos() as u64
|
|
290
|
+
% (base / 2 + 1);
|
|
291
|
+
Duration::from_millis((base + jitter).min(30_000))
|
|
292
|
+
}
|
|
293
|
+
|
|
281
294
|
impl std::fmt::Debug for Transport {
|
|
282
|
-
fn fmt(&self,
|
|
283
|
-
|
|
284
|
-
.field("config", &self.config)
|
|
285
|
-
.finish()
|
|
295
|
+
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
296
|
+
formatter.debug_struct("Transport").field("config", &self.config).finish()
|
|
286
297
|
}
|
|
287
298
|
}
|