Skip to main content

kagi_sdk/
client.rs

1use crate::{
2    auth::{BotToken, CredentialKind, Credentials, SessionToken},
3    config::ClientConfig,
4    error::KagiError,
5    official_api::OfficialApi,
6    routing::ProtocolSurface,
7    session_web::SessionWeb,
8    transport::Transport,
9};
10
11#[derive(Debug, Clone)]
12pub struct KagiClient {
13    transport: Transport,
14    credentials: Credentials,
15}
16
17impl KagiClient {
18    pub fn builder() -> KagiClientBuilder {
19        KagiClientBuilder::default()
20    }
21
22    pub fn new(credentials: Credentials, config: ClientConfig) -> Result<Self, KagiError> {
23        let transport = Transport::new(config)?;
24        Ok(Self {
25            transport,
26            credentials,
27        })
28    }
29
30    pub fn with_bot_token(token: BotToken) -> Result<Self, KagiError> {
31        Self::new(Credentials::from(token), ClientConfig::default())
32    }
33
34    pub fn with_session_token(token: SessionToken) -> Result<Self, KagiError> {
35        Self::new(Credentials::from(token), ClientConfig::default())
36    }
37
38    pub fn official_api(&self) -> Result<OfficialApi<'_>, KagiError> {
39        self.ensure_surface_access(ProtocolSurface::OfficialApi)?;
40        Ok(OfficialApi::new(self))
41    }
42
43    pub fn session_web(&self) -> Result<SessionWeb<'_>, KagiError> {
44        self.ensure_surface_access(ProtocolSurface::SessionWeb)?;
45        Ok(SessionWeb::new(self))
46    }
47
48    pub(crate) fn transport(&self) -> &Transport {
49        &self.transport
50    }
51
52    pub(crate) fn credentials(&self) -> &Credentials {
53        &self.credentials
54    }
55
56    fn ensure_surface_access(&self, surface: ProtocolSurface) -> Result<(), KagiError> {
57        let provided = self.credentials.kind();
58        let expected = match surface {
59            ProtocolSurface::OfficialApi => CredentialKind::BotToken,
60            ProtocolSurface::SessionWeb => CredentialKind::SessionToken,
61        };
62
63        if provided != expected {
64            return Err(KagiError::UnsupportedAuthSurface {
65                surface,
66                credential: provided,
67                expected,
68            });
69        }
70
71        Ok(())
72    }
73}
74
75#[derive(Debug, Clone, Default)]
76pub struct KagiClientBuilder {
77    config: ClientConfig,
78    credentials: Option<Credentials>,
79    credential_conflict: Option<(CredentialKind, CredentialKind)>,
80}
81
82impl KagiClientBuilder {
83    pub fn config(mut self, config: ClientConfig) -> Self {
84        self.config = config;
85        self
86    }
87
88    pub fn bot_token(mut self, token: BotToken) -> Self {
89        self.set_credentials(Credentials::from(token));
90        self
91    }
92
93    pub fn session_token(mut self, token: SessionToken) -> Self {
94        self.set_credentials(Credentials::from(token));
95        self
96    }
97
98    pub fn credentials(mut self, credentials: Credentials) -> Self {
99        self.set_credentials(credentials);
100        self
101    }
102
103    pub fn build(self) -> Result<KagiClient, KagiError> {
104        if let Some((already_set, attempted)) = self.credential_conflict {
105            return Err(KagiError::ConflictingCredentialConfiguration {
106                already_set,
107                attempted,
108            });
109        }
110
111        let credentials =
112            self.credentials
113                .ok_or_else(|| KagiError::MissingCredentialConfiguration {
114                    reason: "set bot_token(...) or session_token(...) before build()".to_string(),
115                })?;
116
117        KagiClient::new(credentials, self.config)
118    }
119
120    fn set_credentials(&mut self, next_credentials: Credentials) {
121        let attempted = next_credentials.kind();
122
123        if let Some(current_credentials) = &self.credentials {
124            let already_set = current_credentials.kind();
125            if already_set != attempted {
126                self.credential_conflict = Some((already_set, attempted));
127                return;
128            }
129        }
130
131        self.credentials = Some(next_credentials);
132    }
133}