#![doc = "Generated reqwest client with typed errors and streaming bodies."]
use async_trait::async_trait;
use reqwest::{Client, Url};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use spiral_contract::auth::AuthHeader;
use spiral_contract::call::{CallError, ServiceFailure};
use spiral_contract::error::ErrorEnvelope;
use crate::api::workers::WorkersApi;
use crate::api::workers::objects::*;
#[derive(Clone)]
pub struct WorkersClient {
http: ClientWithMiddleware,
base: Url,
}
impl std::fmt::Debug for WorkersClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WorkersClient")
.field("base", &self.base)
.finish_non_exhaustive()
}
}
impl WorkersClient {
#[expect(
clippy::disallowed_methods,
reason = "Generated clients keep their transport independent of the Spiral application runtime."
)]
pub fn new(base: &str) -> Result<Self, CallError<WorkersError>> {
let http = Client::builder()
.redirect(reqwest::redirect::Policy::none())
.connect_timeout(std::time::Duration::from_secs(10))
.build()
.map_err(CallError::transport)?;
Self::from_http(base, http)
}
pub fn from_http(base: &str, http: Client) -> Result<Self, CallError<WorkersError>> {
Self::from_transport(base, ClientBuilder::new(http).build())
}
pub fn from_transport(
base: &str,
http: ClientWithMiddleware,
) -> Result<Self, CallError<WorkersError>> {
let base = Url::parse(base)
.map_err(|_| CallError::<WorkersError>::protocol("invalid base URL"))?;
if !matches!(base.scheme(), "http" | "https")
|| base.host_str().is_none()
|| !base.username().is_empty()
|| base.password().is_some()
|| base.query().is_some()
|| base.fragment().is_some()
{
return Err(CallError::<WorkersError>::protocol("invalid base URL"));
}
Ok(Self { http, base })
}
pub fn with_transport(mut self, http: ClientWithMiddleware) -> Self {
self.http = http;
self
}
pub fn with_http(mut self, http: Client) -> Self {
self.http = ClientBuilder::new(http).build();
self
}
pub async fn disable_managed_workers_http(
&self,
auth_header: AuthHeader,
project_id: String,
provider_id: String,
) -> Result<ManagedWorkerProviderResponse, CallError<WorkersError>> {
let mut url = self.base.clone();
{
let mut path = url.path_segments_mut().map_err(|_| {
CallError::<WorkersError>::protocol("base URL cannot contain path segments")
})?;
path.pop_if_empty();
path.push("api");
path.push("workers");
path.push("projects");
let segment = project_id.to_string();
if matches!(segment.as_str(), "." | "..") {
return Err(CallError::<WorkersError>::protocol("dot path segment"));
}
path.push(&segment);
path.push("managed-workers");
let segment = provider_id.to_string();
if matches!(segment.as_str(), "." | "..") {
return Err(CallError::<WorkersError>::protocol("dot path segment"));
}
path.push(&segment);
}
let builder = self.http.delete(url).header("accept", "application/json");
let builder = builder.header("authorization", auth_header.as_str());
let response = self.send(builder, "application/json").await?;
let status = response.status().as_u16();
let bytes = bounded_body(response, 8 * 1024 * 1024).await?;
serde_json::from_slice(&bytes)
.map_err(|_| CallError::protocol_response(status, "invalid JSON response"))
}
pub async fn enable_managed_workers_http(
&self,
auth_header: AuthHeader,
project_id: String,
provider_id: String,
) -> Result<ManagedWorkerProviderResponse, CallError<WorkersError>> {
let mut url = self.base.clone();
{
let mut path = url.path_segments_mut().map_err(|_| {
CallError::<WorkersError>::protocol("base URL cannot contain path segments")
})?;
path.pop_if_empty();
path.push("api");
path.push("workers");
path.push("projects");
let segment = project_id.to_string();
if matches!(segment.as_str(), "." | "..") {
return Err(CallError::<WorkersError>::protocol("dot path segment"));
}
path.push(&segment);
path.push("managed-workers");
let segment = provider_id.to_string();
if matches!(segment.as_str(), "." | "..") {
return Err(CallError::<WorkersError>::protocol("dot path segment"));
}
path.push(&segment);
}
let builder = self.http.put(url).header("accept", "application/json");
let builder = builder.header("authorization", auth_header.as_str());
let response = self.send(builder, "application/json").await?;
let status = response.status().as_u16();
let bytes = bounded_body(response, 8 * 1024 * 1024).await?;
serde_json::from_slice(&bytes)
.map_err(|_| CallError::protocol_response(status, "invalid JSON response"))
}
pub async fn read_managed_workers_http(
&self,
auth_header: AuthHeader,
project_id: String,
) -> Result<ManagedWorkersResponse, CallError<WorkersError>> {
let mut url = self.base.clone();
{
let mut path = url.path_segments_mut().map_err(|_| {
CallError::<WorkersError>::protocol("base URL cannot contain path segments")
})?;
path.pop_if_empty();
path.push("api");
path.push("workers");
path.push("projects");
let segment = project_id.to_string();
if matches!(segment.as_str(), "." | "..") {
return Err(CallError::<WorkersError>::protocol("dot path segment"));
}
path.push(&segment);
path.push("managed-workers");
}
let builder = self.http.get(url).header("accept", "application/json");
let builder = builder.header("authorization", auth_header.as_str());
let response = self.send(builder, "application/json").await?;
let status = response.status().as_u16();
let bytes = bounded_body(response, 8 * 1024 * 1024).await?;
serde_json::from_slice(&bytes)
.map_err(|_| CallError::protocol_response(status, "invalid JSON response"))
}
async fn send(
&self,
builder: reqwest_middleware::RequestBuilder,
expected_media: &str,
) -> Result<reqwest::Response, CallError<WorkersError>> {
let response = builder
.timeout(std::time::Duration::from_secs(60))
.send()
.await
.map_err(CallError::transport)?;
let status = response.status();
if !status.is_success() {
let status = status.as_u16();
let content_type = response
.headers()
.get("content-type")
.and_then(|value| value.to_str().ok())
.map(str::to_owned);
let (bytes, truncated) = bounded_error_body::<WorkersError>(
response,
64 * 1024,
status,
content_type.clone(),
)
.await?;
if truncated {
return Err(CallError::unknown_remote(status, content_type, bytes, true));
}
return match serde_json::from_slice::<ErrorEnvelope>(&bytes) {
Ok(envelope) => Err(CallError::Service(ServiceFailure::remote(status, envelope))),
Err(_) => Err(CallError::unknown_remote(
status,
content_type,
bytes,
false,
)),
};
}
if !media_matches(&response, expected_media) {
return Err(CallError::protocol_response(
status.as_u16(),
"unexpected response media type",
));
}
Ok(response)
}
}
fn media_matches(response: &reqwest::Response, expected: &str) -> bool {
response
.headers()
.get("content-type")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.split(';').next())
.is_some_and(|value| value.trim().eq_ignore_ascii_case(expected))
}
async fn bounded_body(
mut response: reqwest::Response,
limit: usize,
) -> Result<Vec<u8>, CallError<WorkersError>> {
let status = response.status().as_u16();
let mut result = Vec::new();
while let Some(chunk) = response
.chunk()
.await
.map_err(|error| CallError::transport_response(status, error))?
{
if chunk.len() > limit.saturating_sub(result.len()) {
return Err(CallError::protocol_response(
status,
"response exceeds JSON size limit",
));
}
result.extend_from_slice(&chunk);
}
Ok(result)
}
async fn bounded_error_body<E>(
mut response: reqwest::Response,
limit: usize,
status: u16,
content_type: Option<String>,
) -> Result<(Vec<u8>, bool), CallError<E>> {
let mut result = Vec::new();
loop {
let chunk = response.chunk().await.map_err(|error| {
CallError::unreadable_remote(status, content_type.clone(), result.clone(), error)
})?;
let Some(chunk) = chunk else {
return Ok((result, false));
};
let remaining = limit.saturating_sub(result.len());
if chunk.len() > remaining {
result.extend_from_slice(&chunk[..remaining]);
return Ok((result, true));
}
result.extend_from_slice(&chunk);
}
}
#[async_trait]
impl WorkersApi for WorkersClient {
async fn disable_managed_workers(
&self,
auth_header: AuthHeader,
project_id: String,
provider_id: String,
) -> Result<ManagedWorkerProviderResponse, CallError<WorkersError>> {
let response =
Self::disable_managed_workers_http(self, auth_header, project_id, provider_id).await?;
Ok(response)
}
async fn enable_managed_workers(
&self,
auth_header: AuthHeader,
project_id: String,
provider_id: String,
) -> Result<ManagedWorkerProviderResponse, CallError<WorkersError>> {
let response =
Self::enable_managed_workers_http(self, auth_header, project_id, provider_id).await?;
Ok(response)
}
async fn read_managed_workers(
&self,
auth_header: AuthHeader,
project_id: String,
) -> Result<ManagedWorkersResponse, CallError<WorkersError>> {
let response = Self::read_managed_workers_http(self, auth_header, project_id).await?;
Ok(response)
}
}