1use reqwest::Url;
7use reqwest_middleware::ClientWithMiddleware as ReqwestClient;
8
9use crate::Error;
10use crate::auth::{TokenSource, default_auth_url, default_credentials};
11use crate::client::endpoint;
12use crate::http::default_http_client;
13
14pub const DEFAULT_API_URL: &str = "https://api.spiraldb.dev";
16pub const DEFAULT_SPFS_URL: &str = "https://spfs.spiraldb.dev";
18
19#[derive(Clone)]
21pub struct Config {
22 pub(crate) api_url: Url,
23 pub(crate) auth_url: Url,
24 pub(crate) scandal_url: Option<Url>,
25 pub(crate) auth: TokenSource,
26 pub(crate) http: ReqwestClient,
27}
28
29impl Config {
30 pub async fn load() -> Result<Self, Error> {
32 Self::builder().load().await
33 }
34
35 pub fn builder() -> ConfigBuilder {
37 ConfigBuilder::default()
38 }
39
40 pub fn api_url(&self) -> &Url {
42 &self.api_url
43 }
44
45 pub fn scandal_url(&self) -> Option<&Url> {
47 self.scandal_url.as_ref()
48 }
49
50 pub fn token_source(&self) -> &TokenSource {
52 &self.auth
53 }
54}
55
56#[derive(Default)]
58pub struct ConfigBuilder {
59 api_url: Option<String>,
60 scandal_url: Option<String>,
61 auth_url: Option<String>,
62 auth: Option<TokenSource>,
63 http: Option<ReqwestClient>,
64 fallback: Option<TokenSource>,
65}
66
67impl ConfigBuilder {
68 pub fn api_url(mut self, url: impl Into<String>) -> Self {
70 self.api_url = Some(url.into());
71 self
72 }
73
74 pub fn scandal_url(mut self, url: impl Into<String>) -> Self {
76 self.scandal_url = Some(url.into());
77 self
78 }
79
80 pub fn auth_url(mut self, url: impl Into<String>) -> Self {
82 self.auth_url = Some(url.into());
83 self
84 }
85
86 pub fn token_source(mut self, provider: TokenSource) -> Self {
88 self.auth = Some(provider);
89 self
90 }
91
92 pub fn bearer_token(self, token: impl Into<String>) -> Self {
94 self.token_source(TokenSource::fixed(token.into().into()))
95 }
96
97 pub fn http_client(mut self, http: impl Into<ReqwestClient>) -> Self {
101 self.http = Some(http.into());
102 self
103 }
104
105 pub fn fallback_token_source(mut self, provider: TokenSource) -> Self {
109 self.fallback = Some(provider);
110 self
111 }
112
113 pub async fn load(self) -> Result<Config, Error> {
120 let api_explicit = self.api_url.is_some();
121 let mut api_url = endpoint(self.api_url.as_deref().unwrap_or(DEFAULT_API_URL))?;
122 let scandal_url = self.scandal_url.as_deref().map(endpoint).transpose()?;
123 let auth_url = match self.auth_url.as_deref() {
124 Some(url) => endpoint(url)?,
125 None => default_auth_url(),
126 };
127 let http = match self.http {
128 Some(http) => http,
129 None => default_http_client()?,
130 };
131 let auth = match self.auth {
132 Some(auth) => auth,
133 None => default_credentials(http.clone(), &api_url, &auth_url, self.fallback)?,
134 };
135 let token = auth.token().await?.ok_or(Error::CredentialsNotFound)?;
136 if !api_explicit && let Some(resource) = token.resource() {
137 api_url = endpoint(resource)?;
138 }
139 Ok(Config {
140 api_url,
141 auth_url,
142 scandal_url,
143 auth,
144 http,
145 })
146 }
147}