Skip to main content

loadsmith_core/
url.rs

1use std::{fmt::Display, str::FromStr};
2
3use camino::Utf8PathBuf;
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7use crate::{Error, Result};
8
9/// A URL or local file path that points to a package source.
10///
11/// The parser accepts both proper URLs (`"https://..."`) and `file://`
12/// prefixed local paths.
13///
14/// ```rust
15/// # use loadsmith_core::FileUrl;
16/// let url = FileUrl::try_from_url(
17///     "https://thunderstore.io/package/download/denikson/BepInExPack_Valheim/5.4.2202/",
18/// ).unwrap();
19/// assert!(url.to_string().starts_with("https://"));
20///
21/// let local: FileUrl = "file:///home/user/.config/loadsmith/cache/BepInExPack_Valheim-5.4.2202.zip"
22///     .parse().unwrap();
23/// assert_eq!(
24///     local.to_string(),
25///     "file:///home/user/.config/loadsmith/cache/BepInExPack_Valheim-5.4.2202.zip",
26/// );
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(into = "String", try_from = "String")]
30pub enum FileUrl {
31    Url(Url),
32    Path(Utf8PathBuf),
33}
34
35impl FileUrl {
36    /// Parse a string as a proper URL.
37    ///
38    /// This does not recognise `file://` prefixes. To handle both forms,
39    /// use [`FromStr`](str::parse) instead.
40    ///
41    /// ```rust
42    /// # use loadsmith_core::FileUrl;
43    /// let url = FileUrl::try_from_url("https://thunderstore.io/api/v1/package/").unwrap();
44    /// assert!(url.to_string().starts_with("https://"));
45    /// ```
46    pub fn try_from_url(s: &str) -> Result<Self> {
47        let url = Url::parse(s)?;
48        Ok(Self::Url(url))
49    }
50}
51
52impl Display for FileUrl {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            FileUrl::Url(url) => write!(f, "{url}"),
56            FileUrl::Path(path) => write!(f, "file://{path}"),
57        }
58    }
59}
60
61impl From<FileUrl> for String {
62    fn from(source: FileUrl) -> Self {
63        source.to_string()
64    }
65}
66
67impl FromStr for FileUrl {
68    type Err = Error;
69
70    fn from_str(s: &str) -> Result<Self> {
71        if let Some(path) = s.strip_prefix("file://") {
72            Ok(FileUrl::Path(Utf8PathBuf::from(path)))
73        } else {
74            FileUrl::try_from_url(s)
75        }
76    }
77}
78
79impl TryFrom<String> for FileUrl {
80    type Error = Error;
81
82    fn try_from(s: String) -> Result<Self> {
83        s.parse()
84    }
85}
86
87impl From<Url> for FileUrl {
88    fn from(url: Url) -> Self {
89        Self::Url(url)
90    }
91}
92
93impl From<Utf8PathBuf> for FileUrl {
94    fn from(path: Utf8PathBuf) -> Self {
95        Self::Path(path)
96    }
97}