use super::uri::{ParsedUri, UriParseError};
pub(super) fn expand(uri: ParsedUri) -> Result<ParsedUri, UriParseError> {
match uri.scheme.as_str() {
"github" => github_template(uri),
"gitlab" => gitlab_template(uri),
_ => Ok(uri),
}
}
fn github_template(uri: ParsedUri) -> Result<ParsedUri, UriParseError> {
let owner_repo = uri.body.trim_start_matches('/');
match uri.via.as_deref() {
None | Some("https") => {
let rev = uri.rev.as_deref().unwrap_or("HEAD");
let expanded_str = format!("https://api.github.com/repos/{owner_repo}/tarball/{rev}");
let mut expanded = ParsedUri::parse(&expanded_str)?;
expanded.original = uri.original;
expanded.subdir = uri.subdir;
expanded.rev = uri.rev;
Ok(expanded)
}
Some("git") => {
let expanded_str = format!("git:https://github.com/{owner_repo}.git");
let mut expanded = ParsedUri::parse(&expanded_str)?;
expanded.original = uri.original;
expanded.subdir = uri.subdir;
expanded.rev = uri.rev;
Ok(expanded)
}
Some(other) => Err(UriParseError::UnsupportedVia {
value: other.to_string(),
}),
}
}
fn gitlab_template(uri: ParsedUri) -> Result<ParsedUri, UriParseError> {
let owner_repo = uri.body.trim_start_matches('/');
match uri.via.as_deref() {
None | Some("https") => {
let repo = owner_repo
.rsplit_once('/')
.map(|(_, r)| r)
.unwrap_or(owner_repo);
let rev = uri.rev.as_deref().unwrap_or("HEAD");
let rev_filename = rev.replace('/', "-");
let expanded_str = format!(
"https://gitlab.com/{owner_repo}/-/archive/{rev}/{repo}-{rev_filename}.tar.gz"
);
let mut expanded = ParsedUri::parse(&expanded_str)?;
expanded.original = uri.original;
expanded.subdir = uri.subdir;
expanded.rev = uri.rev;
Ok(expanded)
}
Some("git") => {
let expanded_str = format!("git:https://gitlab.com/{owner_repo}.git");
let mut expanded = ParsedUri::parse(&expanded_str)?;
expanded.original = uri.original;
expanded.subdir = uri.subdir;
expanded.rev = uri.rev;
Ok(expanded)
}
Some(other) => Err(UriParseError::UnsupportedVia {
value: other.to_string(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn github_no_rev_expands_to_head_tarball() {
let input = ParsedUri::parse("github:acme/lex-labels").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "https");
assert_eq!(
out.body,
"//api.github.com/repos/acme/lex-labels/tarball/HEAD"
);
assert_eq!(out.original, "github:acme/lex-labels");
}
#[test]
fn github_with_rev_expands_to_tagged_tarball() {
let input = ParsedUri::parse("github:acme/lex-labels#v1.2.0").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "https");
assert_eq!(
out.body,
"//api.github.com/repos/acme/lex-labels/tarball/v1.2.0"
);
}
#[test]
fn github_preserves_subdir_across_expansion() {
let input = ParsedUri::parse("github:acme/lex-labels?subdir=schemas").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "https");
assert_eq!(out.subdir.as_deref(), Some("schemas"));
}
#[test]
fn github_preserves_original_uri_for_diagnostics() {
let input = ParsedUri::parse("github:acme/lex-labels#main").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.original, "github:acme/lex-labels#main");
}
#[test]
fn github_preserves_rev_for_cache_immutability_check() {
let input = ParsedUri::parse("github:acme/lex-labels#v1.2.0").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.rev.as_deref(), Some("v1.2.0"));
}
#[test]
fn github_no_rev_leaves_rev_none() {
let input = ParsedUri::parse("github:acme/lex-labels").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.rev, None);
}
#[test]
fn gitlab_no_rev_expands_to_head_archive() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "https");
assert_eq!(
out.body,
"//gitlab.com/foolco/lex-labels/-/archive/HEAD/lex-labels-HEAD.tar.gz"
);
}
#[test]
fn gitlab_with_rev_expands_to_tagged_archive() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels#v2.1.0").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "https");
assert_eq!(
out.body,
"//gitlab.com/foolco/lex-labels/-/archive/v2.1.0/lex-labels-v2.1.0.tar.gz"
);
}
#[test]
fn gitlab_slashed_rev_dashes_the_filename() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels#feature/foo").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "https");
assert_eq!(
out.body,
"//gitlab.com/foolco/lex-labels/-/archive/feature/foo/lex-labels-feature-foo.tar.gz"
);
}
#[test]
fn gitlab_preserves_rev_for_cache_immutability_check() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels#v2.1.0").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.rev.as_deref(), Some("v2.1.0"));
}
#[test]
fn github_via_git_expands_to_git_clone_url() {
let input = ParsedUri::parse("github:acme/lex-labels?via=git").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "git");
assert_eq!(out.body, "https://github.com/acme/lex-labels.git");
assert_eq!(
out.via, None,
"via is a one-shot routing knob; should not propagate"
);
assert_eq!(out.original, "github:acme/lex-labels?via=git");
}
#[test]
fn github_via_git_with_rev_preserves_rev() {
let input = ParsedUri::parse("github:acme/lex-labels#v1.2.0?via=git").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "git");
assert!(
out.body.ends_with(".git"),
"git transport body should end in .git, got: {}",
out.body
);
assert_eq!(out.rev.as_deref(), Some("v1.2.0"));
}
#[test]
fn github_via_https_explicit_matches_default() {
let default = expand(ParsedUri::parse("github:acme/lex-labels").unwrap()).unwrap();
let explicit =
expand(ParsedUri::parse("github:acme/lex-labels?via=https").unwrap()).unwrap();
assert_eq!(default.scheme, explicit.scheme);
assert_eq!(default.body, explicit.body);
assert_eq!(default.rev, explicit.rev);
assert_eq!(default.subdir, explicit.subdir);
assert_eq!(explicit.via, None);
}
#[test]
fn github_via_unknown_value_errors() {
let input = ParsedUri::parse("github:acme/lex-labels?via=ftp").unwrap();
let err = expand(input).unwrap_err();
match err {
UriParseError::UnsupportedVia { value } => assert_eq!(value, "ftp"),
other => panic!("expected UnsupportedVia, got: {other:?}"),
}
}
#[test]
fn github_via_git_with_subdir() {
let input = ParsedUri::parse("github:acme/lex-labels?subdir=labels&via=git").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "git");
assert!(
out.body.ends_with(".git"),
"git transport body should end in .git, got: {}",
out.body
);
assert_eq!(out.subdir.as_deref(), Some("labels"));
assert_eq!(out.via, None);
}
#[test]
fn gitlab_via_git_expands_to_git_clone_url() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels?via=git").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "git");
assert_eq!(out.body, "https://gitlab.com/foolco/lex-labels.git");
assert_eq!(
out.via, None,
"via is a one-shot routing knob; should not propagate"
);
assert_eq!(out.original, "gitlab:foolco/lex-labels?via=git");
}
#[test]
fn gitlab_via_git_with_rev_preserves_rev() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels#v2.1.0?via=git").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "git");
assert!(
out.body.ends_with(".git"),
"git transport body should end in .git, got: {}",
out.body
);
assert_eq!(out.rev.as_deref(), Some("v2.1.0"));
}
#[test]
fn gitlab_via_git_with_slashed_rev_preserves_rev() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels#feature/foo?via=git").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "git");
assert_eq!(out.body, "https://gitlab.com/foolco/lex-labels.git");
assert_eq!(out.rev.as_deref(), Some("feature/foo"));
}
#[test]
fn gitlab_via_https_explicit_matches_default() {
let default = expand(ParsedUri::parse("gitlab:foolco/lex-labels").unwrap()).unwrap();
let explicit =
expand(ParsedUri::parse("gitlab:foolco/lex-labels?via=https").unwrap()).unwrap();
assert_eq!(default.scheme, explicit.scheme);
assert_eq!(default.body, explicit.body);
assert_eq!(default.rev, explicit.rev);
assert_eq!(default.subdir, explicit.subdir);
assert_eq!(explicit.via, None);
}
#[test]
fn gitlab_via_unknown_value_errors() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels?via=ftp").unwrap();
let err = expand(input).unwrap_err();
match err {
UriParseError::UnsupportedVia { value } => assert_eq!(value, "ftp"),
other => panic!("expected UnsupportedVia, got: {other:?}"),
}
}
#[test]
fn gitlab_via_git_with_subdir() {
let input = ParsedUri::parse("gitlab:foolco/lex-labels?subdir=labels&via=git").unwrap();
let out = expand(input).unwrap();
assert_eq!(out.scheme, "git");
assert!(
out.body.ends_with(".git"),
"git transport body should end in .git, got: {}",
out.body
);
assert_eq!(out.subdir.as_deref(), Some("labels"));
assert_eq!(out.via, None);
}
#[test]
fn non_template_uri_passes_through_unchanged() {
for input_str in [
"path:./local",
"https://example.com/foo.tar.gz",
"git+ssh://git@host/repo.git#main",
] {
let input = ParsedUri::parse(input_str).unwrap();
let out = expand(input.clone()).unwrap();
assert_eq!(out, input, "non-template URI was rewritten: {input_str}");
}
}
}