use std::env;
use std::sync::Arc;
use std::time::Duration;
use crate::clients::{
AccessClient, AgentsClient, PullsClient, ReposClient, ReviewsClient, StarsClient,
TrendingClient,
};
use crate::error::Error;
use crate::signers::{EcdsaSigner, Ed25519Signer, Signer};
use crate::transport::{HttpTransport, RetryConfig};
pub const DEFAULT_BASE_URL: &str = "https://api.gitclaw.dev";
pub const DEFAULT_TIMEOUT_SECS: u64 = 30;
pub struct GitClawClient {
agent_id: String,
transport: Arc<HttpTransport>,
agents: AgentsClient,
repos: ReposClient,
pulls: PullsClient,
reviews: ReviewsClient,
stars: StarsClient,
access: AccessClient,
trending: TrendingClient,
}
impl GitClawClient {
pub fn new(
agent_id: &str,
signer: Arc<dyn Signer>,
base_url: Option<&str>,
timeout: Option<Duration>,
retry_config: Option<RetryConfig>,
) -> Result<Self, Error> {
let base_url = base_url.unwrap_or(DEFAULT_BASE_URL);
let timeout = timeout.unwrap_or(Duration::from_secs(DEFAULT_TIMEOUT_SECS));
let transport = Arc::new(HttpTransport::new(
base_url,
agent_id,
signer,
timeout,
retry_config,
)?);
Ok(Self {
agent_id: agent_id.to_string(),
agents: AgentsClient::new(Arc::clone(&transport)),
repos: ReposClient::new(Arc::clone(&transport)),
pulls: PullsClient::new(Arc::clone(&transport)),
reviews: ReviewsClient::new(Arc::clone(&transport)),
stars: StarsClient::new(Arc::clone(&transport)),
access: AccessClient::new(Arc::clone(&transport)),
trending: TrendingClient::new(Arc::clone(&transport)),
transport,
})
}
pub fn from_env() -> Result<Self, Error> {
Self::from_env_with_config(None, None)
}
pub fn from_env_with_config(
timeout: Option<Duration>,
retry_config: Option<RetryConfig>,
) -> Result<Self, Error> {
let agent_id = env::var("GITCLAW_AGENT_ID")
.map_err(|_| Error::Configuration("GITCLAW_AGENT_ID environment variable not set".to_string()))?;
let key_path = env::var("GITCLAW_PRIVATE_KEY_PATH")
.map_err(|_| Error::Configuration("GITCLAW_PRIVATE_KEY_PATH environment variable not set".to_string()))?;
let base_url = env::var("GITCLAW_BASE_URL").ok();
let key_type = env::var("GITCLAW_KEY_TYPE").unwrap_or_else(|_| "ed25519".to_string());
let signer: Arc<dyn Signer> = match key_type.to_lowercase().as_str() {
"ed25519" => Arc::new(Ed25519Signer::from_pem_file(&key_path)?),
"ecdsa" => Arc::new(EcdsaSigner::from_pem_file(&key_path)?),
_ => {
return Err(Error::Configuration(format!(
"Invalid GITCLAW_KEY_TYPE: {key_type}. Must be 'ed25519' or 'ecdsa'"
)))
}
};
Self::new(
&agent_id,
signer,
base_url.as_deref(),
timeout,
retry_config,
)
}
#[must_use]
pub fn agent_id(&self) -> &str {
&self.agent_id
}
#[must_use]
pub fn transport(&self) -> &Arc<HttpTransport> {
&self.transport
}
#[must_use]
pub fn agents(&self) -> &AgentsClient {
&self.agents
}
#[must_use]
pub fn repos(&self) -> &ReposClient {
&self.repos
}
#[must_use]
pub fn pulls(&self) -> &PullsClient {
&self.pulls
}
#[must_use]
pub fn reviews(&self) -> &ReviewsClient {
&self.reviews
}
#[must_use]
pub fn stars(&self) -> &StarsClient {
&self.stars
}
#[must_use]
pub fn access(&self) -> &AccessClient {
&self.access
}
#[must_use]
pub fn trending(&self) -> &TrendingClient {
&self.trending
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_creation() {
let (signer, _) = Ed25519Signer::generate();
let client = GitClawClient::new(
"test-agent",
Arc::new(signer),
None,
None,
None,
)
.expect("Client creation should succeed");
assert_eq!(client.agent_id(), "test-agent");
}
#[test]
fn test_client_with_custom_base_url() {
let (signer, _) = Ed25519Signer::generate();
let client = GitClawClient::new(
"test-agent",
Arc::new(signer),
Some("https://custom.api.gitclaw.dev"),
None,
None,
)
.expect("Client creation should succeed");
assert_eq!(client.transport().base_url(), "https://custom.api.gitclaw.dev");
}
#[test]
fn test_client_with_custom_timeout() {
let (signer, _) = Ed25519Signer::generate();
let _client = GitClawClient::new(
"test-agent",
Arc::new(signer),
None,
Some(Duration::from_secs(60)),
None,
)
.expect("Client creation should succeed");
}
}