1use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11use serde_json::{Map, Value};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum AuthType {
17 Root,
19 Namespace,
21 Database,
23 Scope,
25}
26
27impl std::fmt::Display for AuthType {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 let s = match self {
30 Self::Root => "root",
31 Self::Namespace => "namespace",
32 Self::Database => "database",
33 Self::Scope => "scope",
34 };
35 f.write_str(s)
36 }
37}
38
39pub trait Credentials {
42 fn auth_type(&self) -> AuthType;
44
45 fn to_signin_payload(&self) -> Map<String, Value>;
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct RootCredentials {
52 pub username: String,
54 #[serde(skip_serializing_if = "Option::is_none", default)]
56 pub password: Option<String>,
57}
58
59impl RootCredentials {
60 pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
62 Self {
63 username: username.into(),
64 password: Some(password.into()),
65 }
66 }
67}
68
69impl Credentials for RootCredentials {
70 fn auth_type(&self) -> AuthType {
71 AuthType::Root
72 }
73
74 fn to_signin_payload(&self) -> Map<String, Value> {
75 let mut m = Map::new();
76 m.insert("username".into(), Value::String(self.username.clone()));
77 if let Some(p) = &self.password {
78 m.insert("password".into(), Value::String(p.clone()));
79 }
80 m
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86pub struct NamespaceCredentials {
87 pub namespace: String,
89 pub username: String,
91 #[serde(skip_serializing_if = "Option::is_none", default)]
93 pub password: Option<String>,
94}
95
96impl NamespaceCredentials {
97 pub fn new(
99 namespace: impl Into<String>,
100 username: impl Into<String>,
101 password: impl Into<String>,
102 ) -> Self {
103 Self {
104 namespace: namespace.into(),
105 username: username.into(),
106 password: Some(password.into()),
107 }
108 }
109}
110
111impl Credentials for NamespaceCredentials {
112 fn auth_type(&self) -> AuthType {
113 AuthType::Namespace
114 }
115
116 fn to_signin_payload(&self) -> Map<String, Value> {
117 let mut m = Map::new();
118 m.insert("namespace".into(), Value::String(self.namespace.clone()));
119 m.insert("username".into(), Value::String(self.username.clone()));
120 if let Some(p) = &self.password {
121 m.insert("password".into(), Value::String(p.clone()));
122 }
123 m
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub struct DatabaseCredentials {
130 pub namespace: String,
132 pub database: String,
134 pub username: String,
136 #[serde(skip_serializing_if = "Option::is_none", default)]
138 pub password: Option<String>,
139}
140
141impl DatabaseCredentials {
142 pub fn new(
144 namespace: impl Into<String>,
145 database: impl Into<String>,
146 username: impl Into<String>,
147 password: impl Into<String>,
148 ) -> Self {
149 Self {
150 namespace: namespace.into(),
151 database: database.into(),
152 username: username.into(),
153 password: Some(password.into()),
154 }
155 }
156}
157
158impl Credentials for DatabaseCredentials {
159 fn auth_type(&self) -> AuthType {
160 AuthType::Database
161 }
162
163 fn to_signin_payload(&self) -> Map<String, Value> {
164 let mut m = Map::new();
165 m.insert("namespace".into(), Value::String(self.namespace.clone()));
166 m.insert("database".into(), Value::String(self.database.clone()));
167 m.insert("username".into(), Value::String(self.username.clone()));
168 if let Some(p) = &self.password {
169 m.insert("password".into(), Value::String(p.clone()));
170 }
171 m
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct ScopeCredentials {
182 pub namespace: String,
184 pub database: String,
186 pub access: String,
188 #[serde(default)]
190 pub variables: BTreeMap<String, Value>,
191}
192
193impl ScopeCredentials {
194 pub fn new(
196 namespace: impl Into<String>,
197 database: impl Into<String>,
198 access: impl Into<String>,
199 ) -> Self {
200 Self {
201 namespace: namespace.into(),
202 database: database.into(),
203 access: access.into(),
204 variables: BTreeMap::new(),
205 }
206 }
207
208 pub fn with(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
210 self.variables.insert(key.into(), value.into());
211 self
212 }
213}
214
215impl Credentials for ScopeCredentials {
216 fn auth_type(&self) -> AuthType {
217 AuthType::Scope
218 }
219
220 fn to_signin_payload(&self) -> Map<String, Value> {
221 let mut m = Map::new();
222 m.insert("namespace".into(), Value::String(self.namespace.clone()));
223 m.insert("database".into(), Value::String(self.database.clone()));
224 m.insert("access".into(), Value::String(self.access.clone()));
225 for (k, v) in &self.variables {
226 m.insert(k.clone(), v.clone());
227 }
228 m
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234pub struct TokenAuth {
235 pub token: String,
237}
238
239impl TokenAuth {
240 pub fn new(token: impl Into<String>) -> Self {
242 Self {
243 token: token.into(),
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use serde_json::json;
252
253 #[test]
254 fn auth_type_display() {
255 assert_eq!(AuthType::Root.to_string(), "root");
256 assert_eq!(AuthType::Namespace.to_string(), "namespace");
257 assert_eq!(AuthType::Database.to_string(), "database");
258 assert_eq!(AuthType::Scope.to_string(), "scope");
259 }
260
261 #[test]
262 fn root_payload() {
263 let creds = RootCredentials::new("root", "secret");
264 let p = creds.to_signin_payload();
265 assert_eq!(p.get("username").unwrap(), &json!("root"));
266 assert_eq!(p.get("password").unwrap(), &json!("secret"));
267 assert_eq!(creds.auth_type(), AuthType::Root);
268 }
269
270 #[test]
271 fn namespace_payload() {
272 let creds = NamespaceCredentials::new("prod", "u", "p");
273 let p = creds.to_signin_payload();
274 assert_eq!(p.get("namespace").unwrap(), &json!("prod"));
275 assert_eq!(p.get("username").unwrap(), &json!("u"));
276 assert_eq!(p.get("password").unwrap(), &json!("p"));
277 assert_eq!(creds.auth_type(), AuthType::Namespace);
278 }
279
280 #[test]
281 fn database_payload() {
282 let creds = DatabaseCredentials::new("prod", "app", "u", "p");
283 let p = creds.to_signin_payload();
284 assert_eq!(p.get("namespace").unwrap(), &json!("prod"));
285 assert_eq!(p.get("database").unwrap(), &json!("app"));
286 assert_eq!(p.get("username").unwrap(), &json!("u"));
287 assert_eq!(p.get("password").unwrap(), &json!("p"));
288 assert_eq!(creds.auth_type(), AuthType::Database);
289 }
290
291 #[test]
292 fn scope_payload_flattens_variables() {
293 let creds = ScopeCredentials::new("prod", "app", "user")
294 .with("email", "a@example.com")
295 .with("password", "secret");
296 let p = creds.to_signin_payload();
297 assert_eq!(p.get("namespace").unwrap(), &json!("prod"));
298 assert_eq!(p.get("database").unwrap(), &json!("app"));
299 assert_eq!(p.get("access").unwrap(), &json!("user"));
300 assert_eq!(p.get("email").unwrap(), &json!("a@example.com"));
301 assert_eq!(p.get("password").unwrap(), &json!("secret"));
302 assert_eq!(creds.auth_type(), AuthType::Scope);
303 }
304
305 #[test]
306 fn token_auth_debug_does_not_panic() {
307 let t = TokenAuth::new("eyJhbGciOiJIUzI1NiJ9.abc");
308 let _ = format!("{t:?}");
311 }
312
313 #[test]
314 fn auth_type_serde() {
315 let json = serde_json::to_string(&AuthType::Root).unwrap();
316 assert_eq!(json, "\"root\"");
317 let back: AuthType = serde_json::from_str("\"scope\"").unwrap();
318 assert_eq!(back, AuthType::Scope);
319 }
320
321 #[test]
322 fn root_credentials_skip_missing_password() {
323 let creds = RootCredentials {
324 username: "root".into(),
325 password: None,
326 };
327 let json = serde_json::to_string(&creds).unwrap();
328 assert!(!json.contains("password"));
329 }
330}