use url::Url;
use crate::{DeltaResult, Error};
pub(crate) fn resolve_amt_location(path: &str, table_root: &Url) -> DeltaResult<Url> {
if has_scheme(path) {
Url::parse(path).map_err(|e| {
Error::generic(format!(
"Failed to parse absolute AMT location {path:?}: {e}"
))
})
} else {
let mut base = table_root.as_str().to_string();
if !base.ends_with('/') {
base.push('/');
}
Url::parse(&format!("{base}{path}")).map_err(|e| {
Error::generic(format!(
"Failed to resolve relative AMT location {path:?} against table root {base}: {e}"
))
})
}
}
fn has_scheme(location: &str) -> bool {
for (position, ch) in location.char_indices() {
if ch == ':' {
return position > 0;
}
if !is_scheme_char(ch, position) {
return false;
}
}
false
}
fn is_scheme_char(ch: char, position: usize) -> bool {
if ch.is_ascii_alphabetic() {
return true;
}
position > 0 && (ch.is_ascii_digit() || ch == '+' || ch == '-' || ch == '.')
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case::relative_path(
"memory:///table/",
"metadata/root.parquet",
"memory:///table/metadata/root.parquet"
)]
#[case::absolute_path(
"memory:///table/",
"s3://bucket/table/metadata/root.parquet",
"s3://bucket/table/metadata/root.parquet"
)]
#[case::table_root_without_trailing_slash_gets_one(
"memory:///table",
"metadata/root.parquet",
"memory:///table/metadata/root.parquet"
)]
#[case::single_char_scheme_treated_as_absolute(
"memory:///table/",
"c:/foo/root.parquet",
"c:/foo/root.parquet"
)]
#[case::colon_in_relative_segment_stays_relative(
"memory:///table/",
"metadata/snap-123:456.parquet",
"memory:///table/metadata/snap-123:456.parquet"
)]
#[case::leading_digit_scheme_treated_as_relative(
"memory:///table/",
"3com/root.parquet",
"memory:///table/3com/root.parquet"
)]
#[case::non_ascii_scheme_treated_as_relative(
"memory:///table/",
"\u{03b1}scheme/root.parquet",
"memory:///table/%CE%B1scheme/root.parquet"
)]
#[case::compound_scheme_treated_as_absolute(
"memory:///table/",
"git+ssh://host/repo/root.parquet",
"git+ssh://host/repo/root.parquet"
)]
fn test_resolve_amt_location(
#[case] table_root: &str,
#[case] path: &str,
#[case] expected_location: &str,
) {
let table_root = Url::parse(table_root).unwrap();
let location = resolve_amt_location(path, &table_root).unwrap();
assert_eq!(location.as_str(), expected_location);
}
}