82 lines
2.1 KiB
Rust
82 lines
2.1 KiB
Rust
use serde::{de::Visitor, Deserialize, Serialize};
|
|
use std::str::FromStr;
|
|
use thiserror::Error;
|
|
use url::Url;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug)]
|
|
pub enum Link {
|
|
File(PathBuf),
|
|
Http(Url),
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum LinkParseError {
|
|
#[error("Unknown protocol")]
|
|
UnkownProtocol,
|
|
#[error("Error parsing url: {0}")]
|
|
UrlParseError(url::ParseError),
|
|
}
|
|
|
|
impl FromStr for Link {
|
|
type Err = LinkParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
if s.starts_with("file://") {
|
|
Ok(Link::File(PathBuf::from(s.trim_start_matches("file://"))))
|
|
} else if s.starts_with("http://") || s.starts_with("https://") {
|
|
s.parse::<Url>()
|
|
.map(Link::Http)
|
|
.map_err(LinkParseError::UrlParseError)
|
|
} else {
|
|
Err(LinkParseError::UnkownProtocol)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToString for Link {
|
|
fn to_string(&self) -> String {
|
|
match self {
|
|
Link::File(path) => format!("file://{}", path.to_string_lossy()),
|
|
Link::Http(url) => url.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for Link {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&self.to_string())
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Link {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
struct Vis;
|
|
impl<'de> Visitor<'de> for Vis {
|
|
type Value = Link;
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
formatter.write_str(
|
|
"either a (somewhat invalid) relative file:// url or a http/https url",
|
|
)
|
|
}
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: serde::de::Error,
|
|
{
|
|
v.parse::<Link>()
|
|
.map_err(|e| E::custom(format!("invalid link: {}", e)))
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_str(Vis)
|
|
}
|
|
}
|