surrealdb_core/sql/
object.rs

1use crate::ctx::Context;
2use crate::dbs::Options;
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::sql::{
6	escape::escape_key,
7	fmt::{is_pretty, pretty_indent, Fmt, Pretty},
8	Operation, Thing, Value,
9};
10use http::{HeaderMap, HeaderName, HeaderValue};
11use reblessive::tree::Stk;
12use revision::revisioned;
13use serde::{Deserialize, Serialize};
14use std::collections::BTreeMap;
15use std::collections::HashMap;
16use std::fmt::{self, Display, Formatter, Write};
17use std::ops::Deref;
18use std::ops::DerefMut;
19
20pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Object";
21
22/// Invariant: Keys never contain NUL bytes.
23#[revisioned(revision = 1)]
24#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
25#[serde(rename = "$surrealdb::private::sql::Object")]
26#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
27#[non_exhaustive]
28pub struct Object(#[serde(with = "no_nul_bytes_in_keys")] pub BTreeMap<String, Value>);
29
30impl From<BTreeMap<&str, Value>> for Object {
31	fn from(v: BTreeMap<&str, Value>) -> Self {
32		Self(v.into_iter().map(|(key, val)| (key.to_string(), val)).collect())
33	}
34}
35
36impl From<BTreeMap<String, Value>> for Object {
37	fn from(v: BTreeMap<String, Value>) -> Self {
38		Self(v)
39	}
40}
41
42impl FromIterator<(String, Value)> for Object {
43	fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
44		Self(BTreeMap::from_iter(iter))
45	}
46}
47
48impl From<BTreeMap<String, String>> for Object {
49	fn from(v: BTreeMap<String, String>) -> Self {
50		Self(v.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
51	}
52}
53
54impl TryFrom<HeaderMap> for Object {
55	type Error = Error;
56	fn try_from(v: HeaderMap) -> Result<Self, Error> {
57		Ok(Self(
58			v.into_iter()
59				.map(|(k, v)| {
60					if let Some(k) = k {
61						Ok((k.to_string(), Value::from(v.to_str()?)))
62					} else {
63						Err(Error::Unreachable("Encountered a header without a name".into()))
64					}
65				})
66				.collect::<Result<BTreeMap<String, Value>, Error>>()?,
67		))
68	}
69}
70
71impl From<HashMap<&str, Value>> for Object {
72	fn from(v: HashMap<&str, Value>) -> Self {
73		Self(v.into_iter().map(|(key, val)| (key.to_string(), val)).collect())
74	}
75}
76
77impl From<HashMap<String, Value>> for Object {
78	fn from(v: HashMap<String, Value>) -> Self {
79		Self(v.into_iter().collect())
80	}
81}
82
83impl From<Option<Self>> for Object {
84	fn from(v: Option<Self>) -> Self {
85		v.unwrap_or_default()
86	}
87}
88
89impl From<Operation> for Object {
90	fn from(v: Operation) -> Self {
91		Self(match v {
92			Operation::Add {
93				path,
94				value,
95			} => map! {
96				String::from("op") => Value::from("add"),
97				String::from("path") => path.to_path().into(),
98				String::from("value") => value
99			},
100			Operation::Remove {
101				path,
102			} => map! {
103				String::from("op") => Value::from("remove"),
104				String::from("path") => path.to_path().into()
105			},
106			Operation::Replace {
107				path,
108				value,
109			} => map! {
110				String::from("op") => Value::from("replace"),
111				String::from("path") => path.to_path().into(),
112				String::from("value") => value
113			},
114			Operation::Change {
115				path,
116				value,
117			} => map! {
118				String::from("op") => Value::from("change"),
119				String::from("path") => path.to_path().into(),
120				String::from("value") => value
121			},
122			Operation::Copy {
123				path,
124				from,
125			} => map! {
126				String::from("op") => Value::from("copy"),
127				String::from("path") => path.to_path().into(),
128				String::from("from") => from.to_path().into()
129			},
130			Operation::Move {
131				path,
132				from,
133			} => map! {
134				String::from("op") => Value::from("move"),
135				String::from("path") => path.to_path().into(),
136				String::from("from") => from.to_path().into()
137			},
138			Operation::Test {
139				path,
140				value,
141			} => map! {
142				String::from("op") => Value::from("test"),
143				String::from("path") => path.to_path().into(),
144				String::from("value") => value
145			},
146		})
147	}
148}
149
150impl Deref for Object {
151	type Target = BTreeMap<String, Value>;
152	fn deref(&self) -> &Self::Target {
153		&self.0
154	}
155}
156
157impl DerefMut for Object {
158	fn deref_mut(&mut self) -> &mut Self::Target {
159		&mut self.0
160	}
161}
162
163impl IntoIterator for Object {
164	type Item = (String, Value);
165	type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
166	fn into_iter(self) -> Self::IntoIter {
167		self.0.into_iter()
168	}
169}
170
171impl TryInto<BTreeMap<String, String>> for Object {
172	type Error = Error;
173	fn try_into(self) -> Result<BTreeMap<String, String>, Self::Error> {
174		self.into_iter().map(|(k, v)| Ok((k, v.coerce_to_string()?))).collect()
175	}
176}
177
178impl TryInto<HeaderMap> for Object {
179	type Error = Error;
180	fn try_into(self) -> Result<HeaderMap, Self::Error> {
181		let mut headermap = HeaderMap::new();
182		for (k, v) in self.into_iter() {
183			let k: HeaderName = k.parse()?;
184			let v: HeaderValue = v.coerce_to_string()?.parse()?;
185			headermap.insert(k, v);
186		}
187
188		Ok(headermap)
189	}
190}
191
192impl Object {
193	/// Fetch the record id if there is one
194	pub fn rid(&self) -> Option<Thing> {
195		match self.get("id") {
196			Some(Value::Thing(v)) => Some(v.clone()),
197			_ => None,
198		}
199	}
200	/// Convert this object to a diff-match-patch operation
201	pub fn to_operation(&self) -> Result<Operation, Error> {
202		match self.get("op") {
203			Some(op_val) => match self.get("path") {
204				Some(path_val) => {
205					let path = path_val.jsonpath();
206
207					let from =
208						self.get("from").map(|value| value.jsonpath()).ok_or(Error::InvalidPatch {
209							message: String::from("'from' key missing"),
210						});
211
212					let value = self.get("value").cloned().ok_or(Error::InvalidPatch {
213						message: String::from("'value' key missing"),
214					});
215
216					match op_val.clone().as_string().as_str() {
217						// Add operation
218						"add" => Ok(Operation::Add {
219							path,
220							value: value?,
221						}),
222						// Remove operation
223						"remove" => Ok(Operation::Remove {
224							path,
225						}),
226						// Replace operation
227						"replace" => Ok(Operation::Replace {
228							path,
229							value: value?,
230						}),
231						// Change operation
232						"change" => Ok(Operation::Change {
233							path,
234							value: value?,
235						}),
236						// Copy operation
237						"copy" => Ok(Operation::Copy {
238							path,
239							from: from?,
240						}),
241						// Move operation
242						"move" => Ok(Operation::Move {
243							path,
244							from: from?,
245						}),
246						// Test operation
247						"test" => Ok(Operation::Test {
248							path,
249							value: value?,
250						}),
251						unknown_op => Err(Error::InvalidPatch {
252							message: format!("unknown op '{unknown_op}'"),
253						}),
254					}
255				}
256				_ => Err(Error::InvalidPatch {
257					message: String::from("'path' key missing"),
258				}),
259			},
260			_ => Err(Error::InvalidPatch {
261				message: String::from("'op' key missing"),
262			}),
263		}
264	}
265}
266
267impl Object {
268	/// Process this type returning a computed simple Value
269	pub(crate) async fn compute(
270		&self,
271		stk: &mut Stk,
272		ctx: &Context,
273		opt: &Options,
274		doc: Option<&CursorDoc>,
275	) -> Result<Value, Error> {
276		let mut x = BTreeMap::new();
277		for (k, v) in self.iter() {
278			match v.compute(stk, ctx, opt, doc).await {
279				Ok(v) => x.insert(k.clone(), v),
280				Err(e) => return Err(e),
281			};
282		}
283		Ok(Value::Object(Object(x)))
284	}
285
286	/// Checks whether all object values are static values
287	pub(crate) fn is_static(&self) -> bool {
288		self.values().all(Value::is_static)
289	}
290
291	/// Validate that a Object contains only computed Values
292	pub(crate) fn validate_computed(&self) -> Result<(), Error> {
293		self.values().try_for_each(|v| v.validate_computed())
294	}
295}
296
297impl Display for Object {
298	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
299		let mut f = Pretty::from(f);
300		if is_pretty() {
301			f.write_char('{')?;
302		} else {
303			f.write_str("{ ")?;
304		}
305		if !self.is_empty() {
306			let indent = pretty_indent();
307			write!(
308				f,
309				"{}",
310				Fmt::pretty_comma_separated(
311					self.0.iter().map(|args| Fmt::new(args, |(k, v), f| write!(
312						f,
313						"{}: {}",
314						escape_key(k),
315						v
316					))),
317				)
318			)?;
319			drop(indent);
320		}
321		if is_pretty() {
322			f.write_char('}')
323		} else {
324			f.write_str(" }")
325		}
326	}
327}
328
329mod no_nul_bytes_in_keys {
330	use serde::{
331		de::{self, Visitor},
332		ser::SerializeMap,
333		Deserializer, Serializer,
334	};
335	use std::{collections::BTreeMap, fmt};
336
337	use crate::sql::Value;
338
339	pub(crate) fn serialize<S>(
340		m: &BTreeMap<String, Value>,
341		serializer: S,
342	) -> Result<S::Ok, S::Error>
343	where
344		S: Serializer,
345	{
346		let mut s = serializer.serialize_map(Some(m.len()))?;
347		for (k, v) in m {
348			debug_assert!(!k.contains('\0'));
349			s.serialize_entry(k, v)?;
350		}
351		s.end()
352	}
353
354	pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<String, Value>, D::Error>
355	where
356		D: Deserializer<'de>,
357	{
358		struct NoNulBytesInKeysVisitor;
359
360		impl<'de> Visitor<'de> for NoNulBytesInKeysVisitor {
361			type Value = BTreeMap<String, Value>;
362
363			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
364				formatter.write_str("a map without any NUL bytes in its keys")
365			}
366
367			fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
368			where
369				A: de::MapAccess<'de>,
370			{
371				let mut ret = BTreeMap::new();
372				while let Some((k, v)) = map.next_entry()? {
373					ret.insert(k, v);
374				}
375				Ok(ret)
376			}
377		}
378
379		deserializer.deserialize_map(NoNulBytesInKeysVisitor)
380	}
381}