1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct RecordId {
8 pub table: String,
9 pub id: RecordIdValue,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(untagged)]
15pub enum RecordIdValue {
16 String(String),
17 Number(i64),
18 Object(Value),
19 Array(Vec<Value>),
20}
21
22impl RecordId {
23 pub fn new(table: impl Into<String>, id: impl Into<RecordIdValue>) -> Self {
25 Self {
26 table: table.into(),
27 id: id.into(),
28 }
29 }
30
31 pub fn string(table: impl Into<String>, id: impl Into<String>) -> Self {
33 Self::new(table, RecordIdValue::String(id.into()))
34 }
35
36 pub fn number(table: impl Into<String>, id: i64) -> Self {
38 Self::new(table, RecordIdValue::Number(id))
39 }
40
41 pub fn object(table: impl Into<String>, id: Value) -> Self {
43 Self::new(table, RecordIdValue::Object(id))
44 }
45
46 pub fn array(table: impl Into<String>, id: Vec<Value>) -> Self {
48 Self::new(table, RecordIdValue::Array(id))
49 }
50
51 pub fn parse(input: &str) -> Result<Self, RecordParseError> {
53 let parts: Vec<&str> = input.splitn(2, ':').collect();
54 if parts.len() != 2 {
55 return Err(RecordParseError::InvalidFormat);
56 }
57
58 let table = parts[0].to_string();
59 let id_str = parts[1];
60
61 if let Ok(num) = id_str.parse::<i64>() {
63 return Ok(Self::number(table, num));
64 }
65
66 if (id_str.starts_with('{') || id_str.starts_with('['))
68 && let Ok(value) = serde_json::from_str::<Value>(id_str)
69 {
70 match value {
71 Value::Object(_) => return Ok(Self::object(table, value)),
72 Value::Array(arr) => return Ok(Self::array(table, arr)),
73 _ => {}
74 }
75 }
76
77 Ok(Self::string(table, id_str))
79 }
80
81 pub fn to_surql(&self) -> String {
83 let table = escape_identifier(&self.table);
84 let id = match &self.id {
85 RecordIdValue::String(s) => escape_identifier(s),
86 RecordIdValue::Number(n) => n.to_string(),
87 RecordIdValue::Object(obj) => obj.to_string(),
88 RecordIdValue::Array(arr) => {
89 format!(
90 "[{}]",
91 arr.iter()
92 .map(|v| v.to_string())
93 .collect::<Vec<_>>()
94 .join(", ")
95 )
96 }
97 };
98 format!("{}:{}", table, id)
99 }
100
101 pub fn table(&self) -> &str {
103 &self.table
104 }
105
106 pub fn id(&self) -> &RecordIdValue {
108 &self.id
109 }
110}
111
112impl fmt::Display for RecordId {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "{}", self.to_surql())
115 }
116}
117
118impl From<&str> for RecordId {
119 fn from(s: &str) -> Self {
120 Self::parse(s).unwrap_or_else(|_| Self::string("unknown", s))
121 }
122}
123
124impl From<String> for RecordId {
125 fn from(s: String) -> Self {
126 Self::from(s.as_str())
127 }
128}
129
130impl From<RecordId> for Value {
131 fn from(record_id: RecordId) -> Self {
132 Value::String(record_id.to_string())
133 }
134}
135
136impl From<&RecordId> for Value {
137 fn from(record_id: &RecordId) -> Self {
138 Value::String(record_id.to_string())
139 }
140}
141
142impl From<String> for RecordIdValue {
143 fn from(s: String) -> Self {
144 RecordIdValue::String(s)
145 }
146}
147
148impl From<&str> for RecordIdValue {
149 fn from(s: &str) -> Self {
150 RecordIdValue::String(s.to_string())
151 }
152}
153
154impl From<i64> for RecordIdValue {
155 fn from(n: i64) -> Self {
156 RecordIdValue::Number(n)
157 }
158}
159
160impl From<Value> for RecordIdValue {
161 fn from(v: Value) -> Self {
162 match v {
163 Value::String(s) => RecordIdValue::String(s),
164 Value::Number(n) => {
165 if let Some(i) = n.as_i64() {
166 RecordIdValue::Number(i)
167 } else {
168 RecordIdValue::Object(Value::Number(n))
169 }
170 }
171 Value::Array(arr) => RecordIdValue::Array(arr),
172 other => RecordIdValue::Object(other),
173 }
174 }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
179pub struct Table {
180 pub name: String,
181}
182
183impl Table {
184 pub fn new(name: impl Into<String>) -> Self {
186 Self { name: name.into() }
187 }
188
189 pub fn name(&self) -> &str {
191 &self.name
192 }
193
194 pub fn to_surql(&self) -> String {
196 escape_identifier(&self.name)
197 }
198}
199
200impl fmt::Display for Table {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 write!(f, "{}", self.to_surql())
203 }
204}
205
206impl From<&str> for Table {
207 fn from(s: &str) -> Self {
208 Self::new(s)
209 }
210}
211
212impl From<String> for Table {
213 fn from(s: String) -> Self {
214 Self::new(s)
215 }
216}
217
218impl From<Table> for Value {
219 fn from(table: Table) -> Self {
220 Value::String(table.name)
221 }
222}
223
224impl From<&Table> for Value {
225 fn from(table: &Table) -> Self {
226 Value::String(table.name.clone())
227 }
228}
229
230#[derive(Debug, Clone, PartialEq)]
232pub struct RecordRange {
233 pub table: String,
234 pub start: Option<RecordIdValue>,
235 pub end: Option<RecordIdValue>,
236 pub start_inclusive: bool,
237 pub end_inclusive: bool,
238}
239
240impl RecordRange {
241 pub fn new(table: impl Into<String>) -> Self {
243 Self {
244 table: table.into(),
245 start: None,
246 end: None,
247 start_inclusive: true,
248 end_inclusive: true,
249 }
250 }
251
252 pub fn start(mut self, start: impl Into<RecordIdValue>, inclusive: bool) -> Self {
254 self.start = Some(start.into());
255 self.start_inclusive = inclusive;
256 self
257 }
258
259 pub fn end(mut self, end: impl Into<RecordIdValue>, inclusive: bool) -> Self {
261 self.end = Some(end.into());
262 self.end_inclusive = inclusive;
263 self
264 }
265
266 pub fn to_surql(&self) -> String {
268 let table = escape_identifier(&self.table);
269
270 let start_str = match &self.start {
271 Some(start) => {
272 let start_val = match start {
273 RecordIdValue::String(s) => escape_identifier(s),
274 RecordIdValue::Number(n) => n.to_string(),
275 RecordIdValue::Object(obj) => obj.to_string(),
276 RecordIdValue::Array(arr) => {
277 format!(
278 "[{}]",
279 arr.iter()
280 .map(|v| v.to_string())
281 .collect::<Vec<_>>()
282 .join(", ")
283 )
284 }
285 };
286 if self.start_inclusive {
287 start_val
288 } else {
289 format!(">{}", start_val)
290 }
291 }
292 None => String::new(),
293 };
294
295 let end_str = match &self.end {
296 Some(end) => {
297 let end_val = match end {
298 RecordIdValue::String(s) => escape_identifier(s),
299 RecordIdValue::Number(n) => n.to_string(),
300 RecordIdValue::Object(obj) => obj.to_string(),
301 RecordIdValue::Array(arr) => {
302 format!(
303 "[{}]",
304 arr.iter()
305 .map(|v| v.to_string())
306 .collect::<Vec<_>>()
307 .join(", ")
308 )
309 }
310 };
311 if self.end_inclusive {
312 end_val
313 } else {
314 format!("={}", end_val)
315 }
316 }
317 None => String::new(),
318 };
319
320 if start_str.is_empty() && end_str.is_empty() {
321 table
322 } else {
323 format!("{}:{}..{}", table, start_str, end_str)
324 }
325 }
326}
327
328impl fmt::Display for RecordRange {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 write!(f, "{}", self.to_surql())
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
336pub enum RecordParseError {
337 InvalidFormat,
338 InvalidId,
339}
340
341impl fmt::Display for RecordParseError {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 match self {
344 RecordParseError::InvalidFormat => write!(f, "Invalid record ID format"),
345 RecordParseError::InvalidId => write!(f, "Invalid record ID value"),
346 }
347 }
348}
349
350impl std::error::Error for RecordParseError {}
351
352const RESERVED_KEYWORDS: &[&str] = &[
355 "DEFINE", "CREATE", "SELECT", "UPDATE", "DELETE", "FROM", "RETURN", "WHERE", "SET", "ONLY",
356 "TABLE",
357];
358
359pub fn escape_identifier(ident: &str) -> String {
374 if ident.is_empty() {
375 return "⟨⟩".to_string();
376 }
377
378 if ident.parse::<i64>().is_ok() || ident.parse::<f64>().is_ok() {
380 return format!("⟨{}⟩", ident);
381 }
382
383 let is_reserved = RESERVED_KEYWORDS.contains(&ident.to_uppercase().as_str());
384
385 if is_reserved
386 || ident.chars().next().unwrap().is_ascii_digit()
387 || ident.chars().any(|c| !c.is_alphanumeric() && c != '_')
388 {
389 let escaped = ident.replace('\\', "\\\\").replace('⟩', "\\u{27E9}");
391 return format!("⟨{}⟩", escaped);
392 }
393
394 ident.to_string()
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_record_id_creation() {
403 let record = RecordId::string("user", "john");
404 assert_eq!(record.table, "user");
405 assert_eq!(record.id, RecordIdValue::String("john".to_string()));
406 }
407
408 #[test]
409 fn test_record_id_parsing() {
410 let record = RecordId::parse("user:123").unwrap();
411 assert_eq!(record.table, "user");
412 assert_eq!(record.id, RecordIdValue::Number(123));
413
414 let record = RecordId::parse("user:john").unwrap();
415 assert_eq!(record.table, "user");
416 assert_eq!(record.id, RecordIdValue::String("john".to_string()));
417 }
418
419 #[test]
420 fn test_record_id_surql() {
421 let record = RecordId::string("user", "john");
422 assert_eq!(record.to_surql(), "user:john");
423
424 let record = RecordId::number("user", 123);
425 assert_eq!(record.to_surql(), "user:123");
426 }
427
428 #[test]
429 fn test_table_creation() {
430 let table = Table::new("users");
431 assert_eq!(table.name(), "users");
432 assert_eq!(table.to_surql(), "users");
433 }
434
435 #[test]
436 fn test_escape_identifier() {
437 assert_eq!(escape_identifier("normal"), "normal");
438 assert_eq!(escape_identifier("123"), "⟨123⟩");
439 assert_eq!(escape_identifier("with-dash"), "⟨with-dash⟩");
440 assert_eq!(escape_identifier(""), "⟨⟩");
441 assert_eq!(escape_identifier("SELECT"), "⟨SELECT⟩");
443 assert_eq!(escape_identifier("from"), "⟨from⟩");
444 assert_eq!(escape_identifier("a⟩b"), "⟨a\\u{27E9}b⟩");
448 assert_eq!(escape_identifier("a\\b"), "⟨a\\\\b⟩");
452 assert_eq!(escape_identifier("a\\⟩b"), "⟨a\\\\\\u{27E9}b⟩");
454 }
455
456 #[test]
457 fn test_record_range() {
458 let range = RecordRange::new("user").start("a", true).end("z", false);
459
460 assert_eq!(range.to_surql(), "user:a..=z");
461 }
462
463 #[test]
464 fn test_conversions() {
465 let record = RecordId::string("user", "john");
466 let value: Value = record.into();
467 assert_eq!(value, Value::String("user:john".to_string()));
468
469 let table = Table::new("users");
470 let value: Value = table.into();
471 assert_eq!(value, Value::String("users".to_string()));
472 }
473}