use std::fmt::Display;
use serde::{de, Deserialize, Deserializer, Serialize};
use thiserror::Error;
use url::Url;
const PLUS: &str = "+";
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub(crate) enum RemotePackageSource {
LuarocksRockspec(Url),
LuarocksSrcRock(Url),
LuarocksBinaryRock(Url),
RockspecContent(String),
Local,
#[cfg(test)]
Test,
}
impl RemotePackageSource {
pub(crate) fn url(self) -> Option<Url> {
match self {
Self::LuarocksRockspec(url)
| Self::LuarocksSrcRock(url)
| Self::LuarocksBinaryRock(url) => Some(url),
Self::RockspecContent(_) | Self::Local => None,
#[cfg(test)]
Self::Test => None,
}
}
}
impl Display for RemotePackageSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
RemotePackageSource::LuarocksRockspec(url) => {
format!("luarocks_rockspec{PLUS}{url}").fmt(f)
}
RemotePackageSource::LuarocksSrcRock(url) => {
format!("luarocks_src_rock{PLUS}{url}").fmt(f)
}
RemotePackageSource::LuarocksBinaryRock(url) => {
format!("luarocks_rock{PLUS}{url}").fmt(f)
}
RemotePackageSource::RockspecContent(content) => {
format!("rockspec{PLUS}{content}").fmt(f)
}
RemotePackageSource::Local => "local".fmt(f),
#[cfg(test)]
RemotePackageSource::Test => "test+foo_bar".fmt(f),
}
}
}
impl Serialize for RemotePackageSource {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
format!("{self}").serialize(serializer)
}
}
#[derive(Error, Debug)]
pub enum RemotePackageSourceError {
#[error("error parsing remote source URL {0}. Missing URL.")]
MissingUrl(String),
#[error("error parsing remote source URL {0}. Expected <source_type>+<url>.")]
MissingSeparator(String),
#[error("error parsing remote source type {0}. Expected 'luarocks' or 'rockspec'.")]
UnknownRemoteSourceType(String),
#[error(transparent)]
Url(#[from] url::ParseError),
}
impl TryFrom<String> for RemotePackageSource {
type Error = RemotePackageSourceError;
fn try_from(value: String) -> Result<Self, Self::Error> {
if let Some(pos) = value.find(PLUS) {
if let Some(str) = value.get(pos + 1..) {
let remote_source_type = value[..pos].into();
match remote_source_type {
"luarocks_rockspec" => Ok(Self::LuarocksRockspec(Url::parse(str)?)),
"luarocks_src_rock" => Ok(Self::LuarocksSrcRock(Url::parse(str)?)),
"luarocks_rock" => Ok(Self::LuarocksBinaryRock(Url::parse(str)?)),
"rockspec" => Ok(Self::RockspecContent(str.into())),
_ => Err(RemotePackageSourceError::UnknownRemoteSourceType(
remote_source_type.into(),
)),
}
} else {
Err(RemotePackageSourceError::MissingUrl(value))
}
} else if value == "local" {
Ok(Self::Local)
} else {
Err(RemotePackageSourceError::MissingSeparator(value))
}
}
}
impl<'de> Deserialize<'de> for RemotePackageSource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::try_from(value).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod test {
use super::*;
const LUAROCKS_ROCKSPEC: &str = "
rockspec_format = \"3.0\"
package = 'luarocks'
version = '3.11.1-1'
source = {
url = 'git+https://github.com/luarocks/luarocks',
tag = 'v3.11.1'
}
";
#[test]
fn luarocks_source_roundtrip() {
let url = Url::parse("https://luarocks.org/").unwrap();
let source = RemotePackageSource::LuarocksRockspec(url.clone());
let roundtripped = RemotePackageSource::try_from(format!("{source}")).unwrap();
assert_eq!(source, roundtripped);
let source = RemotePackageSource::LuarocksSrcRock(url.clone());
let roundtripped = RemotePackageSource::try_from(format!("{source}")).unwrap();
assert_eq!(source, roundtripped);
let source = RemotePackageSource::LuarocksBinaryRock(url);
let roundtripped = RemotePackageSource::try_from(format!("{source}")).unwrap();
assert_eq!(source, roundtripped)
}
#[test]
fn rockspec_source_roundtrip() {
let source = RemotePackageSource::RockspecContent(LUAROCKS_ROCKSPEC.into());
let roundtripped = RemotePackageSource::try_from(format!("{source}")).unwrap();
assert_eq!(source, roundtripped)
}
}