use super::UriConfig;
use crate::core::moniker::Moniker;
pub fn to_uri(moniker: &Moniker, config: &UriConfig<'_>) -> String {
let view = moniker.as_view();
let mut out = String::with_capacity(config.scheme.len() + view.as_encoded().len() + 16);
out.push_str(config.scheme);
write_name(&mut out, view.project());
for seg in view.segments() {
out.push('/');
let kind = match std::str::from_utf8(seg.kind) {
Ok(kind) => kind,
Err(_) => panic!("moniker segment kind must be UTF-8"),
};
out.push_str(kind);
out.push(':');
write_name(&mut out, seg.name);
}
out
}
fn name_needs_escaping(bytes: &[u8]) -> bool {
bytes.is_empty()
|| bytes
.iter()
.any(|b| *b == b'/' || *b == b'`' || b.is_ascii_whitespace())
}
fn write_name(out: &mut String, bytes: &[u8]) {
let s = match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(_) => panic!("moniker project and segment names must be UTF-8"),
};
if !name_needs_escaping(bytes) {
out.push_str(s);
return;
}
out.push('`');
for c in s.chars() {
if c == '`' {
out.push_str("``");
} else {
out.push(c);
}
}
out.push('`');
}
#[cfg(test)]
mod tests {
use super::super::test_helpers::*;
use super::*;
use crate::core::moniker::MonikerBuilder;
#[test]
fn to_uri_project_only() {
let m = MonikerBuilder::new().project(b"my-app").build();
assert_eq!(to_uri(&m, &default_config()), "esac+moniker://my-app");
}
#[test]
fn to_uri_path_chain() {
let m = MonikerBuilder::new()
.project(b"my-app")
.segment(b"path", b"main")
.segment(b"path", b"com")
.segment(b"path", b"acme")
.segment(b"class", b"Foo")
.build();
assert_eq!(
to_uri(&m, &default_config()),
"esac+moniker://my-app/path:main/path:com/path:acme/class:Foo"
);
}
#[test]
fn to_uri_method_no_arity_in_name() {
let m = MonikerBuilder::new()
.project(b"my-app")
.segment(b"path", b"main")
.segment(b"class", b"Foo")
.segment(b"method", b"bar()")
.build();
assert_eq!(
to_uri(&m, &default_config()),
"esac+moniker://my-app/path:main/class:Foo/method:bar()"
);
}
#[test]
fn to_uri_method_with_arity_in_name() {
let m = MonikerBuilder::new()
.project(b"app")
.segment(b"class", b"Foo")
.segment(b"method", b"bar(2)")
.build();
assert_eq!(
to_uri(&m, &default_config()),
"esac+moniker://app/class:Foo/method:bar(2)"
);
}
#[test]
fn to_uri_escapes_slash_in_name() {
let m = MonikerBuilder::new()
.project(b"app")
.segment(b"path", b"util/test.ts")
.build();
assert_eq!(
to_uri(&m, &default_config()),
"esac+moniker://app/path:`util/test.ts`"
);
}
#[test]
fn to_uri_escapes_backtick() {
let m = MonikerBuilder::new()
.project(b"app")
.segment(b"class", b"weird`name")
.build();
assert_eq!(
to_uri(&m, &default_config()),
"esac+moniker://app/class:`weird``name`"
);
}
use proptest::prelude::*;
fn arb_moniker() -> impl Strategy<Value = crate::core::moniker::Moniker> {
use crate::core::moniker::MonikerBuilder;
(
"[a-zA-Z][a-zA-Z0-9_-]{0,15}",
proptest::collection::vec(("[a-zA-Z][a-zA-Z0-9_]{0,7}", "\\PC{0,32}"), 0..6),
)
.prop_map(|(project, segs)| {
let mut b = MonikerBuilder::new();
b.project(project.as_bytes());
for (kind, name) in &segs {
b.segment(kind.as_bytes(), name.as_bytes());
}
b.build()
})
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 256,
..ProptestConfig::default()
})]
#[test]
fn to_uri_from_uri_roundtrip(m in arb_moniker()) {
let s = to_uri(&m, &default_config());
let m2 = super::super::parse::from_uri(&s, &default_config())
.expect("from_uri must accept what to_uri produced");
prop_assert_eq!(m, m2);
}
}
}