#![cfg(all(feature = "middleware", not(target_arch = "wasm32")))]
use crate::auth::AuthType;
use crate::http::reqwest_transport::ReqwestTransport;
use crate::http::{HttpRequest, Method, Transport};
use crate::response::deserialize_response;
use jules_core::client::ClientRequest;
use jules_core::errors::{SDKError, ValidationError};
use jules_core::message::Message;
use jules_core::middleware::retry::RetryMiddleware;
use jules_core::middleware::MiddlewarePipeline;
use jules_core::response::ClientResponse;
use jules_core::traits::Client;
use serde::Serialize;
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
mod endpoints;
pub use endpoints::CreateSessionParams;
pub const JULES_API_BASE_URL: &str = "https://jules.googleapis.com";
#[derive(Clone)]
pub struct JulesClient {
transport: ReqwestTransport,
base_url: String,
auth: AuthType,
pipeline: Arc<MiddlewarePipeline>,
}
impl JulesClient {
#[must_use]
pub fn base_url(&self) -> &str {
&self.base_url
}
}
#[derive(Serialize)]
struct RequestBody<'a> {
messages: &'a [Message],
}
async fn send_once(
transport: ReqwestTransport,
base_url: String,
auth: AuthType,
request: ClientRequest,
) -> Result<ClientResponse, SDKError> {
let body = serde_json::to_vec(&RequestBody {
messages: request.conversation.messages(),
})
.map_err(|e| {
SDKError::Validation(ValidationError::new(format!(
"failed to serialize request: {e}"
)))
})?;
let http_request = HttpRequest::new(Method::Post, base_url)
.with_header("Content-Type", "application/json")
.with_body(body);
let http_request = auth.apply(http_request);
let http_response = transport.send(http_request).await?;
deserialize_response(&http_response)
}
impl Client for JulesClient {
fn send_request(
&self,
request: ClientRequest,
) -> impl Future<Output = Result<ClientResponse, SDKError>> + Send {
let transport = self.transport.clone();
let base_url = self.base_url.clone();
let auth = self.auth.clone();
let pipeline = Arc::clone(&self.pipeline);
async move {
pipeline
.execute(request, move |req| {
let transport = transport.clone();
let base_url = base_url.clone();
let auth = auth.clone();
async move { send_once(transport, base_url, auth, req).await }
})
.await
}
}
}
#[derive(Debug, Clone)]
pub struct JulesClientBuilder {
base_url: String,
timeout: Duration,
auth: AuthType,
}
impl Default for JulesClientBuilder {
fn default() -> Self {
Self {
base_url: JULES_API_BASE_URL.to_string(),
timeout: Duration::from_secs(30),
auth: AuthType::None,
}
}
}
impl JulesClientBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = base_url.into();
self
}
#[must_use]
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn auth(mut self, auth: AuthType) -> Self {
self.auth = auth;
self
}
pub fn build(self) -> Result<JulesClient, SDKError> {
let transport = ReqwestTransport::new(self.timeout);
let mut pipeline = MiddlewarePipeline::new();
pipeline.add(RetryMiddleware::new());
Ok(JulesClient {
transport,
base_url: self.base_url,
auth: self.auth,
pipeline: Arc::new(pipeline),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_defaults_to_jules_api_base_url() {
let client = JulesClientBuilder::new().build().unwrap();
assert_eq!(client.base_url(), JULES_API_BASE_URL);
}
#[test]
fn test_builder_success() {
let client = JulesClientBuilder::new()
.base_url("https://api.example.com")
.timeout(Duration::from_secs(5))
.auth(AuthType::Bearer("token".into()))
.build()
.unwrap();
assert_eq!(client.base_url(), "https://api.example.com");
}
}