Skip to main content

surreal_client/
params.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Parameters for connecting to SurrealDB
5#[derive(Default, Debug, Clone, Serialize, Deserialize)]
6pub struct ConnectParams {
7    /// Namespace to use
8    pub namespace: Option<String>,
9
10    /// Database to use
11    pub database: Option<String>,
12
13    /// Whether to check SurrealDB version compatibility
14    pub version_check: Option<bool>,
15}
16
17impl ConnectParams {
18    /// Create new connection parameters
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Set the namespace
24    pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
25        self.namespace = Some(namespace.into());
26        self
27    }
28
29    /// Set the database
30    pub fn with_database(mut self, database: impl Into<String>) -> Self {
31        self.database = Some(database.into());
32        self
33    }
34
35    /// Enable or disable version checking
36    pub fn with_version_check(mut self, check: bool) -> Self {
37        self.version_check = Some(check);
38        self
39    }
40}
41
42/// Parameters for authentication
43#[derive(Default, Debug, Clone, Serialize, Deserialize)]
44pub struct SigninParams {
45    /// Username for root/namespace/database authentication
46    pub user: Option<String>,
47
48    /// Password for root/namespace/database authentication
49    pub pass: Option<String>,
50
51    /// Namespace for authentication
52    pub namespace: Option<String>,
53
54    /// Database for authentication
55    pub database: Option<String>,
56
57    /// Scope for record-level authentication (legacy)
58    pub scope: Option<String>,
59
60    /// Access method for record-level authentication (v2.0+)
61    pub access: Option<String>,
62
63    /// Additional authentication variables
64    #[serde(flatten)]
65    pub vars: HashMap<String, serde_json::Value>,
66}
67
68impl SigninParams {
69    /// Create new signin parameters
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Create root user authentication
75    pub fn root(user: impl Into<String>, pass: impl Into<String>) -> Self {
76        Self {
77            user: Some(user.into()),
78            pass: Some(pass.into()),
79            ..Default::default()
80        }
81    }
82
83    /// Create namespace user authentication
84    pub fn namespace(
85        user: impl Into<String>,
86        pass: impl Into<String>,
87        namespace: impl Into<String>,
88    ) -> Self {
89        Self {
90            user: Some(user.into()),
91            pass: Some(pass.into()),
92            namespace: Some(namespace.into()),
93            ..Default::default()
94        }
95    }
96
97    /// Create database user authentication
98    pub fn database(
99        user: impl Into<String>,
100        pass: impl Into<String>,
101        namespace: impl Into<String>,
102        database: impl Into<String>,
103    ) -> Self {
104        Self {
105            user: Some(user.into()),
106            pass: Some(pass.into()),
107            namespace: Some(namespace.into()),
108            database: Some(database.into()),
109            ..Default::default()
110        }
111    }
112
113    /// Create scope-based authentication (legacy)
114    pub fn scope(
115        namespace: impl Into<String>,
116        database: impl Into<String>,
117        scope: impl Into<String>,
118    ) -> Self {
119        Self {
120            namespace: Some(namespace.into()),
121            database: Some(database.into()),
122            scope: Some(scope.into()),
123            ..Default::default()
124        }
125    }
126
127    /// Create access-based authentication (v2.0+)
128    pub fn access(
129        namespace: impl Into<String>,
130        database: impl Into<String>,
131        access: impl Into<String>,
132    ) -> Self {
133        Self {
134            namespace: Some(namespace.into()),
135            database: Some(database.into()),
136            access: Some(access.into()),
137            ..Default::default()
138        }
139    }
140
141    /// Add an authentication variable
142    pub fn with_var(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
143        self.vars.insert(key.into(), value);
144        self
145    }
146
147    /// Convert to the format expected by SurrealDB RPC
148    pub fn to_rpc_params(&self) -> HashMap<String, serde_json::Value> {
149        let mut params = HashMap::new();
150
151        // Add basic auth fields
152        if let Some(ref user) = self.user {
153            params.insert("user".to_string(), serde_json::Value::String(user.clone()));
154        }
155        if let Some(ref pass) = self.pass {
156            params.insert("pass".to_string(), serde_json::Value::String(pass.clone()));
157        }
158
159        // Add scope/namespace/database with SurrealDB abbreviations
160        if let Some(ref namespace) = self.namespace {
161            params.insert(
162                "NS".to_string(),
163                serde_json::Value::String(namespace.clone()),
164            );
165        }
166        if let Some(ref database) = self.database {
167            params.insert(
168                "DB".to_string(),
169                serde_json::Value::String(database.clone()),
170            );
171        }
172        if let Some(ref scope) = self.scope {
173            params.insert("SC".to_string(), serde_json::Value::String(scope.clone()));
174        }
175        if let Some(ref access) = self.access {
176            params.insert("AC".to_string(), serde_json::Value::String(access.clone()));
177        }
178
179        // Add custom variables
180        for (key, value) in &self.vars {
181            params.insert(key.clone(), value.clone());
182        }
183
184        params
185    }
186}
187
188/// Parameters for signup operations
189pub type SignupParams = SigninParams;
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_connect_params_builder() {
197        let params = ConnectParams::new()
198            .with_namespace("test_ns")
199            .with_database("test_db")
200            .with_version_check(false);
201
202        assert_eq!(params.namespace, Some("test_ns".to_string()));
203        assert_eq!(params.database, Some("test_db".to_string()));
204        assert_eq!(params.version_check, Some(false));
205    }
206
207    #[test]
208    fn test_signin_params_root() {
209        let params = SigninParams::root("admin", "password123");
210        assert_eq!(params.user, Some("admin".to_string()));
211        assert_eq!(params.pass, Some("password123".to_string()));
212        assert!(params.namespace.is_none());
213    }
214
215    #[test]
216    fn test_signin_params_to_rpc() {
217        let params = SigninParams::root("admin", "pass")
218            .with_var("custom", serde_json::Value::String("value".to_string()));
219
220        let rpc_params = params.to_rpc_params();
221
222        assert_eq!(
223            rpc_params.get("user"),
224            Some(&serde_json::Value::String("admin".to_string()))
225        );
226        assert_eq!(
227            rpc_params.get("pass"),
228            Some(&serde_json::Value::String("pass".to_string()))
229        );
230        assert_eq!(
231            rpc_params.get("custom"),
232            Some(&serde_json::Value::String("value".to_string()))
233        );
234    }
235
236    #[test]
237    fn test_signin_params_namespace_abbreviations() {
238        let params = SigninParams::scope("my_ns", "my_db", "user_scope");
239        let rpc_params = params.to_rpc_params();
240
241        assert_eq!(
242            rpc_params.get("NS"),
243            Some(&serde_json::Value::String("my_ns".to_string()))
244        );
245        assert_eq!(
246            rpc_params.get("DB"),
247            Some(&serde_json::Value::String("my_db".to_string()))
248        );
249        assert_eq!(
250            rpc_params.get("SC"),
251            Some(&serde_json::Value::String("user_scope".to_string()))
252        );
253    }
254}