use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use url::{Url, form_urlencoded};
use aptos_openapi_link::impl_poem_type;
impl_poem_type!(UriQuilt, "object", ());
impl_poem_type!(UriParser, "object", ());
const LATEST_TAG: &str = "latest";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "_tag", content = "value")]
pub enum RevisionPointer {
Hash(String),
Tag(String),
}
impl Default for RevisionPointer {
fn default() -> Self {
Self::Tag(String::from(LATEST_TAG))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct UriQuilt {
pub domain: String,
pub namespace: String,
pub revision: RevisionPointer,
pub path: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct UriParser {
pub scheme: String,
pub host: String,
pub path: String,
pub query: HashMap<String, String>,
pub fragments: HashMap<String, String>,
pub quilt: Option<UriQuilt>
}
fn make_domain(uri_parser: &UriParser) -> String {
let mut domain = uri_parser.scheme.clone();
domain.push_str("://");
domain.push_str(&uri_parser.host);
domain.push_str(&uri_parser.path);
domain
}
impl TryFrom<&UriParser> for UriQuilt {
type Error = String;
fn try_from(uri_parser: &UriParser) -> Result<Self, Self::Error> {
let domain = make_domain(uri_parser);
let path = uri_parser.fragments.get("path").unwrap_or(&"".to_string()).clone();
let pkg_spec = uri_parser.fragments.get("package").unwrap_or(&"".to_string()).clone();
let (namespace, revision) = match pkg_spec.split_once(['@',':']) {
Some((namespace, top_hash)) => (
namespace.to_string(),
RevisionPointer::Hash(top_hash.into()),
),
None => (pkg_spec, RevisionPointer::default()),
};
Ok(Self {
domain,
namespace,
revision,
path,
})
}
}
fn mapify(input: &str) -> HashMap<String, String> {
form_urlencoded::parse(input.as_bytes())
.into_owned()
.collect()
}
fn normalize_input(input: &String) -> String {
let split = input.split_once("://");
if split.is_some() {
return input.to_string();
}
let body = if input.starts_with("/") {
input.to_string()
} else {
let cwd = std::env::current_dir().unwrap();
if input.starts_with("./") {
let body = input[2..].to_string();
format!("{}/{}", cwd.to_string_lossy(), body)
} else {
format!("{}/{}", cwd.to_string_lossy(), input)
}
};
format!("file://{}", body)
}
impl TryFrom<&String> for UriParser {
type Error = String;
fn try_from(input: &String) -> Result<Self, Self::Error> {
let uri_string = normalize_input(input);
let parsed_url = Url::parse(&uri_string).map_err(|err| err.to_string())?;
let scheme = parsed_url.scheme().to_string();
let is_quilt = scheme.starts_with("quilt+");
let host = parsed_url.host_str().unwrap_or("").to_string();
let path = parsed_url.path().to_string();
let query_string = parsed_url.query().unwrap_or("");
let query = mapify(query_string);
let fragment_string = parsed_url.fragment().unwrap_or("");
let fragments = mapify(fragment_string);
let mut parsed: Self = Self {
scheme,
host,
path,
query,
fragments,
quilt: None,
};
if !is_quilt {
return Ok(parsed);
}
let quilt: Option<UriQuilt> = UriQuilt::try_from(&parsed).ok();
parsed.quilt = quilt;
Ok(parsed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_from_uri() {
let input = "quilt+s3://example.com?param1=value1¶m2=value2#package=my/package&path=my_path";
let result = UriParser::try_from(&input.to_string());
assert!(result.is_ok());
let uri_parser = result.unwrap();
assert_eq!(uri_parser.scheme, "quilt+s3");
assert_eq!(uri_parser.host, "example.com");
assert_eq!(uri_parser.query.get("param1"), Some(&"value1".to_string()));
assert_eq!(uri_parser.query.get("param2"), Some(&"value2".to_string()));
assert_eq!(uri_parser.path, "");
assert_eq!(uri_parser.fragments.len(), 2);
let quilt = uri_parser.quilt.unwrap();
assert_eq!(quilt.domain, "quilt+s3://example.com");
assert_eq!(quilt.namespace, "my/package");
assert_eq!(quilt.revision, RevisionPointer::default());
assert_eq!(quilt.path, "my_path");
}
#[test]
fn test_try_from_relative() {
let input = "./my_domain/folder";
let result = UriParser::try_from(&input.to_string());
assert!(result.is_ok());
let uri_parser = result.unwrap();
assert_eq!(uri_parser.scheme, "file");
assert_eq!(uri_parser.host, "");
assert_eq!(uri_parser.query.len(), 0);
assert!(uri_parser.path.ends_with("/my_domain/folder"));
assert_eq!(uri_parser.fragments.len(), 0);
assert_eq!(uri_parser.quilt, None);
}
}