1use std::path::{Path, PathBuf};
2
3use crate::DhttpHome;
4use snafu::{OptionExt, ResultExt, Snafu};
5
6use dhttp_identity::name::{DhttpName, InvalidDhttpName};
7
8#[cfg(feature = "settings")]
9pub mod settings;
10#[cfg(feature = "ssl")]
11pub mod ssl;
12
13#[derive(Debug, Clone)]
20pub struct IdentityProfile {
21 pub(crate) path: PathBuf,
22 pub(crate) name: DhttpName<'static>,
23}
24
25#[derive(Debug, Snafu)]
26#[snafu(module)]
27pub enum IdentityProfileFromPathError {
28 #[snafu(display("identity profile path has no directory name: {}", path.display()))]
29 MissingFileName { path: PathBuf },
30 #[snafu(display("identity profile directory name is not valid unicode: {}", path.display()))]
31 NonUtf8FileName { path: PathBuf },
32 #[snafu(display("failed to parse identity profile directory name as dhttp name"))]
33 InvalidName { source: InvalidDhttpName },
34}
35
36impl IdentityProfile {
37 pub const LOGS_DIR: &'static str = "logs";
38 pub const ACCESS_LOG_FILE: &'static str = "access.log";
39 pub const DB_DIR: &'static str = "db";
40 pub const ACCESS_DB_FILE: &'static str = "access.db";
41 pub const SERVER_CONF_FILE: &'static str = "server.conf";
42
43 pub fn name(&self) -> &DhttpName<'static> {
44 &self.name
45 }
46
47 pub fn path(&self) -> &Path {
48 self.path.as_path()
49 }
50
51 pub fn join(&self, sub: impl AsRef<Path>) -> PathBuf {
52 self.path.join(sub)
53 }
54
55 pub fn logs_dir(&self) -> PathBuf {
56 self.join(Self::LOGS_DIR)
57 }
58
59 pub fn access_log_path(&self) -> PathBuf {
60 self.logs_dir().join(Self::ACCESS_LOG_FILE)
61 }
62
63 pub fn access_db_path(&self) -> PathBuf {
64 self.join(Self::DB_DIR).join(Self::ACCESS_DB_FILE)
65 }
66
67 pub fn server_conf_path(&self) -> PathBuf {
68 self.join(Self::SERVER_CONF_FILE)
69 }
70
71 fn try_from_path(path: PathBuf) -> Result<Self, IdentityProfileFromPathError> {
72 use identity_profile_from_path_error::*;
73
74 let file_name = path
75 .file_name()
76 .context(MissingFileNameSnafu { path: &path })?;
77 let file_name = file_name
78 .to_str()
79 .context(NonUtf8FileNameSnafu { path: &path })?;
80 let name = file_name.parse::<DhttpName>().context(InvalidNameSnafu)?;
81 Ok(Self { path, name })
82 }
83}
84
85impl TryFrom<PathBuf> for IdentityProfile {
86 type Error = IdentityProfileFromPathError;
87
88 fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
89 Self::try_from_path(path)
90 }
91}
92
93impl TryFrom<&Path> for IdentityProfile {
94 type Error = IdentityProfileFromPathError;
95
96 fn try_from(path: &Path) -> Result<Self, Self::Error> {
97 Self::try_from_path(path.to_path_buf())
98 }
99}
100
101impl DhttpHome {
102 pub fn join_identity_name(&self, name: DhttpName<'_>) -> PathBuf {
103 self.join(name.as_partial())
104 }
105
106 pub fn identity_profile(&self, name: DhttpName<'_>) -> IdentityProfile {
112 IdentityProfile {
113 path: self.join_identity_name(name.clone()),
114 name: name.to_owned(),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn identity_profile_from_path_uses_directory_name_as_dhttp_name() {
125 let profile = IdentityProfile::try_from(PathBuf::from("/tmp/reimu.pilot")).unwrap();
126
127 assert_eq!(profile.path(), Path::new("/tmp/reimu.pilot"));
128 assert_eq!(profile.name().as_full(), "reimu.pilot.dhttp.net");
129 }
130
131 #[test]
132 fn identity_profile_from_path_rejects_path_without_directory_name() {
133 let error = IdentityProfile::try_from(Path::new("/")).unwrap_err();
134
135 assert!(matches!(
136 error,
137 IdentityProfileFromPathError::MissingFileName { .. }
138 ));
139 }
140
141 #[test]
142 fn identity_profile_from_path_rejects_invalid_directory_name() {
143 let error = IdentityProfile::try_from(Path::new("/tmp/123")).unwrap_err();
144
145 assert!(matches!(
146 error,
147 IdentityProfileFromPathError::InvalidName { .. }
148 ));
149 }
150}