use anyhow::{Context, Result};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedUrl(pub String);
pub fn extract_raw_header_url(html: &str) -> Result<&str> {
let lhs_search = r#"<meta name="bmstable" content=""#;
let lhs = html.find(lhs_search).context("missing bmstable meta")?;
let html = &html[lhs + lhs_search.len()..];
let rhs = html.find('"').context("missing bmstable meta rhs")?;
Ok(&html[..rhs])
}
impl TryFrom<&str> for ResolvedUrl {
type Error = anyhow::Error;
fn try_from(url: &str) -> Result<Self> {
Self::validate(url)?;
Ok(Self(url.trim_matches(' ').to_string()))
}
}
impl TryFrom<String> for ResolvedUrl {
type Error = anyhow::Error;
fn try_from(url: String) -> Result<Self> {
Self::try_from(url.as_str())
}
}
impl ResolvedUrl {
pub fn resolve_json_url(&self, raw_url: &str) -> Result<Self> {
if let Ok(url) = raw_url.try_into() {
return Ok(url);
}
let last_slash = self.0.rfind(['/', '\\']).context("No slash in URL")?;
let url_prefix = &self.0[..=last_slash];
format!("{url_prefix}{raw_url}")
.try_into()
.context("don't expect this to fail actually")
}
pub fn validate(url: &str) -> Result<()> {
let url = url.trim_matches(' ');
anyhow::ensure!(
url.starts_with("https://") || url.starts_with("http://") || url.starts_with("file://"),
"Invalid protocol in table URL: {url}"
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use test_log::test;
#[test]
fn test_extract_header_url() {
use super::extract_raw_header_url;
assert_eq!(
extract_raw_header_url(
r#"<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head>
<meta name"blahblah" /><meta name="bmstable" content="header.json" />"#
)
.ok(),
Some("header.json")
);
assert_eq!(
extract_raw_header_url(r#"<meta name="bmstable" content="header.json"/>"#).ok(),
Some("header.json")
);
assert_eq!(
format!("{}", extract_raw_header_url("").unwrap_err()),
"missing bmstable meta"
);
assert_eq!(
format!(
"{}",
extract_raw_header_url(r#"<meta name="bmstable" content="header.json"#)
.unwrap_err()
),
"missing bmstable meta rhs"
);
assert_eq!(
format!(
"{}",
extract_raw_header_url(r#"<meta name="bmstable" content="header.json" />"#)
.unwrap_err()
),
"missing bmstable meta"
);
}
#[test]
fn test_json_table_header_score_url() {
use crate::ResolvedUrl;
assert_eq!(
ResolvedUrl::try_from("https://stellabms.xyz/fr/table.html")
.unwrap()
.resolve_json_url("score.json")
.ok()
.map(|url| url.0)
.as_deref(),
Some("https://stellabms.xyz/fr/score.json")
);
assert_eq!(
ResolvedUrl::try_from("http://flowermaster.web.fc2.com/lrnanido/gla/LN.html")
.unwrap()
.resolve_json_url("http://flowermaster.web.fc2.com/lrnanido/gla/score.json")
.ok()
.map(|url| url.0)
.as_deref(),
Some("http://flowermaster.web.fc2.com/lrnanido/gla/score.json")
);
assert_eq!(
ResolvedUrl::try_from("file:///a/b")
.unwrap()
.resolve_json_url("file:///c")
.ok()
.map(|url| url.0)
.as_deref(),
Some("file:///c")
);
assert_eq!(
ResolvedUrl::try_from("file://a/b")
.unwrap()
.resolve_json_url("c")
.ok()
.map(|url| url.0)
.as_deref(),
Some("file://a/c")
);
assert_eq!(
ResolvedUrl::try_from("file://D:\\a\\b")
.unwrap()
.resolve_json_url("file://D:\\c")
.ok()
.map(|url| url.0)
.as_deref(),
Some("file://D:\\c")
);
assert_eq!(
ResolvedUrl::try_from("file://D:\\a\\b")
.unwrap()
.resolve_json_url("c")
.ok()
.map(|url| url.0)
.as_deref(),
Some("file://D:\\a\\c")
);
}
#[test]
fn resolved_url() {
use crate::ResolvedUrl;
assert_eq!(ResolvedUrl::try_from(" file:// ").unwrap().0, "file://");
assert_eq!(ResolvedUrl::try_from(" http:// ").unwrap().0, "http://");
assert_eq!(ResolvedUrl::try_from(" https:// ").unwrap().0, "https://");
}
}