grass/dev/strategy/discovery/
local.rs

1use std::fs;
2
3use crate::dev::{
4    config::GrassConfig,
5    public::api::RepositoryLocation,
6    strategy::{
7        discovery::DiscoveryStrategyError,
8        path::{PathStrategy, PathStrategyError},
9    },
10};
11
12use super::{BoxedIterator, DiscoveryExists, DiscoveryStrategy, Result};
13
14pub struct LocalDiscoveryStrategy<'a, T>
15where
16    T: PathStrategy,
17{
18    config: &'a GrassConfig,
19    path_strategy: &'a T,
20}
21
22impl<'a, T> LocalDiscoveryStrategy<'a, T>
23where
24    T: PathStrategy,
25{
26    pub fn new(config: &'a GrassConfig, path_strategy: &'a T) -> Self {
27        LocalDiscoveryStrategy {
28            config,
29            path_strategy,
30        }
31    }
32}
33
34impl<'a, T> DiscoveryStrategy for LocalDiscoveryStrategy<'a, T>
35where
36    T: PathStrategy,
37{
38    fn check_repository_exists<U>(&self, repository: U) -> Result<DiscoveryExists>
39    where
40        U: Into<RepositoryLocation>,
41    {
42        let repository_dir = self.path_strategy.get_directory(repository)?;
43
44        match repository_dir.is_dir() {
45            true => Ok(DiscoveryExists::Exists),
46            false => Ok(DiscoveryExists::RepositoryNotFound),
47        }
48    }
49
50    fn check_category_exists<U>(&self, category: U) -> Result<DiscoveryExists>
51    where
52        U: AsRef<str>,
53    {
54        match self.config.get_by_category(category) {
55            Some(_) => Ok(DiscoveryExists::Exists),
56            None => Ok(DiscoveryExists::CategoryNotFound),
57        }
58    }
59
60    fn list_repositories_in_category<U>(
61        &self,
62        category: U,
63    ) -> Result<BoxedIterator<Result<RepositoryLocation>>>
64    where
65        U: AsRef<str>,
66    {
67        let category = self.config.get_by_category(category).ok_or(
68            DiscoveryStrategyError::CategoryNotFound {
69                context: "When trying to ge tthe category from the configuration".into(),
70                reason: "Category doesn't exist".into(),
71            },
72        )?;
73
74        let base_dir = self.config.base_dir.join(&category.name);
75
76        let directory =
77            fs::read_dir(&base_dir).map_err(|error| DiscoveryStrategyError::FilesystemError {
78                context: format!(
79                "When trying to read the directory '{dir}' (expected to be a category directory)",
80                dir = base_dir.to_str().unwrap_or("CANNOT DISPLAY DIRECTORY PATH")
81            ),
82                reason: error.to_string(),
83            })?;
84
85        Ok(Box::from(directory.into_iter().filter_map(move |entry| {
86            let entry = match entry {
87                Ok(entry) => entry,
88                Err(error) => {
89                    return Some(Err(DiscoveryStrategyError::FilesystemError {
90                        context: "When reading unknown entry".into(),
91                        reason: error.to_string(),
92                    }))
93                }
94            };
95
96            let metadata = match entry.metadata() {
97                Ok(metadata) => metadata,
98                Err(error) => {
99                    return Some(Err(DiscoveryStrategyError::FilesystemError {
100                        context: format!(
101                            "When retrieving metadata for entry '{name}'",
102                            name = entry
103                                .path()
104                                .to_str()
105                                .unwrap_or("CANNOT DISPLAY DIRECTORY PATH")
106                        ),
107                        reason: error.to_string(),
108                    }))
109                }
110            };
111
112            if !metadata.is_dir() {
113                return None;
114            };
115
116            let entry_path = entry.path();
117
118            let repository = match entry_path.file_name().ok_or_else(|| {
119                DiscoveryStrategyError::FilesystemError {
120                    context: format!(
121                        "When retrieving directory name from path {path}",
122                        path = entry
123                            .path()
124                            .to_str()
125                            .unwrap_or("CANNOT_DISPLAY_DIRECTORY_PATH")
126                    ),
127                    reason: "No reason given".into(),
128                }
129            }) {
130                Ok(repository) => repository,
131                Err(error) => return Some(Err(error)),
132            };
133
134            let repository: String = match repository.to_str() {
135                Some(repository) => repository,
136                None => {
137                    return Some(Err(DiscoveryStrategyError::FilesystemError {
138                        context: format!(
139                            "When retrieving directory name from path {path}",
140                            path = entry
141                                .path()
142                                .to_str()
143                                .unwrap_or("CANNOT_DISPLAY_DIRECTORY_PATH")
144                        ),
145                        reason: "No reason given".into(),
146                    }))
147                }
148            }
149            .into();
150
151            Some(Ok(RepositoryLocation {
152                category: category.name.clone().into(),
153                repository,
154            }))
155        })))
156    }
157
158    fn list_categories<U>(&self) -> Result<U>
159    where
160        U: FromIterator<String>,
161    {
162        Ok(self.config.category.keys().cloned().collect())
163    }
164
165    fn create_repository(&self, location: RepositoryLocation) -> Result<()> {
166        if matches!(
167            self.check_repository_exists(location.clone()),
168            Ok(DiscoveryExists::Exists)
169        ) {
170            return Err(DiscoveryStrategyError::RepositoryExists {
171                context: "Before creating a new repository".into(),
172                reason: "Repository already exists".into(),
173            });
174        }
175
176        let repository_directory = self.path_strategy.get_directory(location)?;
177
178        fs::create_dir_all(repository_directory)?;
179
180        Ok(())
181    }
182
183    fn move_repository(
184        &self,
185        old_location: RepositoryLocation,
186        new_location: RepositoryLocation,
187    ) -> Result<()> {
188        if !matches!(
189            self.check_repository_exists(old_location.clone()),
190            Ok(DiscoveryExists::Exists)
191        ) {
192            return Err(DiscoveryStrategyError::RepositoryDoesNotExist {
193                context: "Before moving a repository".into(),
194                reason: "Old repository does not exists".into(),
195            });
196        }
197
198        if matches!(
199            self.check_repository_exists(new_location.clone()),
200            Ok(DiscoveryExists::Exists)
201        ) {
202            return Err(DiscoveryStrategyError::RepositoryExists {
203                context: "Before moving a repository".into(),
204                reason: "New repository already exists".into(),
205            });
206        }
207
208        let old_repository_directory = self.path_strategy.get_directory(old_location)?;
209        let new_repository_directory = self.path_strategy.get_directory(new_location)?;
210
211        fs::rename(old_repository_directory, new_repository_directory)?;
212
213        Ok(())
214    }
215}
216
217impl From<PathStrategyError> for DiscoveryStrategyError {
218    fn from(value: PathStrategyError) -> Self {
219        match value {
220            PathStrategyError::RepositoryNotFound { context, reason } => {
221                DiscoveryStrategyError::CategoryNotFound { context, reason }
222            }
223            PathStrategyError::FileDoesNotExist { context, reason } => {
224                DiscoveryStrategyError::FilesystemError { context, reason }
225            }
226            PathStrategyError::Unknown { context, reason } => {
227                DiscoveryStrategyError::UnknownError { context, reason }
228            }
229        }
230    }
231}