aragog/
edge_record.rs

1use crate::{DatabaseAccess, DatabaseRecord, Error, Record, Validate};
2use serde::{Deserialize, Serialize};
3use std::ops::{Deref, DerefMut};
4
5/// Struct wrapping an edge document, with the `from` and `to` fields correctly set.
6///
7/// The document of type `T` mut implement [`Record`] and `EdgeRecord` also implements it.
8///
9/// # Note
10///
11/// `EdgeRecord` implements `Deref` and `DerefMut` into `T`
12///
13/// [`Record`]: crate::Record
14#[derive(Serialize, Deserialize, Clone)]
15pub struct EdgeRecord<T> {
16    /// The `_from` field of `ArangoDB` edge documents
17    #[serde(rename(serialize = "_from", deserialize = "_from"))]
18    from: String,
19    /// The `to` field of `ArangoDB` edge documents
20    #[serde(rename(serialize = "_to", deserialize = "_to"))]
21    to: String,
22    /// The main document data, must implement [`Record`].
23    ///
24    /// Note: The data is flattened on save, so you won't have any field named `data` in your database.
25    ///
26    /// [`Record`]: crate::Record
27    #[serde(flatten)]
28    pub data: T,
29}
30
31impl<T: Record> EdgeRecord<T> {
32    /// Manually instantiates an Edge record
33    ///
34    /// # Arguments
35    ///
36    /// * `id_from` - The **from** document `id`
37    /// * `id_to` - The **to** document `id`
38    /// * `data` - The main document data
39    ///
40    /// # Errors
41    ///
42    /// This function validates the format of the id fields which can result in an error.
43    pub fn new(id_from: String, id_to: String, data: T) -> Result<Self, Error> {
44        let res = Self {
45            from: id_from,
46            to: id_to,
47            data,
48        };
49        res.validate()?;
50        Ok(res)
51    }
52
53    /// Retrieves the `from` document from the database
54    #[maybe_async::maybe_async]
55    pub async fn from_record<D, R>(&self, db_access: &D) -> Result<DatabaseRecord<R>, Error>
56    where
57        D: DatabaseAccess + ?Sized,
58        R: Record,
59    {
60        DatabaseRecord::find(self.key_from(), db_access).await
61    }
62
63    /// Retrieves the `to` document from the database
64    #[maybe_async::maybe_async]
65    pub async fn to_record<D, R>(&self, db_access: &D) -> Result<DatabaseRecord<R>, Error>
66    where
67        D: DatabaseAccess + ?Sized,
68        R: Record,
69    {
70        DatabaseRecord::find(self.key_to(), db_access).await
71    }
72
73    /// Retrieves the document `_from` field, storing the target documents `id`.
74    #[allow(clippy::missing_const_for_fn)] // Can't be const in 1.56
75    #[must_use]
76    #[inline]
77    pub fn id_from(&self) -> &String {
78        &self.from
79    }
80
81    /// Retrieves the document `_to` field, storing the target documents `id`.
82    #[allow(clippy::missing_const_for_fn)] // Can't be const in 1.56
83    #[must_use]
84    #[inline]
85    pub fn id_to(&self) -> &String {
86        &self.to
87    }
88
89    /// Parses the `from` value to retrieve only the `_key` part.
90    ///
91    /// # Panics
92    ///
93    /// This method may panic if the `from` value is not formatted correctly.
94    #[must_use]
95    pub fn key_from(&self) -> &str {
96        self.id_from().split('/').last().unwrap()
97    }
98
99    /// Parses the `to` value to retrieve only the `_key` part.
100    ///
101    /// # Panics
102    ///
103    /// This method may panic if the `to` value is not formatted correctly.
104    #[must_use]
105    pub fn key_to(&self) -> &str {
106        self.id_to().split('/').last().unwrap()
107    }
108
109    /// Parses the `from` value to retrieve only the collection name part.
110    ///
111    /// # Panics
112    ///
113    /// This method may panic if the `to` value is not formatted correctly.
114    #[must_use]
115    pub fn to_collection_name(&self) -> String {
116        self.id_to().split('/').next().unwrap().to_string()
117    }
118
119    /// Parses the `to` value to retrieve only the collection name part.
120    ///
121    /// # Panics
122    ///
123    /// This method may panic if the `from` value is not formatted correctly.
124    #[must_use]
125    pub fn from_collection_name(&self) -> &str {
126        self.id_from().split('/').next().unwrap()
127    }
128
129    fn validate_edge_fields(&self, errors: &mut Vec<String>) {
130        let array = [("from", self.id_from()), ("to", self.id_to())];
131        for (name, field) in array {
132            let vec: Vec<&str> = field.split('/').collect();
133            let [left, right]: [_; 2] = if let Ok(v) = vec.try_into() {
134                v
135            } else {
136                errors.push(format!(r#"{} "{}" is not a valid id"#, name, field));
137                continue;
138            };
139            Self::validate_min_len(name, left, 2, errors);
140            Self::validate_min_len(name, right, 2, errors);
141        }
142    }
143}
144
145impl<T: Record> Validate for EdgeRecord<T> {
146    fn validations(&self, errors: &mut Vec<String>) {
147        self.validate_edge_fields(errors);
148    }
149}
150
151#[maybe_async::maybe_async]
152impl<T: Record + Send> Record for EdgeRecord<T> {
153    const COLLECTION_NAME: &'static str = T::COLLECTION_NAME;
154
155    async fn before_create_hook<D>(&mut self, db_accessor: &D) -> Result<(), Error>
156    where
157        D: DatabaseAccess + ?Sized,
158    {
159        self.validate()?;
160        self.data.before_create_hook(db_accessor).await
161    }
162
163    async fn before_save_hook<D>(&mut self, db_accessor: &D) -> Result<(), Error>
164    where
165        D: DatabaseAccess + ?Sized,
166    {
167        self.data.before_save_hook(db_accessor).await
168    }
169
170    async fn before_delete_hook<D>(&mut self, db_accessor: &D) -> Result<(), Error>
171    where
172        D: DatabaseAccess + ?Sized,
173    {
174        self.data.before_delete_hook(db_accessor).await
175    }
176
177    async fn after_create_hook<D>(&mut self, db_accessor: &D) -> Result<(), Error>
178    where
179        D: DatabaseAccess + ?Sized,
180    {
181        self.data.after_create_hook(db_accessor).await
182    }
183
184    async fn after_save_hook<D>(&mut self, db_accessor: &D) -> Result<(), Error>
185    where
186        D: DatabaseAccess + ?Sized,
187    {
188        self.validate()?;
189        self.data.after_save_hook(db_accessor).await
190    }
191
192    async fn after_delete_hook<D>(&mut self, db_accessor: &D) -> Result<(), Error>
193    where
194        D: DatabaseAccess + ?Sized,
195    {
196        self.data.after_delete_hook(db_accessor).await
197    }
198}
199
200impl<T: Record> Deref for EdgeRecord<T> {
201    type Target = T;
202
203    fn deref(&self) -> &Self::Target {
204        &self.data
205    }
206}
207
208impl<T: Record> DerefMut for EdgeRecord<T> {
209    fn deref_mut(&mut self) -> &mut Self::Target {
210        &mut self.data
211    }
212}