Skip to main content

surrealdb/api/opt/
resource.rs

1use std::ops::{self, Bound};
2
3use crate::api::Result;
4use crate::api::err::Error;
5use crate::core::val;
6use crate::{Object, RecordId, RecordIdKey, Value};
7
8/// A wrapper type to assert that you ment to use a string as a table name.
9///
10/// To prevent some possible errors, by defauit [`IntoResource`] does not allow
11/// `:` in table names as this might be an indication that the user might have
12/// intended to use a record id instead. If you wrap your table name string in
13/// this tupe the [`IntoResource`] trait will accept any table names.
14#[derive(Debug)]
15pub struct Table<T>(pub T);
16
17impl<T> Table<T>
18where
19	T: Into<String>,
20{
21	#[allow(dead_code)]
22	pub(crate) fn into_core(self) -> val::Table {
23		//  TODO: Null byte validity
24		unsafe { val::Table::new_unchecked(self.0.into()) }
25	}
26
27	/// Add a range of keys to the table.
28	pub fn with_range<R>(self, range: R) -> QueryRange
29	where
30		KeyRange: From<R>,
31	{
32		let range = KeyRange::from(range);
33		let res = val::RecordIdKeyRange {
34			start: range.start.map(RecordIdKey::into_inner),
35			end: range.end.map(RecordIdKey::into_inner),
36		};
37		let res = val::RecordId::new(self.0.into(), Box::new(res));
38		QueryRange(res)
39	}
40}
41
42transparent_wrapper!(
43	/// A table range.
44	#[derive(Clone, PartialEq)]
45	pub struct QueryRange(val::RecordId)
46);
47
48#[derive(Clone, Copy, Eq, PartialEq)]
49pub enum Direction {
50	Out,
51	In,
52	Both,
53}
54
55/// A database resource
56///
57/// A resource is a location, or a range of locations, from which data can be
58/// fetched.
59#[derive(Debug, Clone, PartialEq)]
60#[non_exhaustive]
61pub enum Resource {
62	/// Table name
63	Table(String),
64	/// Record ID
65	RecordId(RecordId),
66	/// An object
67	Object(Object),
68	/// An array
69	Array(Vec<Value>),
70	/// A range of id's on a table.
71	Range(QueryRange),
72	/// Unspecified resource
73	Unspecified,
74}
75
76impl Resource {
77	/// Add a range to the resource, this only works if the resource is a table.
78	pub fn with_range(self, range: KeyRange) -> Result<Self> {
79		match self {
80			Resource::Table(table) => Ok(Resource::Range(Table(table).with_range(range))),
81			Resource::RecordId(_) => Err(Error::RangeOnRecordId.into()),
82			Resource::Object(_) => Err(Error::RangeOnObject.into()),
83			Resource::Array(_) => Err(Error::RangeOnArray.into()),
84			Resource::Range(_) => Err(Error::RangeOnRange.into()),
85			Resource::Unspecified => Err(Error::RangeOnUnspecified.into()),
86		}
87	}
88
89	#[cfg(any(feature = "protocol-ws", feature = "protocol-http"))]
90	pub(crate) fn into_core_value(self) -> val::Value {
91		match self {
92			Resource::Table(x) => Table(x).into_core().into(),
93			Resource::RecordId(x) => x.into_inner().into(),
94			Resource::Object(x) => x.into_inner().into(),
95			Resource::Array(x) => Value::array_to_core(x).into(),
96			Resource::Range(x) => x.into_inner().into(),
97			Resource::Unspecified => val::Value::None,
98		}
99	}
100	pub fn is_single_recordid(&self) -> bool {
101		match self {
102			Resource::RecordId(rid) => {
103				!matches!(rid.into_inner_ref().key, val::RecordIdKey::Range(_))
104			}
105			_ => false,
106		}
107	}
108}
109
110impl From<RecordId> for Resource {
111	fn from(thing: RecordId) -> Self {
112		Self::RecordId(thing)
113	}
114}
115
116impl From<&RecordId> for Resource {
117	fn from(thing: &RecordId) -> Self {
118		Self::RecordId(thing.clone())
119	}
120}
121
122impl From<Object> for Resource {
123	fn from(object: Object) -> Self {
124		Self::Object(object)
125	}
126}
127
128impl From<&Object> for Resource {
129	fn from(object: &Object) -> Self {
130		Self::Object(object.clone())
131	}
132}
133
134impl From<Vec<Value>> for Resource {
135	fn from(array: Vec<Value>) -> Self {
136		Self::Array(array)
137	}
138}
139
140impl From<&[Value]> for Resource {
141	fn from(array: &[Value]) -> Self {
142		Self::Array(array.to_vec())
143	}
144}
145
146impl From<&str> for Resource {
147	fn from(s: &str) -> Self {
148		Resource::from(s.to_string())
149	}
150}
151
152impl From<&String> for Resource {
153	fn from(s: &String) -> Self {
154		Self::from(s.as_str())
155	}
156}
157
158impl From<String> for Resource {
159	fn from(s: String) -> Self {
160		Resource::Table(s)
161	}
162}
163
164impl From<QueryRange> for Resource {
165	fn from(value: QueryRange) -> Self {
166		Resource::Range(value)
167	}
168}
169
170impl<T, I> From<(T, I)> for Resource
171where
172	T: Into<String>,
173	I: Into<RecordIdKey>,
174{
175	fn from((table, id): (T, I)) -> Self {
176		let record_id = RecordId::from_table_key(table, id);
177		Self::RecordId(record_id)
178	}
179}
180
181impl From<()> for Resource {
182	fn from(_value: ()) -> Self {
183		Self::Unspecified
184	}
185}
186
187/// Holds the `start` and `end` bounds of a range query
188#[derive(Debug, PartialEq, Clone)]
189pub struct KeyRange {
190	pub(crate) start: Bound<RecordIdKey>,
191	pub(crate) end: Bound<RecordIdKey>,
192}
193
194impl<T> From<(Bound<T>, Bound<T>)> for KeyRange
195where
196	T: Into<RecordIdKey>,
197{
198	fn from((start, end): (Bound<T>, Bound<T>)) -> Self {
199		Self {
200			start: match start {
201				Bound::Included(idx) => Bound::Included(idx.into()),
202				Bound::Excluded(idx) => Bound::Excluded(idx.into()),
203				Bound::Unbounded => Bound::Unbounded,
204			},
205			end: match end {
206				Bound::Included(idx) => Bound::Included(idx.into()),
207				Bound::Excluded(idx) => Bound::Excluded(idx.into()),
208				Bound::Unbounded => Bound::Unbounded,
209			},
210		}
211	}
212}
213
214impl<T> From<ops::Range<T>> for KeyRange
215where
216	T: Into<RecordIdKey>,
217{
218	fn from(
219		ops::Range {
220			start,
221			end,
222		}: ops::Range<T>,
223	) -> Self {
224		Self {
225			start: Bound::Included(start.into()),
226			end: Bound::Excluded(end.into()),
227		}
228	}
229}
230
231impl<T> From<ops::RangeInclusive<T>> for KeyRange
232where
233	T: Into<RecordIdKey>,
234{
235	fn from(range: ops::RangeInclusive<T>) -> Self {
236		let (start, end) = range.into_inner();
237		Self {
238			start: Bound::Included(start.into()),
239			end: Bound::Included(end.into()),
240		}
241	}
242}
243
244impl<T> From<ops::RangeFrom<T>> for KeyRange
245where
246	T: Into<RecordIdKey>,
247{
248	fn from(
249		ops::RangeFrom {
250			start,
251		}: ops::RangeFrom<T>,
252	) -> Self {
253		Self {
254			start: Bound::Included(start.into()),
255			end: Bound::Unbounded,
256		}
257	}
258}
259
260impl<T> From<ops::RangeTo<T>> for KeyRange
261where
262	T: Into<RecordIdKey>,
263{
264	fn from(
265		ops::RangeTo {
266			end,
267		}: ops::RangeTo<T>,
268	) -> Self {
269		Self {
270			start: Bound::Unbounded,
271			end: Bound::Excluded(end.into()),
272		}
273	}
274}
275
276impl<T> From<ops::RangeToInclusive<T>> for KeyRange
277where
278	T: Into<RecordIdKey>,
279{
280	fn from(
281		ops::RangeToInclusive {
282			end,
283		}: ops::RangeToInclusive<T>,
284	) -> Self {
285		Self {
286			start: Bound::Unbounded,
287			end: Bound::Included(end.into()),
288		}
289	}
290}
291
292impl From<ops::RangeFull> for KeyRange {
293	fn from(_: ops::RangeFull) -> Self {
294		Self {
295			start: Bound::Unbounded,
296			end: Bound::Unbounded,
297		}
298	}
299}
300
301/// A trait for types which can be used as a resource selection for a query.
302pub trait IntoResource<Output>: into_resource::Sealed<Output> {}
303
304mod into_resource {
305	pub trait Sealed<Output> {
306		fn into_resource(self) -> super::Result<super::Resource>;
307	}
308}
309
310/// A trait for types which can be used as a resource selection for a query that
311/// returns an `Option`.
312pub trait CreateResource<Output>: create_resource::Sealed<Output> {}
313
314mod create_resource {
315	pub trait Sealed<Output> {
316		fn into_resource(self) -> super::Result<super::Resource>;
317	}
318}
319
320fn no_colon(a: &str) -> Result<()> {
321	if a.contains(':') {
322		return Err(Error::TableColonId {
323			table: a.to_string(),
324		}
325		.into());
326	}
327	Ok(())
328}
329
330// IntoResource
331
332impl IntoResource<Value> for Resource {}
333impl into_resource::Sealed<Value> for Resource {
334	fn into_resource(self) -> Result<Resource> {
335		Ok(self)
336	}
337}
338
339impl<R> IntoResource<Option<R>> for Object {}
340impl<R> into_resource::Sealed<Option<R>> for Object {
341	fn into_resource(self) -> Result<Resource> {
342		Ok(self.into())
343	}
344}
345
346impl<R> IntoResource<Option<R>> for RecordId {}
347impl<R> into_resource::Sealed<Option<R>> for RecordId {
348	fn into_resource(self) -> Result<Resource> {
349		Ok(self.into())
350	}
351}
352
353impl<R> IntoResource<Option<R>> for &RecordId {}
354impl<R> into_resource::Sealed<Option<R>> for &RecordId {
355	fn into_resource(self) -> Result<Resource> {
356		Ok(self.clone().into())
357	}
358}
359
360impl<R, T, I> IntoResource<Option<R>> for (T, I)
361where
362	T: Into<String>,
363	I: Into<RecordIdKey>,
364{
365}
366impl<R, T, I> into_resource::Sealed<Option<R>> for (T, I)
367where
368	T: Into<String>,
369	I: Into<RecordIdKey>,
370{
371	fn into_resource(self) -> Result<Resource> {
372		Ok(self.into())
373	}
374}
375
376impl<R> IntoResource<Vec<R>> for Vec<Value> {}
377impl<R> into_resource::Sealed<Vec<R>> for Vec<Value> {
378	fn into_resource(self) -> Result<Resource> {
379		Ok(self.into())
380	}
381}
382
383impl<R> IntoResource<Vec<R>> for QueryRange {}
384impl<R> into_resource::Sealed<Vec<R>> for QueryRange {
385	fn into_resource(self) -> Result<Resource> {
386		Ok(self.into())
387	}
388}
389
390impl<T, R> IntoResource<Vec<R>> for Table<T> where T: Into<String> {}
391impl<T, R> into_resource::Sealed<Vec<R>> for Table<T>
392where
393	T: Into<String>,
394{
395	fn into_resource(self) -> Result<Resource> {
396		let t = self.0.into();
397		Ok(t.into())
398	}
399}
400
401impl<R> IntoResource<Vec<R>> for &str {}
402impl<R> into_resource::Sealed<Vec<R>> for &str {
403	fn into_resource(self) -> Result<Resource> {
404		no_colon(self)?;
405		Ok(self.into())
406	}
407}
408
409impl<R> IntoResource<Vec<R>> for String {}
410impl<R> into_resource::Sealed<Vec<R>> for String {
411	fn into_resource(self) -> Result<Resource> {
412		no_colon(&self)?;
413		Ok(self.into())
414	}
415}
416
417impl<R> IntoResource<Vec<R>> for &String {}
418impl<R> into_resource::Sealed<Vec<R>> for &String {
419	fn into_resource(self) -> Result<Resource> {
420		no_colon(self)?;
421		Ok(self.into())
422	}
423}
424
425impl<R> IntoResource<Vec<R>> for () {}
426impl<R> into_resource::Sealed<Vec<R>> for () {
427	fn into_resource(self) -> Result<Resource> {
428		Ok(Resource::Unspecified)
429	}
430}
431
432// CreateResource
433
434impl CreateResource<Value> for Resource {}
435impl create_resource::Sealed<Value> for Resource {
436	fn into_resource(self) -> Result<Resource> {
437		Ok(self)
438	}
439}
440
441impl<R> CreateResource<Option<R>> for Object {}
442impl<R> create_resource::Sealed<Option<R>> for Object {
443	fn into_resource(self) -> Result<Resource> {
444		Ok(self.into())
445	}
446}
447
448impl<R> CreateResource<Option<R>> for RecordId {}
449impl<R> create_resource::Sealed<Option<R>> for RecordId {
450	fn into_resource(self) -> Result<Resource> {
451		Ok(self.into())
452	}
453}
454
455impl<R> CreateResource<Option<R>> for &RecordId {}
456impl<R> create_resource::Sealed<Option<R>> for &RecordId {
457	fn into_resource(self) -> Result<Resource> {
458		Ok(self.clone().into())
459	}
460}
461
462impl<R, T, I> CreateResource<Option<R>> for (T, I)
463where
464	T: Into<String>,
465	I: Into<RecordIdKey>,
466{
467}
468impl<R, T, I> create_resource::Sealed<Option<R>> for (T, I)
469where
470	T: Into<String>,
471	I: Into<RecordIdKey>,
472{
473	fn into_resource(self) -> Result<Resource> {
474		Ok(self.into())
475	}
476}
477
478impl<T, R> CreateResource<Option<R>> for Table<T> where T: Into<String> {}
479impl<T, R> create_resource::Sealed<Option<R>> for Table<T>
480where
481	T: Into<String>,
482{
483	fn into_resource(self) -> Result<Resource> {
484		let t = self.0.into();
485		Ok(t.into())
486	}
487}
488
489impl<R> CreateResource<Option<R>> for &str {}
490impl<R> create_resource::Sealed<Option<R>> for &str {
491	fn into_resource(self) -> Result<Resource> {
492		no_colon(self)?;
493		Ok(self.into())
494	}
495}
496
497impl<R> CreateResource<Option<R>> for String {}
498impl<R> create_resource::Sealed<Option<R>> for String {
499	fn into_resource(self) -> Result<Resource> {
500		no_colon(&self)?;
501		Ok(self.into())
502	}
503}
504
505impl<R> CreateResource<Option<R>> for &String {}
506impl<R> create_resource::Sealed<Option<R>> for &String {
507	fn into_resource(self) -> Result<Resource> {
508		no_colon(self)?;
509		Ok(self.into())
510	}
511}