Skip to main content

surrealdb_types/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![allow(clippy::mutable_key_type)]
4
5mod error;
6mod flatbuffers;
7mod hashmap;
8mod kind;
9mod notification;
10#[macro_use]
11mod sql;
12mod traits;
13pub(crate) mod utils;
14mod value;
15mod variables;
16
17pub use error::*;
18pub use flatbuffers::*;
19pub use hashmap::HashMap;
20pub use kind::*;
21pub use notification::*;
22pub use sql::{SqlFormat, ToSql, write_sql};
23// Re-export the derive macro
24pub use surrealdb_types_derive::{SurrealValue, kind};
25pub use traits::*;
26pub use utils::either::*;
27pub use value::*;
28pub use variables::*;
29
30/// Macro for creating a SurrealDB object.
31///
32/// This macro creates a SurrealDB object, which is a collection of key-value pairs.
33/// All values must implement the `SurrealValue` trait.
34///
35/// # Example
36///
37/// ```rust
38/// use surrealdb_types::object;
39///
40/// let obj = object! {
41///     name: "John".to_string(),
42///     "user-id": 12345,
43/// };
44/// ```
45#[macro_export]
46macro_rules! object {
47    // Base case: empty object
48    () => {
49        $crate::Object::new()
50    };
51
52    // Handle a list of field-value pairs (supports both identifiers and literals)
53    ($($key:tt: $value:expr),* $(,)?) => {
54        {
55            let mut obj = $crate::Object::new();
56            $(
57                $crate::object!(@insert obj, $key: $crate::SurrealValue::into_value($value));
58            )*
59            obj
60        }
61    };
62
63    // Internal helper to insert a single field - handles identifiers
64    (@insert $obj:expr, $key:ident: $value:expr) => {
65        $obj.insert(stringify!($key).to_string(), $value);
66    };
67
68    // Internal helper to insert a single field - handles string literals
69    (@insert $obj:expr, $key:literal: $value:expr) => {
70        $obj.insert($key.to_string(), $value);
71    };
72}
73
74/// Macro for creating a SurrealDB set.
75///
76/// This macro creates a SurrealDB set, which is a collection of values.
77/// All values must implement the `SurrealValue` trait.
78///
79/// # Example
80///
81/// ```rust
82/// use surrealdb_types::{set, Value};
83///
84/// let set = set! {
85///     Value::Number(1.into()),
86///     Value::Number(2.into()),
87///     Value::Number(3.into()),
88/// };
89/// ```
90#[macro_export]
91macro_rules! set {
92	($($value:expr),* $(,)?) => {
93		$crate::Set::from(vec![$($value),*])
94	};
95}
96
97/// Macro for creating a SurrealDB variables struct.
98///
99/// This macro creates a SurrealDB variables struct, which is a collection of key-value pairs.
100/// All values must implement the `SurrealValue` trait.
101///
102/// # Example
103///
104/// ```rust
105/// use surrealdb_types::vars;
106///
107/// let obj = vars! {
108///     name: "John".to_string(),
109///     "user-id": 12345,
110/// };
111/// ```
112///
113/// Uses the `object!` macro to create the variables struct.
114#[macro_export]
115macro_rules! vars {
116	($($key:tt: $value:expr),* $(,)?) => {
117		$crate::Variables::from($crate::object! {
118			$($key: $value),*
119		})
120	};
121}
122
123/// Macro for creating a SurrealDB array.
124///
125/// This macro creates a SurrealDB array, which is a collection of values.
126/// All values must implement the `SurrealValue` trait.
127///
128/// # Example
129///
130/// ```rust
131/// use surrealdb_types::array;
132///
133/// let arr = array![1, 2, 3];
134/// ```
135#[macro_export]
136macro_rules! array {
137    // Base case: empty array
138    [] => {
139        $crate::Array::new()
140    };
141
142    // Handle a list of values
143    [$($value:expr),* $(,)?] => {
144        {
145            let mut arr = $crate::Array::new();
146            $(
147                arr.push($crate::Value::from_t($value));
148            )*
149            arr
150        }
151    };
152}
153
154/// Example usage of the `object!` and `array!` macros:
155///
156/// ```rust
157/// use surrealdb_types::{object, array};
158///
159/// // Create an empty object
160/// let empty = object! {};
161///
162/// // Create an object with regular field names
163/// let person = object! {
164///     name: "John",
165///     age: 30,
166///     active: true,
167/// };
168///
169/// // Create an object with quoted field names (for fields with hyphens, spaces, etc.)
170/// let config = object! {
171///     "first-name": "John",
172///     "last-name": "Doe",
173///     "user-id": 12345,
174/// };
175///
176/// // Mix regular and quoted field names in the same object!
177/// let mixed = object! {
178///     name: "John",           // regular identifier
179///     "last-name": "Doe",     // quoted string
180///     age: 30,                // regular identifier
181///     "user-id": 12345,       // quoted string
182///     active: true,           // regular identifier
183/// };
184///
185/// // Create arrays
186/// let numbers = array! [1, 2, 3, 4, 5];
187/// let mixed = array! ["hello", 42, true];
188/// let nested = array! [1, person, "end"];  // can include objects and other values
189/// ```
190#[cfg(test)]
191mod tests {
192	use super::*;
193
194	#[test]
195	fn test_object_macro() {
196		// Test empty object
197		let empty = object! {};
198		assert_eq!(empty.0.len(), 0);
199
200		let _ = Value::None.is_bool();
201
202		// Test with regular field names
203		let obj1 = object! {
204			name: "John".to_string(),
205			age: 30,
206		};
207		assert_eq!(obj1.0.get("name"), Some(&Value::String("John".to_string())));
208		assert_eq!(obj1.0.get("age"), Some(&Value::Number(30.into())));
209
210		// Test with quoted field names
211		let obj2 = object! {
212			"first-name": "John".to_string(),
213			"last-name": "Doe".to_string(),
214		};
215		assert_eq!(obj2.0.get("first-name"), Some(&Value::String("John".to_string())));
216		assert_eq!(obj2.0.get("last-name"), Some(&Value::String("Doe".to_string())));
217
218		// Test mixed field names - this now works!
219		let obj3 = object! {
220			name: "John".to_string(),
221			"last-name": "Doe".to_string(),
222			age: 30,
223			"user-id": 12345,
224			active: true,
225		};
226		assert_eq!(obj3.0.get("name"), Some(&Value::String("John".to_string())));
227		assert_eq!(obj3.0.get("last-name"), Some(&Value::String("Doe".to_string())));
228		assert_eq!(obj3.0.get("age"), Some(&Value::Number(30.into())));
229		assert_eq!(obj3.0.get("user-id"), Some(&Value::Number(12345.into())));
230		assert_eq!(obj3.0.get("active"), Some(&Value::Bool(true)));
231
232		// Test with trailing comma
233		let obj4 = object! {
234			name: "Alice".to_string(),
235			"last-name": "Smith".to_string(),
236		};
237		assert_eq!(obj4.0.get("name"), Some(&Value::String("Alice".to_string())));
238		assert_eq!(obj4.0.get("last-name"), Some(&Value::String("Smith".to_string())));
239	}
240
241	#[test]
242	fn test_array_macro() {
243		// Test empty array
244		let empty = array![];
245		assert_eq!(empty.len(), 0);
246
247		// Test with simple values
248		let arr1 = array![1, 2, 3];
249		assert_eq!(arr1.len(), 3);
250		assert_eq!(arr1[0], Value::Number(1.into()));
251		assert_eq!(arr1[1], Value::Number(2.into()));
252		assert_eq!(arr1[2], Value::Number(3.into()));
253
254		// Test with mixed types
255		let arr2 = array!["hello".to_string(), 42, true];
256		assert_eq!(arr2.len(), 3);
257		assert_eq!(arr2[0], Value::String("hello".to_string()));
258		assert_eq!(arr2[1], Value::Number(42.into()));
259		assert_eq!(arr2[2], Value::Bool(true));
260
261		// Test with trailing comma
262		let arr3 = array!["a".to_string(), "b".to_string()];
263		assert_eq!(arr3.len(), 2);
264		assert_eq!(arr3[0], Value::String("a".to_string()));
265		assert_eq!(arr3[1], Value::String("b".to_string()));
266
267		// Test with nested objects and arrays
268		let nested_obj = object! { name: "John".to_string(), age: 30 };
269		let arr4 = array![1, nested_obj.clone(), "end".to_string()];
270		assert_eq!(arr4.len(), 3);
271		assert_eq!(arr4[0], Value::Number(1.into()));
272		assert_eq!(arr4[1], Value::Object(nested_obj));
273		assert_eq!(arr4[2], Value::String("end".to_string()));
274	}
275}