zenoh_util/
lib_search_dirs.rs

1//
2// Copyright (c) 2024 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14use 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}