elicitation/verification/types/
pathbufs.rs1use super::ValidationError;
4use crate::{ElicitClient, ElicitResult, Elicitation, Prompt};
5use elicitation_macros::instrumented_impl;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct PathBufExists(PathBuf);
14
15#[cfg_attr(not(kani), instrumented_impl)]
16impl PathBufExists {
17 pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
19 if path.exists() {
20 Ok(Self(path))
21 } else {
22 Err(ValidationError::PathDoesNotExist(
23 path.display().to_string(),
24 ))
25 }
26 }
27
28 pub fn get(&self) -> &PathBuf {
30 &self.0
31 }
32
33 pub fn into_inner(self) -> PathBuf {
35 self.0
36 }
37}
38
39#[cfg_attr(not(kani), instrumented_impl)]
40impl Prompt for PathBufExists {
41 fn prompt() -> Option<&'static str> {
42 Some("Please provide a path that exists on the filesystem:")
43 }
44}
45
46#[cfg_attr(not(kani), instrumented_impl)]
47impl Elicitation for PathBufExists {
48 type Style = <PathBuf as Elicitation>::Style;
49
50 #[tracing::instrument(skip(client))]
51 async fn elicit(client: &ElicitClient) -> ElicitResult<Self> {
52 tracing::debug!("Eliciting PathBufExists");
53 loop {
54 let path = PathBuf::elicit(client).await?;
55 match Self::new(path) {
56 Ok(valid) => {
57 tracing::debug!(path = ?valid.0, "Valid existing path");
58 return Ok(valid);
59 }
60 Err(e) => {
61 tracing::warn!(error = %e, "Path does not exist, re-prompting");
62 continue;
63 }
64 }
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash)]
74pub struct PathBufReadable(PathBuf);
75
76#[cfg_attr(not(kani), instrumented_impl)]
77impl PathBufReadable {
78 pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
80 match path.metadata() {
82 Ok(_) => Ok(Self(path)),
83 Err(_) => Err(ValidationError::PathNotReadable(path.display().to_string())),
84 }
85 }
86
87 pub fn get(&self) -> &PathBuf {
89 &self.0
90 }
91
92 pub fn into_inner(self) -> PathBuf {
94 self.0
95 }
96}
97
98#[cfg_attr(not(kani), instrumented_impl)]
99impl Prompt for PathBufReadable {
100 fn prompt() -> Option<&'static str> {
101 Some("Please provide a readable path:")
102 }
103}
104
105#[cfg_attr(not(kani), instrumented_impl)]
106impl Elicitation for PathBufReadable {
107 type Style = <PathBuf as Elicitation>::Style;
108
109 #[tracing::instrument(skip(client))]
110 async fn elicit(client: &ElicitClient) -> ElicitResult<Self> {
111 tracing::debug!("Eliciting PathBufReadable");
112 loop {
113 let path = PathBuf::elicit(client).await?;
114 match Self::new(path) {
115 Ok(valid) => {
116 tracing::debug!(path = ?valid.0, "Valid readable path");
117 return Ok(valid);
118 }
119 Err(e) => {
120 tracing::warn!(error = %e, "Path not readable, re-prompting");
121 continue;
122 }
123 }
124 }
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
133pub struct PathBufIsDir(PathBuf);
134
135#[cfg_attr(not(kani), instrumented_impl)]
136impl PathBufIsDir {
137 pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
139 if path.is_dir() {
140 Ok(Self(path))
141 } else if path.exists() {
142 Err(ValidationError::PathNotDirectory(
143 path.display().to_string(),
144 ))
145 } else {
146 Err(ValidationError::PathDoesNotExist(
147 path.display().to_string(),
148 ))
149 }
150 }
151
152 pub fn get(&self) -> &PathBuf {
154 &self.0
155 }
156
157 pub fn into_inner(self) -> PathBuf {
159 self.0
160 }
161}
162
163#[cfg_attr(not(kani), instrumented_impl)]
164impl Prompt for PathBufIsDir {
165 fn prompt() -> Option<&'static str> {
166 Some("Please provide a directory path:")
167 }
168}
169
170#[cfg_attr(not(kani), instrumented_impl)]
171impl Elicitation for PathBufIsDir {
172 type Style = <PathBuf as Elicitation>::Style;
173
174 #[tracing::instrument(skip(client))]
175 async fn elicit(client: &ElicitClient) -> ElicitResult<Self> {
176 tracing::debug!("Eliciting PathBufIsDir");
177 loop {
178 let path = PathBuf::elicit(client).await?;
179 match Self::new(path) {
180 Ok(valid) => {
181 tracing::debug!(path = ?valid.0, "Valid directory path");
182 return Ok(valid);
183 }
184 Err(e) => {
185 tracing::warn!(error = %e, "Path not directory, re-prompting");
186 continue;
187 }
188 }
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Hash)]
198pub struct PathBufIsFile(PathBuf);
199
200#[cfg_attr(not(kani), instrumented_impl)]
201impl PathBufIsFile {
202 pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
204 if path.is_file() {
205 Ok(Self(path))
206 } else if path.exists() {
207 Err(ValidationError::PathNotFile(path.display().to_string()))
208 } else {
209 Err(ValidationError::PathDoesNotExist(
210 path.display().to_string(),
211 ))
212 }
213 }
214
215 pub fn get(&self) -> &PathBuf {
217 &self.0
218 }
219
220 pub fn into_inner(self) -> PathBuf {
222 self.0
223 }
224}
225
226#[cfg_attr(not(kani), instrumented_impl)]
227impl Prompt for PathBufIsFile {
228 fn prompt() -> Option<&'static str> {
229 Some("Please provide a file path:")
230 }
231}
232
233#[cfg_attr(not(kani), instrumented_impl)]
234impl Elicitation for PathBufIsFile {
235 type Style = <PathBuf as Elicitation>::Style;
236
237 #[tracing::instrument(skip(client))]
238 async fn elicit(client: &ElicitClient) -> ElicitResult<Self> {
239 tracing::debug!("Eliciting PathBufIsFile");
240 loop {
241 let path = PathBuf::elicit(client).await?;
242 match Self::new(path) {
243 Ok(valid) => {
244 tracing::debug!(path = ?valid.0, "Valid file path");
245 return Ok(valid);
246 }
247 Err(e) => {
248 tracing::warn!(error = %e, "Path not file, re-prompting");
249 continue;
250 }
251 }
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use std::env;
260
261 #[test]
262 fn test_path_exists_valid() {
263 let mut path = env::current_dir().unwrap();
265 path.push("Cargo.toml");
266 let result = PathBufExists::new(path);
267 assert!(result.is_ok());
268 }
269
270 #[test]
271 fn test_path_exists_invalid() {
272 let path = PathBuf::from("/this/path/does/not/exist/hopefully");
273 let result = PathBufExists::new(path);
274 assert!(result.is_err());
275 }
276
277 #[test]
278 fn test_path_readable_valid() {
279 let mut path = env::current_dir().unwrap();
280 path.push("Cargo.toml");
281 let result = PathBufReadable::new(path);
282 assert!(result.is_ok());
283 }
284
285 #[test]
286 fn test_path_is_dir_valid() {
287 let mut path = env::current_dir().unwrap();
289 path.push("src");
290 let result = PathBufIsDir::new(path);
291 assert!(result.is_ok());
292 }
293
294 #[test]
295 fn test_path_is_dir_file() {
296 let mut path = env::current_dir().unwrap();
297 path.push("Cargo.toml");
298 let result = PathBufIsDir::new(path);
299 assert!(result.is_err());
300 }
301
302 #[test]
303 fn test_path_is_file_valid() {
304 let mut path = env::current_dir().unwrap();
305 path.push("Cargo.toml");
306 let result = PathBufIsFile::new(path);
307 assert!(result.is_ok());
308 }
309
310 #[test]
311 fn test_path_is_file_dir() {
312 let mut path = env::current_dir().unwrap();
313 path.push("src");
314 let result = PathBufIsFile::new(path);
315 assert!(result.is_err());
316 }
317
318 #[test]
319 fn test_path_into_inner() {
320 let mut original = env::current_dir().unwrap();
321 original.push("Cargo.toml");
322 let exists = PathBufExists::new(original.clone()).unwrap();
323 assert_eq!(exists.into_inner(), original);
324 }
325}