aragog/schema/
database_schema.rs

1use std::fs;
2
3use arangors_lite::{ClientError, Database};
4use serde::{Deserialize, Serialize};
5
6use crate::schema::{CollectionSchema, GraphSchema, IndexSchema, SchemaDatabaseOperation};
7use crate::Error;
8
9/// Aragog schema representation of an `ArangoDB` Database.
10/// This struct is meant to load/generate the schema file.
11#[derive(Debug, Serialize, Deserialize, Clone, Default)]
12pub struct DatabaseSchema {
13    /// Schema version
14    pub version: Option<u64>,
15    /// Database collections
16    pub collections: Vec<CollectionSchema>,
17    /// Database Collection Indexes
18    #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
19    pub indexes: Vec<IndexSchema>,
20    /// Database named graphs
21    #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
22    pub graphs: Vec<GraphSchema>,
23}
24
25impl DatabaseSchema {
26    /// Find a Collection index from the schema instance
27    #[must_use]
28    pub fn collection_index(&self, name: &str) -> Option<usize> {
29        self.collections.iter().position(|c| c.name == name)
30    }
31
32    /// Find a Collection from the schema instance
33    #[must_use]
34    pub fn collection(&self, name: &str) -> Option<&CollectionSchema> {
35        self.collections.iter().find(|c| c.name == name)
36    }
37
38    /// Find an index index from the schema instance
39    #[must_use]
40    pub fn index_index(&self, collection: &str, name: &str) -> Option<usize> {
41        self.indexes
42            .iter()
43            .position(|c| c.name == name && c.collection == collection)
44    }
45
46    /// Find an Index from the schema instance
47    #[must_use]
48    pub fn index(&self, collection: &str, name: &str) -> Option<&IndexSchema> {
49        self.indexes
50            .iter()
51            .find(|c| c.name == name && c.collection == collection)
52    }
53
54    /// Find an index index from the schema instance
55    #[must_use]
56    pub fn graph_index(&self, name: &str) -> Option<usize> {
57        self.graphs.iter().position(|c| c.0.name == name)
58    }
59
60    /// Find an Index from the schema instance
61    #[must_use]
62    pub fn graph(&self, name: &str) -> Option<&GraphSchema> {
63        self.graphs.iter().find(|c| c.0.name == name)
64    }
65
66    /// Loads the YAML schema from the give `path`
67    ///
68    /// # Errors
69    ///
70    /// Will fail on wrong file path, file ACLs or content
71    pub fn load(path: &str) -> Result<Self, Error> {
72        let file = match fs::read_to_string(path) {
73            Ok(val) => val,
74            Err(error) => {
75                return Err(Error::InitError {
76                    item: path.to_string(),
77                    message: error.to_string(),
78                });
79            }
80        };
81        let value: Self = match serde_yaml::from_str(&file) {
82            Ok(val) => val,
83            Err(error) => {
84                return Err(Error::InitError {
85                    item: path.to_string(),
86                    message: error.to_string(),
87                });
88            }
89        };
90        Ok(value)
91    }
92}
93
94#[maybe_async::maybe_async]
95impl SchemaDatabaseOperation for DatabaseSchema {
96    type PoolType = ();
97
98    async fn apply_to_database(
99        &self,
100        database: &Database,
101        silent: bool,
102    ) -> Result<Option<Self::PoolType>, ClientError> {
103        for item in &self.collections {
104            Self::handle_error(item.apply_to_database(database, silent).await, silent)?;
105        }
106        for item in &self.indexes {
107            Self::handle_error(item.apply_to_database(database, silent).await, silent)?;
108        }
109        for item in &self.graphs {
110            Self::handle_error(item.apply_to_database(database, silent).await, silent)?;
111        }
112        Ok(Some(()))
113    }
114
115    async fn drop(&self, database: &Database) -> Result<(), ClientError> {
116        for item in &self.collections {
117            item.drop(database).await?;
118        }
119        for item in &self.indexes {
120            item.drop(database).await?;
121        }
122        for item in &self.graphs {
123            item.drop(database).await?;
124        }
125        Ok(())
126    }
127
128    async fn get(&self, _database: &Database) -> Result<Self::PoolType, ClientError> {
129        Ok(())
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use arangors_lite::graph::{EdgeDefinition, Graph, GraphOptions};
136    use arangors_lite::index::IndexSettings;
137
138    use crate::schema::IndexSchema;
139
140    use super::*;
141
142    fn schema() -> DatabaseSchema {
143        DatabaseSchema {
144            version: None,
145            collections: vec![
146                CollectionSchema {
147                    name: "collectionA".to_string(),
148                    is_edge_collection: false,
149                    wait_for_sync: None,
150                },
151                CollectionSchema {
152                    name: "collectionB".to_string(),
153                    is_edge_collection: false,
154                    wait_for_sync: Some(true),
155                },
156                CollectionSchema {
157                    name: "edgeCollectionA".to_string(),
158                    is_edge_collection: true,
159                    wait_for_sync: None,
160                },
161            ],
162            indexes: vec![
163                IndexSchema {
164                    name: "OnUsername".to_string(),
165                    collection: "CollectionA".to_string(),
166                    fields: vec!["username".to_string()],
167                    settings: IndexSettings::Persistent {
168                        unique: true,
169                        sparse: false,
170                        deduplicate: false,
171                    },
172                },
173                IndexSchema {
174                    name: "OnAgeAndemail".to_string(),
175                    collection: "CollectionB".to_string(),
176                    fields: vec!["age".to_string(), "email".to_string()],
177                    settings: IndexSettings::Ttl { expire_after: 3600 },
178                },
179            ],
180            graphs: vec![GraphSchema(Graph {
181                name: "namedGraph".to_string(),
182                edge_definitions: vec![EdgeDefinition {
183                    collection: "edgeCollection1".to_string(),
184                    from: vec!["collectionA".to_string()],
185                    to: vec!["collectionB".to_string(), "collectionC".to_string()],
186                }],
187                orphan_collections: vec![],
188                is_smart: None,
189                is_disjoint: None,
190                options: Some(GraphOptions {
191                    smart_graph_attribute: None,
192                    number_of_shards: None,
193                    replication_factor: Some(10),
194                    write_concern: None,
195                }),
196            })],
197        }
198    }
199
200    #[test]
201    fn serialization_works() {
202        let schema = schema();
203        serde_yaml::to_string(&schema).unwrap();
204    }
205}