Skip to main content

surrealdb_core/dbs/
response.rs

1use std::fmt;
2use std::time::Duration;
3
4use revision::revisioned;
5use serde::{Deserialize, Serialize};
6use surrealdb_types::{
7	Error as TypesError, ErrorDetails, Kind, SerializationError, SurrealValue, Value, kind, object,
8};
9use web_time::Instant;
10
11use crate::expr::TopLevelExpr;
12
13#[revisioned(revision = 1)]
14#[derive(
15	Debug,
16	Copy,
17	Clone,
18	Default,
19	PartialEq,
20	Eq,
21	PartialOrd,
22	Ord,
23	Hash,
24	Serialize,
25	Deserialize,
26	SurrealValue,
27)]
28#[surreal(crate = "surrealdb_types")]
29#[surreal(untagged, lowercase)]
30#[serde(rename_all = "lowercase")]
31pub enum QueryType {
32	// Any kind of query
33	#[default]
34	#[surreal(value = none)]
35	Other,
36	// Indicates that the response live query id must be tracked
37	Live,
38	// Indicates that the live query should be removed from tracking
39	Kill,
40}
41
42impl fmt::Display for QueryType {
43	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44		match self {
45			QueryType::Other => "other".fmt(f),
46			QueryType::Live => "live".fmt(f),
47			QueryType::Kill => "kill".fmt(f),
48		}
49	}
50}
51
52impl QueryType {
53	/// Returns the query type for the given toplevel expression.
54	pub(crate) fn for_toplevel_expr(expr: &TopLevelExpr) -> Self {
55		match expr {
56			TopLevelExpr::Live(_) => QueryType::Live,
57			TopLevelExpr::Kill(_) => QueryType::Kill,
58			_ => QueryType::Other,
59		}
60	}
61}
62
63/// The return value when running a query set on the database.
64#[derive(Debug, Clone)]
65pub struct QueryResult {
66	pub time: Duration,
67	pub result: Result<Value, TypesError>,
68	// Record the query type in case processing the response is necessary (such as tracking live
69	// queries).
70	pub query_type: QueryType,
71}
72
73impl QueryResult {
74	/// Retrieve the response as a normal result
75	pub fn output(self) -> Result<Value, TypesError> {
76		self.result
77	}
78}
79
80/// Serialise this error into the query-result wire shape: `result` (message string), optional
81/// `kind` and `details`. Does not include `code`. Used for query result responses
82/// for backwards compatibility (old clients expect `result` to be the message string).
83fn into_query_result_value(error: &TypesError) -> Value {
84	let mut details = error.details().clone().into_value();
85
86	if let Value::Object(ref mut obj) = details {
87		obj.insert("result", error.message().to_string());
88		details
89	} else {
90		warn!("ErrorDetails::into_value() did not produce an Object; this is a bug");
91		Value::Object(object! {
92			result: "Failed to serialise error",
93			kind: "Internal",
94		})
95	}
96}
97
98/// Deserialise an error from the query-result wire shape. Requires `result` (message string).
99/// The remaining fields (`kind`, optional `details`) are the flattened `ErrorDetails`.
100fn from_query_result_value(value: Value) -> Result<TypesError, TypesError> {
101	let Value::Object(mut map) = value else {
102		return Err(TypesError::serialization(
103			"Expected object for query result error".to_string(),
104			SerializationError::Deserialization,
105		));
106	};
107	let message = map
108		.remove("result")
109		.ok_or_else(|| {
110			TypesError::serialization(
111				"Missing result (message) for query result error".to_string(),
112				SerializationError::Deserialization,
113			)
114		})?
115		.into_string()
116		.map_err(|e| {
117			TypesError::serialization(e.to_string(), SerializationError::Deserialization)
118		})?;
119	let details = ErrorDetails::from_value(Value::Object(map)).unwrap_or(ErrorDetails::Internal);
120	Ok(TypesError::from_details(message, details))
121}
122
123impl SurrealValue for QueryResult {
124	fn kind_of() -> Kind {
125		kind!(
126			{
127				status: "OK",
128				time: string,
129				result: any,
130				query_type: (QueryType::kind_of()),
131			} | {
132				status: "ERR",
133				time: string,
134				result: string,
135				kind: string,
136				details: any,
137				query_type: (QueryType::kind_of()),
138			}
139		)
140	}
141
142	fn is_value(value: &Value) -> bool {
143		value.is_object_and(|map| {
144			map.get("status").is_some_and(Status::is_value)
145				&& map.get("time").is_some_and(Value::is_string)
146				&& map.get("result").is_some()
147				&& map.get("type").is_some_and(QueryType::is_value)
148		})
149	}
150
151	fn into_value(self) -> Value {
152		let mut map = object! {
153			status: Status::from(&self.result).into_value(),
154			time: format!("{:?}", self.time).into_value(),
155			type: self.query_type.into_value(),
156		};
157		match self.result {
158			Ok(v) => {
159				map.insert("result", v);
160			}
161			Err(e) => {
162				let err_val = into_query_result_value(&e);
163				if let Value::Object(err_obj) = err_val {
164					for (k, v) in err_obj.into_inner() {
165						map.insert(k, v);
166					}
167				}
168			}
169		}
170		Value::Object(map)
171	}
172
173	fn from_value(value: Value) -> Result<Self, TypesError> {
174		// Assert required fields
175		let Value::Object(mut map) = value else {
176			return Err(TypesError::serialization(
177				"Expected object for QueryResult".to_string(),
178				SerializationError::Deserialization,
179			));
180		};
181		let Some(status) = map.remove("status") else {
182			return Err(TypesError::serialization(
183				"Expected status for QueryResult".to_string(),
184				SerializationError::Deserialization,
185			));
186		};
187		let Some(time) = map.remove("time") else {
188			return Err(TypesError::serialization(
189				"Expected time for QueryResult".to_string(),
190				SerializationError::Deserialization,
191			));
192		};
193		let Some(result) = map.remove("result") else {
194			return Err(TypesError::serialization(
195				"Expected result for QueryResult".to_string(),
196				SerializationError::Deserialization,
197			));
198		};
199
200		// Grab status, query type and time
201		let status = Status::from_value(status)?;
202		let query_type =
203			map.remove("type").map(QueryType::from_value).transpose()?.unwrap_or_default();
204
205		let time = humantime::parse_duration(&time.into_string().map_err(|e| {
206			TypesError::serialization(e.to_string(), SerializationError::Deserialization)
207		})?)
208		.map_err(|e| {
209			TypesError::serialization(e.to_string(), SerializationError::Deserialization)
210		})?;
211
212		// Grab result based on status
213
214		let result = match status {
215			Status::Ok => Ok(Value::from_value(result)?),
216			Status::Err => {
217				map.insert("result".to_string(), result);
218				Err(from_query_result_value(Value::Object(map))?)
219			}
220		};
221
222		Ok(QueryResult {
223			time,
224			result,
225			query_type,
226		})
227	}
228}
229
230pub struct QueryResultBuilder {
231	start_time: Instant,
232	result: Result<Value, TypesError>,
233	query_type: QueryType,
234}
235
236impl QueryResultBuilder {
237	pub fn started_now() -> Self {
238		Self {
239			start_time: Instant::now(),
240			result: Ok(Value::None),
241			query_type: QueryType::Other,
242		}
243	}
244
245	pub fn instant_none() -> QueryResult {
246		QueryResult {
247			time: Duration::ZERO,
248			result: Ok(Value::None),
249			query_type: QueryType::Other,
250		}
251	}
252
253	pub fn with_result(mut self, result: Result<Value, TypesError>) -> Self {
254		self.result = result;
255		self
256	}
257
258	pub fn with_query_type(mut self, query_type: QueryType) -> Self {
259		self.query_type = query_type;
260		self
261	}
262
263	pub fn finish(self) -> QueryResult {
264		QueryResult {
265			time: self.start_time.elapsed(),
266			result: self.result,
267			query_type: self.query_type,
268		}
269	}
270
271	pub fn finish_with_result(self, result: Result<Value, TypesError>) -> QueryResult {
272		QueryResult {
273			time: self.start_time.elapsed(),
274			result,
275			query_type: self.query_type,
276		}
277	}
278}
279
280#[revisioned(revision = 1)]
281#[derive(Debug, Serialize, Deserialize, SurrealValue)]
282#[surreal(crate = "surrealdb_types")]
283#[serde(rename_all = "UPPERCASE")]
284#[surreal(untagged, uppercase)]
285pub enum Status {
286	Ok,
287	Err,
288}
289
290impl Status {
291	pub fn is_ok(&self) -> bool {
292		matches!(self, Status::Ok)
293	}
294
295	pub fn is_err(&self) -> bool {
296		matches!(self, Status::Err)
297	}
298}
299
300impl<'a, T, E> From<&'a Result<T, E>> for Status {
301	fn from(result: &'a Result<T, E>) -> Self {
302		match result {
303			Ok(_) => Status::Ok,
304			Err(_) => Status::Err,
305		}
306	}
307}
308
309impl Serialize for QueryResult {
310	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
311	where
312		S: serde::Serializer,
313	{
314		self.clone().into_value().serialize(serializer)
315	}
316}
317
318impl<'de> Deserialize<'de> for QueryResult {
319	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
320	where
321		D: serde::Deserializer<'de>,
322	{
323		// Deserialize as a Value first, then convert
324		let value = Value::deserialize(deserializer)?;
325		QueryResult::from_value(value).map_err(serde::de::Error::custom)
326	}
327}
328
329#[cfg(test)]
330mod tests {
331	use surrealdb_types::{AuthError, NotAllowedError, NotFoundError, ValidationError};
332
333	use super::*;
334
335	fn error_query_result(error: TypesError) -> QueryResult {
336		QueryResult {
337			time: Duration::from_millis(42),
338			result: Err(error),
339			query_type: QueryType::Other,
340		}
341	}
342
343	/// Verify that the `kind` field in a serialized error QueryResult is NOT
344	/// duplicated inside `details`. This was a bug where `into_query_result_value`
345	/// manually added `kind` and then also included it inside `details` via
346	/// `ErrorDetails::into_value()`.
347	#[test]
348	fn query_result_error_no_kind_duplication() {
349		let err = TypesError::not_allowed("Token expired".into(), AuthError::TokenExpired);
350		let qr = error_query_result(err);
351		let val = qr.into_value();
352		let Value::Object(ref obj) = val else {
353			panic!("Expected object");
354		};
355
356		assert_eq!(obj.get("status"), Some(&Value::String("ERR".to_string())));
357		assert_eq!(obj.get("result"), Some(&Value::String("Token expired".to_string())));
358		assert_eq!(obj.get("kind"), Some(&Value::String("NotAllowed".to_string())));
359
360		// `details` must contain the inner NotAllowedError, NOT a duplicate of ErrorDetails
361		let Some(Value::Object(details)) = obj.get("details") else {
362			panic!("Expected details object");
363		};
364		assert_eq!(
365			details.get("kind"),
366			Some(&Value::String("Auth".to_string())),
367			"details.kind should be the inner variant, not a duplicate of the top-level kind"
368		);
369	}
370
371	#[test]
372	fn query_result_error_round_trip_with_details() {
373		let err = TypesError::not_allowed("Token expired".into(), AuthError::TokenExpired);
374		let qr = error_query_result(err);
375		let val = qr.into_value();
376		let parsed = QueryResult::from_value(val).expect("round-trip should succeed");
377
378		let err = parsed.result.unwrap_err();
379		assert!(err.is_not_allowed());
380		assert_eq!(err.message(), "Token expired");
381		assert!(matches!(
382			err.not_allowed_details(),
383			Some(NotAllowedError::Auth(AuthError::TokenExpired))
384		));
385	}
386
387	#[test]
388	fn query_result_error_round_trip_nested_struct_details() {
389		let err = TypesError::not_found(
390			"Table not found".into(),
391			NotFoundError::Table {
392				name: "users".into(),
393			},
394		);
395		let qr = error_query_result(err);
396		let val = qr.into_value();
397		let parsed = QueryResult::from_value(val).expect("round-trip should succeed");
398
399		let err = parsed.result.unwrap_err();
400		assert!(err.is_not_found());
401		assert!(matches!(
402			err.not_found_details(),
403			Some(NotFoundError::Table { name }) if name == "users"
404		));
405	}
406
407	#[test]
408	fn query_result_error_round_trip_no_inner_details() {
409		let err = TypesError::internal("Something went wrong".into());
410		let qr = error_query_result(err);
411		let val = qr.into_value();
412
413		let Value::Object(ref obj) = val else {
414			panic!("Expected object");
415		};
416		assert_eq!(obj.get("kind"), Some(&Value::String("Internal".to_string())));
417		assert!(!obj.contains_key("details"), "Internal errors should have no details");
418
419		let parsed = QueryResult::from_value(val).expect("round-trip should succeed");
420		let err = parsed.result.unwrap_err();
421		assert!(err.is_internal());
422		assert_eq!(err.message(), "Something went wrong");
423	}
424
425	#[test]
426	fn query_result_error_round_trip_validation_parse() {
427		let err = TypesError::validation("Parse error".into(), ValidationError::Parse);
428		let qr = error_query_result(err);
429		let val = qr.into_value();
430		let parsed = QueryResult::from_value(val).expect("round-trip should succeed");
431
432		let err = parsed.result.unwrap_err();
433		assert!(err.is_validation());
434		assert_eq!(err.validation_details(), Some(&ValidationError::Parse));
435	}
436
437	#[test]
438	fn query_result_ok_round_trip() {
439		let qr = QueryResult {
440			time: Duration::from_millis(10),
441			result: Ok(Value::String("hello".to_string())),
442			query_type: QueryType::Other,
443		};
444		let val = qr.into_value();
445		let parsed = QueryResult::from_value(val).expect("round-trip should succeed");
446
447		let v = parsed.result.unwrap();
448		assert_eq!(v, Value::String("hello".to_string()));
449	}
450}