Skip to main content

surrealdb_types/value/
file.rs

1use serde::{Deserialize, Serialize};
2
3use crate::sql::{SqlFormat, ToSql};
4
5/// Represents a file reference in SurrealDB
6///
7/// A file reference points to a file stored in a bucket with a specific key.
8/// This is used for file storage and retrieval operations.
9
10#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
11pub struct File {
12	/// The bucket name where the file is stored
13	pub bucket: String,
14	/// The key/identifier for the file within the bucket
15	pub key: String,
16}
17
18impl File {
19	/// Create a new file pointer
20	pub fn new<B: Into<String>, K: Into<String>>(bucket: B, key: K) -> Self {
21		let bucket: String = bucket.into();
22		let key: String = key.into();
23
24		let key = if key.starts_with("/") {
25			key
26		} else {
27			format!("/{key}")
28		};
29
30		Self {
31			bucket,
32			key,
33		}
34	}
35
36	/// Get the bucket name
37	pub fn bucket(&self) -> &str {
38		&self.bucket
39	}
40
41	/// Get the key/identifier for the file within the bucket
42	/// The key always starts with a "/"
43	pub fn key(&self) -> &str {
44		&self.key
45	}
46}
47
48impl ToSql for crate::File {
49	fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
50		f.push_str("f\"");
51		f.push_str(&fmt_inner(&self.bucket, true));
52		f.push(':');
53		f.push_str(&fmt_inner(&self.key, false));
54		f.push('"');
55	}
56}
57
58fn fmt_inner(v: &str, escape_slash: bool) -> String {
59	v.chars()
60		.flat_map(|c| {
61			if c.is_ascii_alphanumeric()
62				|| matches!(c, '-' | '_' | '.')
63				|| (!escape_slash && c == '/')
64			{
65				vec![c]
66			} else {
67				vec!['\\', c]
68			}
69		})
70		.collect::<String>()
71}
72
73#[cfg(feature = "arbitrary")]
74mod arb {
75	use super::*;
76	impl<'a> arbitrary::Arbitrary<'a> for File {
77		fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
78			static CHAR: [u8; 56] = [
79				b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n',
80				b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'A', b'B',
81				b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P',
82				b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'_', b'-', b'.', b'/',
83			];
84
85			let mut bucket = String::new();
86			// Forward slash is not allowed in the bucket name so we exclude it by limiting the
87			// range.
88			bucket.push(CHAR[u.int_in_range(0u8..=54)? as usize] as char);
89			for _ in 0..u.arbitrary_len::<u8>()? {
90				bucket.push(CHAR[u.int_in_range(0u8..=54)? as usize] as char);
91			}
92			let mut key = "/".to_string();
93			for _ in 0..u.arbitrary_len::<u8>()? {
94				key.push(CHAR[u.int_in_range(0u8..=55)? as usize] as char);
95			}
96			Ok(File {
97				bucket,
98				key,
99			})
100		}
101	}
102}