alpm_types/path.rs
1use std::{
2 fmt::{Display, Formatter},
3 path::{MAIN_SEPARATOR_STR, Path, PathBuf},
4 str::FromStr,
5};
6
7use alpm_parsers::prelude::*;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10#[cfg(feature = "serde")]
11use serde_with::DeserializeFromStr;
12use winnow::{
13 combinator::{alt, eof, peek, repeat_till},
14 error::ErrMode,
15 token::any,
16};
17
18use crate::{Error, SharedLibraryPrefix};
19
20/// A representation of an absolute path
21///
22/// AbsolutePath wraps a `PathBuf`, that is guaranteed to be absolute.
23///
24/// ## Examples
25/// ```
26/// use std::{path::PathBuf, str::FromStr};
27///
28/// use alpm_types::{AbsolutePath, Error};
29///
30/// # fn main() -> Result<(), alpm_types::Error> {
31/// // Create AbsolutePath from &str
32/// assert_eq!(
33/// AbsolutePath::from_str("/")?,
34/// AbsolutePath::new(PathBuf::from("/"))?
35/// );
36/// assert!(AbsolutePath::from_str("./").is_err());
37///
38/// // Format as String
39/// assert_eq!("/", AbsolutePath::from_str("/")?.to_string());
40/// # Ok(())
41/// # }
42/// ```
43#[derive(Clone, Debug, Eq, PartialEq)]
44#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
45pub struct AbsolutePath(PathBuf);
46
47impl AbsolutePath {
48 /// Create a new `AbsolutePath`
49 pub fn new(path: PathBuf) -> Result<AbsolutePath, Error> {
50 match path.is_absolute() {
51 true => Ok(AbsolutePath(path)),
52 false => Err(Error::PathNotAbsolute(path)),
53 }
54 }
55}
56
57impl AsRef<Path> for AbsolutePath {
58 /// Returns a reference to the inner [`Path`].
59 fn as_ref(&self) -> &Path {
60 &self.0
61 }
62}
63
64impl FromStr for AbsolutePath {
65 type Err = Error;
66
67 /// Parses an absolute path from a string
68 ///
69 /// # Errors
70 ///
71 /// Returns an error if the path is not absolute
72 fn from_str(s: &str) -> Result<AbsolutePath, Self::Err> {
73 match Path::new(s).is_absolute() {
74 true => Ok(AbsolutePath(PathBuf::from(s))),
75 false => Err(Error::PathNotAbsolute(PathBuf::from(s))),
76 }
77 }
78}
79
80impl Display for AbsolutePath {
81 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
82 write!(fmt, "{}", self.as_ref().display())
83 }
84}
85
86/// An absolute path used as build directory
87///
88/// This is a type alias for [`AbsolutePath`]
89///
90/// ## Examples
91/// ```
92/// use std::str::FromStr;
93///
94/// use alpm_types::{Error, BuildDirectory};
95///
96/// # fn main() -> Result<(), alpm_types::Error> {
97/// // Create BuildDirectory from &str and format it
98/// assert_eq!(
99/// "/etc",
100/// BuildDirectory::from_str("/etc")?.to_string()
101/// );
102/// # Ok(())
103/// # }
104pub type BuildDirectory = AbsolutePath;
105
106/// An absolute path used as start directory in a package build environment
107///
108/// This is a type alias for [`AbsolutePath`]
109///
110/// ## Examples
111/// ```
112/// use std::str::FromStr;
113///
114/// use alpm_types::{Error, StartDirectory};
115///
116/// # fn main() -> Result<(), alpm_types::Error> {
117/// // Create StartDirectory from &str and format it
118/// assert_eq!(
119/// "/etc",
120/// StartDirectory::from_str("/etc")?.to_string()
121/// );
122/// # Ok(())
123/// # }
124pub type StartDirectory = AbsolutePath;
125
126/// A representation of a relative path
127///
128/// [`RelativePath`] wraps a [`PathBuf`] that is guaranteed to represent a relative path, regardless
129/// of whether it points to a file or a directory.
130///
131/// ## Examples
132///
133/// ```
134/// use std::{path::PathBuf, str::FromStr};
135///
136/// use alpm_types::{Error, RelativePath};
137///
138/// # fn main() -> Result<(), alpm_types::Error> {
139/// // Create RelativePath from &str
140/// assert_eq!(
141/// RelativePath::from_str("etc/test.conf")?,
142/// RelativePath::new(PathBuf::from("etc/test.conf"))?
143/// );
144/// assert_eq!(
145/// RelativePath::from_str("etc/")?,
146/// RelativePath::new(PathBuf::from("etc/"))?
147/// );
148/// assert!(RelativePath::from_str("/etc/test.conf").is_err());
149///
150/// // Format as String
151/// assert_eq!("test/", RelativePath::from_str("test/")?.to_string());
152/// # Ok(())
153/// # }
154/// ```
155#[derive(Clone, Debug, Eq, Hash, PartialEq)]
156#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
157pub struct RelativePath(PathBuf);
158
159impl RelativePath {
160 /// Create a new [`RelativePath`]
161 pub fn new(path: PathBuf) -> Result<RelativePath, Error> {
162 if !path.is_relative() {
163 return Err(Error::PathNotRelative(path));
164 }
165 Ok(RelativePath(path))
166 }
167
168 /// Consume `self` and return the inner [`PathBuf`]
169 pub fn into_inner(self) -> PathBuf {
170 self.0
171 }
172}
173
174impl AsRef<Path> for RelativePath {
175 fn as_ref(&self) -> &Path {
176 &self.0
177 }
178}
179
180impl FromStr for RelativePath {
181 type Err = Error;
182
183 /// Parses a relative path from a string
184 ///
185 /// # Errors
186 ///
187 /// Returns an error if the path is not relative.
188 fn from_str(s: &str) -> Result<RelativePath, Self::Err> {
189 Self::new(PathBuf::from(s))
190 }
191}
192
193impl Display for RelativePath {
194 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
195 write!(fmt, "{}", self.as_ref().display())
196 }
197}
198
199/// A representation of a relative file path
200///
201/// `RelativeFilePath` wraps a `PathBuf` that is guaranteed to represent a
202/// relative file path (i.e. it does not end with a `/`).
203///
204/// ## Examples
205///
206/// ```
207/// use std::{path::PathBuf, str::FromStr};
208///
209/// use alpm_types::{Error, RelativeFilePath};
210///
211/// # fn main() -> Result<(), alpm_types::Error> {
212/// // Create RelativeFilePath from &str
213/// assert_eq!(
214/// RelativeFilePath::from_str("etc/test.conf")?,
215/// RelativeFilePath::new(PathBuf::from("etc/test.conf"))?
216/// );
217/// assert!(RelativeFilePath::from_str("/etc/test.conf").is_err());
218///
219/// // Format as String
220/// assert_eq!(
221/// "test/test.txt",
222/// RelativeFilePath::from_str("test/test.txt")?.to_string()
223/// );
224/// # Ok(())
225/// # }
226/// ```
227#[derive(Clone, Debug, Eq, Hash, PartialEq)]
228#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
229pub struct RelativeFilePath(PathBuf);
230
231impl RelativeFilePath {
232 /// Create a new `RelativeFilePath`
233 pub fn new(path: PathBuf) -> Result<RelativeFilePath, Error> {
234 if path.to_string_lossy().ends_with(MAIN_SEPARATOR_STR) {
235 return Err(Error::PathIsNotAFile(path));
236 }
237 if !path.is_relative() {
238 return Err(Error::PathNotRelative(path));
239 }
240 Ok(RelativeFilePath(path))
241 }
242}
243
244impl AsRef<Path> for RelativeFilePath {
245 /// Returns a reference to the inner [`Path`].
246 fn as_ref(&self) -> &Path {
247 &self.0
248 }
249}
250
251impl FromStr for RelativeFilePath {
252 type Err = Error;
253
254 /// Parses a relative path from a string
255 ///
256 /// # Errors
257 ///
258 /// Returns an error if the path is not relative
259 fn from_str(s: &str) -> Result<RelativeFilePath, Self::Err> {
260 Self::new(PathBuf::from(s))
261 }
262}
263
264impl Display for RelativeFilePath {
265 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
266 write!(fmt, "{}", self.as_ref().display())
267 }
268}
269
270/// The basename of a path.
271///
272/// Wraps a [`PathBuf`] that is guaranteed to represent a relative
273/// local (one level deep) file path (i.e. it does not contain a `/`).
274///
275/// ## Examples
276///
277/// ```
278/// use std::{path::PathBuf, str::FromStr};
279///
280/// use alpm_types::{Basename, Error};
281///
282/// # fn main() -> Result<(), alpm_types::Error> {
283/// // Create Basename from &str
284/// assert_eq!(
285/// Basename::from_str("test.conf")?,
286/// Basename::new(PathBuf::from("test.conf"))?
287/// );
288/// assert!(Basename::from_str("etc/test.conf").is_err(),);
289///
290/// // Format as String
291/// assert_eq!("test.txt", Basename::from_str("test.txt")?.to_string());
292/// # Ok(())
293/// # }
294/// ```
295#[derive(Clone, Debug, Eq, Hash, PartialEq)]
296#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
297pub struct Basename(PathBuf);
298
299impl Basename {
300 /// Create a new [`Basename`].
301 ///
302 /// # Errors
303 ///
304 /// Returns an error, if `path`
305 ///
306 /// - ends with a [`MAIN_SEPARATOR_STR`]
307 /// - is relative
308 /// - has more than one component
309 pub fn new(path: PathBuf) -> Result<Self, Error> {
310 if path.to_string_lossy().ends_with(MAIN_SEPARATOR_STR) {
311 return Err(Error::PathIsNotAFile(path));
312 }
313 if !path.is_relative() {
314 return Err(Error::PathNotSibling(path));
315 }
316 // Make sure the path only consists of a single filename.
317 if path.iter().count() != 1 {
318 return Err(Error::PathNotSibling(path));
319 }
320
321 Ok(Self(path))
322 }
323}
324
325impl AsRef<Path> for Basename {
326 /// Returns a reference to the inner [`Path`].
327 fn as_ref(&self) -> &Path {
328 &self.0
329 }
330}
331
332impl FromStr for Basename {
333 type Err = Error;
334
335 /// Parses a sibling file path from a string
336 ///
337 /// # Errors
338 ///
339 /// Returns an error if the path is not relative or contains a directory separator.
340 fn from_str(s: &str) -> Result<Basename, Self::Err> {
341 Self::new(PathBuf::from(s))
342 }
343}
344
345impl Display for Basename {
346 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
347 write!(fmt, "{}", self.as_ref().display())
348 }
349}
350
351/// The path of a packaged file that should be preserved during package operations
352///
353/// This is a type alias for [`RelativeFilePath`]
354///
355/// ## Examples
356/// ```
357/// use std::str::FromStr;
358///
359/// use alpm_types::Backup;
360///
361/// # fn main() -> Result<(), alpm_types::Error> {
362/// // Create Backup from &str and format it
363/// assert_eq!(
364/// "etc/test.conf",
365/// Backup::from_str("etc/test.conf")?.to_string()
366/// );
367/// # Ok(())
368/// # }
369pub type Backup = RelativeFilePath;
370
371/// A special install script that is to be included in the package
372///
373/// This is a type alias for [RelativeFilePath`]
374///
375/// ## Examples
376/// ```
377/// use std::str::FromStr;
378///
379/// use alpm_types::{Error, Install};
380///
381/// # fn main() -> Result<(), alpm_types::Error> {
382/// // Create Install from &str and format it
383/// assert_eq!(
384/// "scripts/setup.install",
385/// Install::from_str("scripts/setup.install")?.to_string()
386/// );
387/// # Ok(())
388/// # }
389pub type Install = RelativeFilePath;
390
391/// The relative path to a changelog file that may be included in a package
392///
393/// This is a type alias for [`RelativeFilePath`]
394///
395/// ## Examples
396/// ```
397/// use std::str::FromStr;
398///
399/// use alpm_types::{Error, Changelog};
400///
401/// # fn main() -> Result<(), alpm_types::Error> {
402/// // Create Changelog from &str and format it
403/// assert_eq!(
404/// "changelog.md",
405/// Changelog::from_str("changelog.md")?.to_string()
406/// );
407/// # Ok(())
408/// # }
409pub type Changelog = RelativeFilePath;
410
411/// A lookup directory for shared object files.
412///
413/// Follows the [alpm-sonamev2] format, which encodes a `prefix` and a `directory`.
414/// The same `prefix` is later used to identify the location of a **soname**, see
415/// [`SonameV2`][crate::SonameV2].
416///
417/// [alpm-sonamev2]: https://alpm.archlinux.page/specifications/alpm-sonamev2.7.html
418#[derive(Clone, Debug, Eq, PartialEq)]
419#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
420pub struct SonameLookupDirectory {
421 /// The lookup prefix for shared objects.
422 pub prefix: SharedLibraryPrefix,
423 /// The directory to look for shared objects in.
424 pub directory: AbsolutePath,
425}
426
427impl SonameLookupDirectory {
428 /// Creates a new lookup directory with a prefix and a directory.
429 ///
430 /// # Examples
431 ///
432 /// ```
433 /// use alpm_types::SonameLookupDirectory;
434 ///
435 /// # fn main() -> Result<(), alpm_types::Error> {
436 /// SonameLookupDirectory::new("lib".parse()?, "/usr/lib".parse()?);
437 /// # Ok(())
438 /// # }
439 /// ```
440 pub fn new(prefix: SharedLibraryPrefix, directory: AbsolutePath) -> Self {
441 Self { prefix, directory }
442 }
443}
444
445impl ParserUntil for SonameLookupDirectory {
446 /// Parses a [`SonameLookupDirectory`] from a string slice.
447 ///
448 /// # Errors
449 ///
450 /// Returns an error, if the parser input does not contain a valid [`SonameLookupDirectory`]
451 /// before the `delimiter`.
452 fn parser_until<'a, P>(delimiter: P) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>>
453 where
454 P: Parser<Input<'a>, &'a str, ErrMode<ParseStack<'a>>>,
455 {
456 // Define the actual parser closure.
457 // The delimiter is moved into the closure and borrowed via `by_ref()` on each call.
458 let mut delimiter_parser = delimiter;
459 let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
460 // Parse until the first `:`, which separates the prefix from the directory.
461 let prefix = repeat_till(1.., any, peek(alt((":", eof))))
462 .try_map(|(name, _): (String, &str)| SharedLibraryPrefix::from_str(&name))
463 .label("prefix for a shared object lookup path")
464 .parse_next(input)?;
465
466 // Take the delimiter.
467 ":".label("delimiter")
468 .expected_text("shared library delimiter `:`")
469 .parse_next(input)?;
470
471 // Parse the rest as a directory.
472 let directory = repeat_till(1.., any, peek(delimiter_parser.by_ref()))
473 .try_map(|(path, _): (String, &str)| AbsolutePath::from_str(&path))
474 .label("directory")
475 .expected_text("directory for a shared object lookup path")
476 .parse_next(input)?;
477
478 peek(delimiter_parser.by_ref())
479 .label("SonameLookupDirectory")
480 .expected_text("valid end of input.")
481 .parse_next(input)?;
482
483 Ok(Self { prefix, directory })
484 };
485
486 parser.layer("soname lookup directory")
487 }
488}
489
490impl Display for SonameLookupDirectory {
491 /// Converts the [`SonameLookupDirectory`] to a string.
492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493 write!(f, "{}:{}", self.prefix, self.directory)
494 }
495}
496
497impl FromStr for SonameLookupDirectory {
498 type Err = Error;
499
500 /// Creates a [`SonameLookupDirectory`] from a string slice.
501 ///
502 /// Delegates to [`SonameLookupDirectory::parser_until`].
503 ///
504 /// # Errors
505 ///
506 /// Returns an error if [`SonameLookupDirectory::parser_until`] fails.
507 ///
508 /// # Examples
509 ///
510 /// ```
511 /// use std::str::FromStr;
512 ///
513 /// use alpm_types::SonameLookupDirectory;
514 ///
515 /// # fn main() -> Result<(), alpm_types::Error> {
516 /// let dir = SonameLookupDirectory::from_str("lib:/usr/lib")?;
517 /// assert_eq!(dir.to_string(), "lib:/usr/lib");
518 /// assert!(SonameLookupDirectory::from_str(":/usr/lib").is_err());
519 /// assert!(SonameLookupDirectory::from_str(":/usr/lib").is_err());
520 /// assert!(SonameLookupDirectory::from_str("lib:").is_err());
521 /// # Ok(())
522 /// # }
523 /// ```
524 fn from_str(s: &str) -> Result<Self, Self::Err> {
525 Ok(Self::parser_until_eof.parse(Input::new(s))?)
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use insta::assert_snapshot;
532 use rstest::rstest;
533 use testresult::TestResult;
534
535 use super::*;
536 use crate::configure_insta;
537
538 /// Ensures, that [`BuildDirectory::from_str`] succeeds on valid input.
539 #[test]
540 fn build_dir_from_str_succeeds() -> TestResult {
541 let _ = BuildDirectory::from_str("/home")?;
542
543 Ok(())
544 }
545
546 /// Ensures, that [`BuildDirectory::from_str`] fails on invalid input.
547 #[rstest]
548 #[case("./")]
549 #[case("~/")]
550 #[case("foo.txt")]
551 fn build_dir_from_str_fails_on_invalid_input(#[case] s: &str) -> TestResult {
552 let Err(Error::PathNotAbsolute(failing_path)) = BuildDirectory::from_str(s) else {
553 panic!("Expected to fail with Error::PathNotAbsolute");
554 };
555
556 assert_eq!(PathBuf::from(s), failing_path);
557
558 Ok(())
559 }
560
561 /// Make sure that relative paths don't deserialize as absolute paths.
562 #[cfg(feature = "serde")]
563 #[rstest]
564 #[case::relative_path("./")]
565 #[case::relative_file("foo.txt")]
566 fn absolute_path_deserialize_error(#[case] input: &str) {
567 let Err(serde_json::Error { .. }) =
568 serde_json::from_str::<AbsolutePath>(&format!("\"{input}\""))
569 else {
570 panic!("'{input}' erroneously deserialized as an AbsolutePath")
571 };
572 }
573
574 /// Ensures, that [`StartDirectory::from_str`] succeeds on valid input.
575 #[test]
576 fn startdir_from_str_succeeds() -> TestResult {
577 let _ = StartDirectory::from_str("/start")?;
578
579 Ok(())
580 }
581
582 /// Ensures, that [`StartDirectory::from_str`] fails on invalid input.
583 #[rstest]
584 #[case("./")]
585 #[case("~/")]
586 #[case("foo.txt")]
587 fn startdir_from_str_fails_on_invalid_input(#[case] s: &str) -> TestResult {
588 let Err(Error::PathNotAbsolute(failing_path)) = StartDirectory::from_str(s) else {
589 panic!("Expected to fail with Error::PathNotAbsolute");
590 };
591
592 assert_eq!(PathBuf::from(s), failing_path);
593
594 Ok(())
595 }
596
597 /// Ensures, that [`RelativePath::from_str`] succeeds on valid input.
598 #[rstest]
599 #[case("etc/test.conf", RelativePath::new(PathBuf::from("etc/test.conf"))?)]
600 #[case("etc/", RelativePath::new(PathBuf::from("etc/"))?)]
601 #[case(
602 "../etc/test.conf",
603 RelativePath::new(PathBuf::from("../etc/test.conf"))?
604 )]
605 fn relative_path_from_str(#[case] s: &str, #[case] expected: RelativePath) -> TestResult {
606 assert_eq!(RelativePath::from_str(s)?, expected);
607
608 Ok(())
609 }
610
611 /// Ensures, that [`RelativePath::from_str`] fails on invalid input.
612 #[test]
613 fn relative_path_from_str_fails_on_invalid_input() {
614 let Err(Error::PathNotRelative(invalid_path)) = RelativePath::from_str("/etc/test.conf")
615 else {
616 panic!("Expected to fail with Error::PathNotRelative");
617 };
618 assert_eq!(PathBuf::from("/etc/test.conf"), invalid_path);
619 }
620
621 /// Make sure that absolute paths don't deserialize as relative paths.
622 #[cfg(feature = "serde")]
623 #[rstest]
624 #[case::root("/")]
625 #[case::absolute_path("/etc")]
626 fn relative_path_deserialize_error(#[case] input: &str) {
627 let Err(serde_json::Error { .. }) =
628 serde_json::from_str::<RelativePath>(&format!("\"{input}\""))
629 else {
630 panic!("'{input}' erroneously deserialized as a RelativePath")
631 };
632 }
633
634 /// Ensures, that [`RelativeFilePath::from_str`] succeeds on valid input.
635 #[rstest]
636 #[case("etc/test.conf", RelativeFilePath::new(PathBuf::from("etc/test.conf"))?)]
637 #[case("etc", RelativeFilePath::new(PathBuf::from("etc"))?)]
638 #[case(
639 "../etc/test.conf",
640 RelativeFilePath::new(PathBuf::from("../etc/test.conf"))?
641 )]
642 fn relative_file_path_from_str_succeeds(
643 #[case] s: &str,
644 #[case] expected: RelativeFilePath,
645 ) -> TestResult {
646 assert_eq!(RelativeFilePath::from_str(s)?, expected);
647
648 Ok(())
649 }
650
651 /// Ensures, that [`RelativeFilePath::from_str`] fails on invalid input.
652 #[test]
653 fn relative_file_path_from_str_fails_on_not_relative() {
654 let input = "/etc/test.conf";
655 let Err(Error::PathNotRelative(returned_path)) = RelativeFilePath::from_str(input) else {
656 panic!("Expected to fail with Error::PathNotRelative");
657 };
658
659 assert_eq!(returned_path, PathBuf::from(input));
660 }
661
662 /// Ensures, that [`RelativeFilePath::from_str`] fails on invalid input.
663 #[test]
664 fn relative_file_path_from_str_fails_on_not_a_file() {
665 let input = "etc/";
666 let Err(Error::PathIsNotAFile(returned_path)) = RelativeFilePath::from_str(input) else {
667 panic!("Expected to fail with Error::PathIsNotAFile")
668 };
669 assert_eq!(returned_path, PathBuf::from(input));
670 }
671
672 /// Make sure that invalid relative file paths don't deserialize.
673 #[cfg(feature = "serde")]
674 #[rstest]
675 #[case::not_a_file("etc/")]
676 #[case::absolute_path("/etc/test.conf")]
677 fn relative_file_path_deserialize_error(#[case] input: &str) {
678 let Err(serde_json::Error { .. }) =
679 serde_json::from_str::<RelativeFilePath>(&format!("\"{input}\""))
680 else {
681 panic!("'{input}' erroneously deserialized as a RelativeFilePath")
682 };
683 }
684
685 #[rstest]
686 #[case("lib:/usr/lib", SonameLookupDirectory {
687 prefix: "lib".parse()?,
688 directory: AbsolutePath::from_str("/usr/lib")?,
689 })]
690 #[case("lib32:/usr/lib32", SonameLookupDirectory {
691 prefix: "lib32".parse()?,
692 directory: AbsolutePath::from_str("/usr/lib32")?,
693 })]
694 fn soname_lookup_directory_from_string(
695 #[case] input: &str,
696 #[case] expected_result: SonameLookupDirectory,
697 ) -> TestResult {
698 let lookup_directory = SonameLookupDirectory::from_str(input)?;
699 assert_eq!(expected_result, lookup_directory);
700 assert_eq!(input, lookup_directory.to_string());
701 Ok(())
702 }
703
704 #[rstest]
705 #[case("lib")]
706 #[case("lib:")]
707 #[case(":/usr/lib")]
708 fn invalid_soname_lookup_directory_parser(#[case] input: &str) {
709 let (test_name, _guard) = configure_insta();
710
711 let Err(Error::ParseError(err_msg)) = SonameLookupDirectory::from_str(input) else {
712 panic!("'{input}' erroneously parsed as a SonameLookupDirectory")
713 };
714
715 assert_snapshot!(test_name, err_msg.to_string());
716 }
717}