1use std::{env, error::Error, fmt::Display, path::PathBuf, str::FromStr};
15
16use serde::{
17 de::{value::MapAccessDeserializer, Visitor},
18 Deserialize, Serialize,
19};
20
21#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
22#[serde(default)]
23pub struct LibSearchDirs(Vec<LibSearchDir>);
24
25impl LibSearchDirs {
26 pub fn from_paths<T: AsRef<str>>(paths: &[T]) -> Self {
27 Self(
28 paths
29 .iter()
30 .map(|s| LibSearchDir::Path(s.as_ref().to_string()))
31 .collect(),
32 )
33 }
34
35 pub fn from_specs<T: AsRef<str>>(paths: &[T]) -> Result<Self, serde_json::Error> {
36 let dirs = paths
37 .iter()
38 .map(|s| {
39 let de = &mut serde_json::Deserializer::from_str(s.as_ref());
40 LibSearchDir::deserialize(de)
41 })
42 .collect::<Result<Vec<_>, _>>()?;
43
44 Ok(Self(dirs))
45 }
46}
47
48#[derive(Debug)]
49pub struct InvalidLibSearchDir {
50 found: LibSearchDir,
51 source: String,
52}
53
54impl Display for InvalidLibSearchDir {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(
57 f,
58 "invalid library search directory `{:?}`: {}",
59 self.found, self.source
60 )
61 }
62}
63
64impl Error for InvalidLibSearchDir {}
65
66pub struct IntoIter {
67 iter: std::vec::IntoIter<LibSearchDir>,
68}
69
70impl Iterator for IntoIter {
71 type Item = Result<PathBuf, InvalidLibSearchDir>;
72
73 fn next(&mut self) -> Option<Self::Item> {
74 self.iter.next().map(LibSearchDir::into_path)
75 }
76}
77
78impl IntoIterator for LibSearchDirs {
79 type Item = Result<PathBuf, InvalidLibSearchDir>;
80
81 type IntoIter = IntoIter;
82
83 fn into_iter(self) -> Self::IntoIter {
84 IntoIter {
85 iter: self.0.into_iter(),
86 }
87 }
88}
89
90impl Default for LibSearchDirs {
91 fn default() -> Self {
92 LibSearchDirs(vec![
93 LibSearchDir::Spec(LibSearchSpec {
94 kind: LibSearchSpecKind::CurrentExeParent,
95 value: None,
96 }),
97 LibSearchDir::Path(".".to_string()),
98 LibSearchDir::Path("~/.zenoh/lib".to_string()),
99 LibSearchDir::Path("/opt/homebrew/lib".to_string()),
100 LibSearchDir::Path("/usr/local/lib".to_string()),
101 LibSearchDir::Path("/usr/lib".to_string()),
102 ])
103 }
104}
105
106#[derive(Clone, Debug, Eq, Hash, PartialEq)]
107pub enum LibSearchDir {
108 Path(String),
109 Spec(LibSearchSpec),
110}
111
112impl LibSearchDir {
113 fn into_path(self) -> Result<PathBuf, InvalidLibSearchDir> {
114 match self {
115 LibSearchDir::Path(path) => LibSearchSpec {
116 kind: LibSearchSpecKind::Path,
117 value: Some(path),
118 }
119 .into_path(),
120 LibSearchDir::Spec(spec) => spec.into_path(),
121 }
122 }
123}
124
125#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash, PartialEq)]
126#[serde(rename_all = "snake_case")]
127pub struct LibSearchSpec {
128 kind: LibSearchSpecKind,
129 value: Option<String>,
130}
131
132impl LibSearchSpec {
133 fn into_path(self) -> Result<PathBuf, InvalidLibSearchDir> {
134 fn error_from_source<T: Error>(spec: &LibSearchSpec, err: T) -> InvalidLibSearchDir {
135 InvalidLibSearchDir {
136 found: LibSearchDir::Spec(spec.clone()),
137 source: err.to_string(),
138 }
139 }
140
141 fn error_from_str(spec: &LibSearchSpec, err: &str) -> InvalidLibSearchDir {
142 InvalidLibSearchDir {
143 found: LibSearchDir::Spec(spec.clone()),
144 source: err.to_string(),
145 }
146 }
147
148 match self.kind {
149 LibSearchSpecKind::Path => {
150 let Some(value) = &self.value else {
151 return Err(error_from_str(
152 &self,
153 "`path` specs should have a `value` field",
154 ));
155 };
156
157 let expanded =
158 shellexpand::full(value).map_err(|err| error_from_source(&self, err))?;
159
160 let path =
161 PathBuf::from_str(&expanded).map_err(|err| error_from_source(&self, err))?;
162
163 Ok(path)
164 }
165 LibSearchSpecKind::CurrentExeParent => {
166 let current_exe =
167 env::current_exe().map_err(|err| error_from_source(&self, err))?;
168
169 let Some(current_exe_parent) = current_exe.parent() else {
170 return Err(error_from_str(
171 &self,
172 "current executable's path has no parent directory",
173 ));
174 };
175
176 let canonicalized = current_exe_parent
177 .canonicalize()
178 .map_err(|err| error_from_source(&self, err))?;
179
180 Ok(canonicalized)
181 }
182 }
183 }
184}
185
186#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash, PartialEq)]
187#[serde(rename_all = "snake_case")]
188pub enum LibSearchSpecKind {
189 Path,
190 CurrentExeParent,
191}
192
193impl<'de> Deserialize<'de> for LibSearchDir {
194 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
195 where
196 D: serde::Deserializer<'de>,
197 {
198 deserializer.deserialize_any(LibSearchSpecOrPathVisitor)
199 }
200}
201
202impl Serialize for LibSearchDir {
203 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
204 where
205 S: serde::Serializer,
206 {
207 match self {
208 LibSearchDir::Path(path) => serializer.serialize_str(path),
209 LibSearchDir::Spec(spec) => spec.serialize(serializer),
210 }
211 }
212}
213
214struct LibSearchSpecOrPathVisitor;
215
216impl<'de> Visitor<'de> for LibSearchSpecOrPathVisitor {
217 type Value = LibSearchDir;
218
219 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
220 formatter.write_str("str or map with field `kind` and optionally field `value`")
221 }
222
223 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
224 where
225 E: serde::de::Error,
226 {
227 Ok(LibSearchDir::Path(v.to_string()))
228 }
229
230 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
231 where
232 A: serde::de::MapAccess<'de>,
233 {
234 LibSearchSpec::deserialize(MapAccessDeserializer::new(map)).map(LibSearchDir::Spec)
235 }
236}