Skip to main content

surrealdb_core/rpc/
response.rs

1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4use surrealdb_types::{Error as TypesError, kind, object};
5use uuid::Uuid;
6
7use crate::dbs;
8use crate::dbs::{QueryResult, QueryType};
9use crate::rpc::request::SESSION_ID;
10use crate::types::{
11	PublicArray, PublicKind, PublicNotification, PublicObject, PublicValue, SurrealValue,
12};
13
14/// Query statistics.
15#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[non_exhaustive]
17pub struct DbResultStats {
18	/// The time taken to execute the query.
19	///
20	/// Note: This comes from the `time` field of the [`crate::dbs::QueryResult`] struct.
21	pub execution_time: Option<Duration>,
22	pub query_type: Option<QueryType>,
23}
24
25impl DbResultStats {
26	pub fn with_execution_time(mut self, execution_time: Duration) -> Self {
27		self.execution_time = Some(execution_time);
28		self
29	}
30
31	pub fn with_query_type(mut self, query_type: QueryType) -> Self {
32		self.query_type = Some(query_type);
33		self
34	}
35}
36
37/// The data returned by the database
38// The variants here should be in exactly the same order as `crate::engine::remote::ws::Data`
39// In future, they will possibly be merged to avoid having to keep them in sync.
40#[derive(Debug, Serialize, Deserialize)]
41pub enum DbResult {
42	/// Generally methods return a `expr::Value`
43	Other(PublicValue),
44	/// The query methods, `query` and `query_with` return a `Vec` of responses
45	Query(Vec<dbs::QueryResult>),
46	/// Live queries return a notification
47	Live(PublicNotification),
48	// Add new variants here
49}
50
51impl SurrealValue for DbResult {
52	fn kind_of() -> PublicKind {
53		kind!(array | {
54			id: uuid,
55			session: uuid | none,
56			action: string,
57			record: any,
58			result: any,
59		} | any)
60	}
61
62	fn is_value(_value: &PublicValue) -> bool {
63		true
64	}
65
66	fn into_value(self) -> PublicValue {
67		match self {
68			DbResult::Query(v) => {
69				let converted: Vec<PublicValue> = v.into_iter().map(|x| x.into_value()).collect();
70				PublicValue::Array(PublicArray::from(converted))
71			}
72			DbResult::Live(v) => PublicValue::Object(object! {
73				id: PublicValue::Uuid(v.id),
74				session: v.session.map(PublicValue::Uuid),
75				action: v.action.into_value(),
76				record: v.record,
77				result: v.result,
78			}),
79			DbResult::Other(v) => v,
80		}
81	}
82
83	fn from_value(value: PublicValue) -> Result<Self, TypesError> {
84		match value {
85			PublicValue::Array(arr) => {
86				let results = arr
87					.into_inner()
88					.into_iter()
89					.map(QueryResult::from_value)
90					.collect::<Result<Vec<_>, TypesError>>()?;
91				Ok(DbResult::Query(results))
92			}
93			PublicValue::Object(obj) => {
94				// Check if this is a Live result
95				if obj.get("id").is_some() && obj.get("action").is_some() {
96					let mut obj = obj.into_inner();
97					let id = obj
98						.remove("id")
99						.ok_or_else(|| TypesError::internal("Missing id".to_string()))?;
100					let action = obj
101						.remove("action")
102						.ok_or_else(|| TypesError::internal("Missing action".to_string()))?;
103					let record = obj.remove("record").unwrap_or(PublicValue::None);
104					let result = obj.remove("result").unwrap_or(PublicValue::None);
105
106					let PublicValue::Uuid(uuid) = id else {
107						return Err(TypesError::internal("Expected UUID for id field".to_string()));
108					};
109					let PublicValue::String(action_str) = action else {
110						return Err(TypesError::internal(
111							"Expected string for action field".to_string(),
112						));
113					};
114
115					let session = match obj.remove(SESSION_ID) {
116						Some(session) => SurrealValue::from_value(session)?,
117						None => None,
118					};
119
120					// Parse action string to PublicAction
121					let action = match action_str.as_str() {
122						"CREATE" => crate::types::PublicAction::Create,
123						"UPDATE" => crate::types::PublicAction::Update,
124						"DELETE" => crate::types::PublicAction::Delete,
125						_ => {
126							return Err(TypesError::internal(format!(
127								"Invalid action: {}",
128								action_str
129							)));
130						}
131					};
132
133					Ok(DbResult::Live(PublicNotification::new(
134						uuid, session, action, record, result,
135					)))
136				} else {
137					Ok(DbResult::Other(PublicValue::Object(obj)))
138				}
139			}
140			other => Ok(DbResult::Other(other)),
141		}
142	}
143}
144
145#[derive(Debug)]
146pub struct DbResponse {
147	pub id: Option<PublicValue>,
148	pub session_id: Option<Uuid>,
149	/// Success payload or wire-friendly error (kind, message, details, cause).
150	pub result: Result<DbResult, TypesError>,
151}
152
153impl DbResponse {
154	pub fn new(
155		id: Option<PublicValue>,
156		session_id: Option<Uuid>,
157		result: Result<DbResult, TypesError>,
158	) -> Self {
159		Self {
160			id,
161			session_id,
162			result,
163		}
164	}
165
166	/// Build a failure response; `error` is converted into the wire error type.
167	pub fn failure(
168		id: Option<PublicValue>,
169		session_id: Option<Uuid>,
170		error: impl Into<TypesError>,
171	) -> Self {
172		Self {
173			id,
174			session_id,
175			result: Err(error.into()),
176		}
177	}
178
179	pub fn success(id: Option<PublicValue>, session_id: Option<Uuid>, result: DbResult) -> Self {
180		Self {
181			id,
182			session_id,
183			result: Ok(result),
184		}
185	}
186
187	pub fn from_bytes(bytes: &[u8]) -> Result<Self, TypesError> {
188		let value = crate::rpc::format::flatbuffers::decode(bytes)
189			.map_err(|e| TypesError::internal(e.to_string()))?;
190		Self::from_value(value)
191	}
192}
193
194impl SurrealValue for DbResponse {
195	fn kind_of() -> PublicKind {
196		PublicKind::Object
197	}
198
199	fn is_value(value: &PublicValue) -> bool {
200		matches!(value, PublicValue::Object(_))
201	}
202
203	fn into_value(self) -> PublicValue {
204		let mut value = match self.result {
205			Ok(result) => map! { "result" => result.into_value() },
206			Err(err) => map! {
207				"error" => SurrealValue::into_value(err),
208			},
209		};
210		if let Some(id) = self.id {
211			value.insert("id", id);
212		}
213		if let Some(session_id) = self.session_id {
214			value.insert(SESSION_ID, PublicValue::Uuid(session_id.into()));
215		}
216		PublicValue::Object(PublicObject::from(
217			value.into_iter().collect::<std::collections::BTreeMap<_, _>>(),
218		))
219	}
220
221	fn from_value(value: PublicValue) -> Result<Self, TypesError> {
222		let PublicValue::Object(mut obj) = value else {
223			return Err(TypesError::internal("Expected object for DbResponse".to_string()));
224		};
225
226		let session_id = SurrealValue::from_value(obj.remove(SESSION_ID).unwrap_or_default())?;
227
228		let id = obj.remove("id");
229
230		let result = if let Some(result) = obj.remove("result") {
231			Ok(DbResult::from_value(result)?)
232		} else if let Some(error) = obj.remove("error") {
233			Err(TypesError::from_value(error)?)
234		} else {
235			return Err(TypesError::internal(
236				"DbResponse must have either 'result' or 'error' field".to_string(),
237			));
238		};
239
240		Ok(DbResponse {
241			id,
242			session_id,
243			result,
244		})
245	}
246}