1use std::sync::Arc;
9use std::time::Duration;
10
11use url::Url;
12
13use crate::auth::Credentials;
14use crate::config::{DEFAULT_BASE_URL, ENV_API_KEY, ENV_BASE_URL};
15use crate::error::{Error, Result};
16use crate::messages::MessagesService;
17
18#[derive(Debug, Clone)]
20pub struct Client {
21 pub(crate) inner: Arc<ClientInner>,
22}
23
24#[derive(Debug)]
25pub(crate) struct ClientInner {
26 pub(crate) http: reqwest::Client,
27 pub(crate) base_url: Url,
28 pub(crate) credentials: Credentials,
29}
30
31impl Client {
32 pub fn from_env() -> Result<Self> {
37 let api_key = std::env::var(ENV_API_KEY)
38 .map_err(|_| Error::Config(format!("environment variable {ENV_API_KEY} is not set")))?;
39 let mut builder = Self::builder().api_key(api_key);
40 if let Ok(url) = std::env::var(ENV_BASE_URL) {
41 builder = builder.base_url(
42 Url::parse(&url)
43 .map_err(|e| Error::Config(format!("invalid {ENV_BASE_URL}: {e}")))?,
44 );
45 }
46 builder.build()
47 }
48
49 pub fn builder() -> ClientBuilder {
51 ClientBuilder::default()
52 }
53
54 pub fn messages(&self) -> MessagesService<'_> {
56 MessagesService::new(self)
57 }
58}
59
60#[derive(Debug, Default)]
62pub struct ClientBuilder {
63 api_key: Option<String>,
64 base_url: Option<Url>,
65 timeout: Option<Duration>,
66 http_client: Option<reqwest::Client>,
67}
68
69impl ClientBuilder {
70 pub fn api_key(mut self, key: impl Into<String>) -> Self {
72 self.api_key = Some(key.into());
73 self
74 }
75
76 pub fn base_url(mut self, url: Url) -> Self {
78 self.base_url = Some(url);
79 self
80 }
81
82 pub fn timeout(mut self, timeout: Duration) -> Self {
85 self.timeout = Some(timeout);
86 self
87 }
88
89 pub fn http_client(mut self, http: reqwest::Client) -> Self {
93 self.http_client = Some(http);
94 self
95 }
96
97 pub fn build(self) -> Result<Client> {
99 let api_key = self
100 .api_key
101 .ok_or_else(|| Error::Config("api_key is required".into()))?;
102 if api_key.trim().is_empty() {
103 return Err(Error::Config("api_key must not be empty".into()));
104 }
105
106 let base_url = match self.base_url {
107 Some(u) => u,
108 None => Url::parse(DEFAULT_BASE_URL)
109 .map_err(|e| Error::Config(format!("default base URL is invalid: {e}")))?,
110 };
111
112 let http = match self.http_client {
113 Some(c) => c,
114 None => {
115 let mut b = reqwest::Client::builder();
116 if let Some(t) = self.timeout {
117 b = b.timeout(t);
118 }
119 b.build().map_err(Error::Http)?
120 }
121 };
122
123 Ok(Client {
124 inner: Arc::new(ClientInner {
125 http,
126 base_url,
127 credentials: Credentials::from_api_key(api_key),
128 }),
129 })
130 }
131}