1#![deny(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6use std::{path::Path, str::Utf8Error};
7
8#[cfg(feature = "diagnostics")]
9use miette::Diagnostic;
10
11use non_empty_str::{EmptyStr, NonEmptyStr, NonEmptyString};
12use thiserror::Error;
13
14mod sealed {
15 pub trait Sealed {}
16}
17
18#[derive(Debug, Error)]
20#[cfg_attr(feature = "diagnostics", derive(Diagnostic))]
21pub enum Error {
22 #[error("path name not found")]
24 #[cfg_attr(
25 feature = "diagnostics",
26 diagnostic(code(path_name::not_found), help("make sure the path name is present"))
27 )]
28 NotFound,
29
30 #[error("empty path name encountered")]
32 #[cfg_attr(
33 feature = "diagnostics",
34 diagnostic(code(path_name::empty), help("make sure the path name is non-empty"))
35 )]
36 Empty(#[from] EmptyStr),
37
38 #[error("invalid utf-8 in path name")]
40 #[cfg_attr(
41 feature = "diagnostics",
42 diagnostic(code(path_name::utf8), help("make sure the path name is valid utf-8"))
43 )]
44 Utf8(#[from] Utf8Error),
45}
46
47pub trait PathName: sealed::Sealed {
49 fn path_name(&self) -> Result<&NonEmptyStr, Error>;
55
56 fn path_name_owned(&self) -> Result<NonEmptyString, Error> {
64 self.path_name().map(ToOwned::to_owned)
65 }
66}
67
68impl<P: AsRef<Path>> sealed::Sealed for P {}
69
70impl<P: AsRef<Path>> PathName for P {
71 fn path_name(&self) -> Result<&NonEmptyStr, Error> {
72 let path = self.as_ref();
73
74 let string: &str = path.file_name().ok_or(Error::NotFound)?.try_into()?;
75
76 let name = string.try_into()?;
77
78 Ok(name)
79 }
80}