///|
/// Building URIs, URI references, IRIs and IRI references from components.
///|
/// An error raised when building a URI/IRI (reference) from components that
/// cannot be put back together unambiguously.
pub(all) suberror BuildError {
/// An authority is present, but the path is not empty and does not start
/// with `'/'`.
NonemptyRootlessPath
/// No authority is present, but the path starts with `"//"`.
PathStartsWithDoubleSlash
/// Neither a scheme nor an authority is present, but the first path segment
/// contains `':'`.
FirstPathSegmentContainsColon
} derive(Eq, Debug)
///|
pub impl Show for BuildError with fn output(self, logger) {
let msg = match self {
NonemptyRootlessPath =>
"when authority is present, path should either be empty or start with '/'"
PathStartsWithDoubleSlash =>
"when authority is not present, path should not start with \"//\""
FirstPathSegmentContainsColon =>
"when neither scheme nor authority is present, first path segment should not contain ':'"
}
logger.write_string(msg)
}
///|
/// Creates an [authority] component from its subcomponents.
///
/// If the contents of a `Host::RegName` host match the `IPv4address` ABNF rule from
/// [Section 3.2.2 of RFC 3986][host], the resulting authority holds an
/// [`Host::Ipv4`] host instead.
///
/// Note that ASCII characters within a host are *case-insensitive*.
/// For consistency, you should only produce normalized hosts.
///
/// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
/// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
///
/// # Panics
///
/// Panics if `host` is [`Host::IpvFuture`], which carries no address and so
/// cannot be written back out.
///
/// ```mbt check
/// test {
/// let auth : @uri.Authority[@enc.Userinfo, @enc.RegName] = @uri.Authority::make(
/// host=@uri.Host::RegName(@uri.EStr::new_or_panic("example.com")),
/// userinfo=@uri.EStr::new_or_panic("user"),
/// port=@uri.EStr::new_or_panic("8042"),
/// )
/// inspect(auth, content="user@example.com:8042")
/// }
/// ```
pub fn[U, R] Authority::make(
host~ : Host[R],
userinfo? : EStr[U],
port? : EStr[@enc.Port],
) -> Authority[U, R] {
let (text, parsed) : (String, Host[@enc.IRegName]) = match host {
Ipv4(addr) => (addr.to_string(), Ipv4(addr))
Ipv6(addr) => ("[" + addr.to_string() + "]", Ipv6(addr))
RegName(name) => {
let text = name.as_str()
(text, parse_v4_or_reg_name(text))
}
IpvFuture => abort("cannot build an authority with an IPvFuture host")
}
{ userinfo, host: text, host_parsed: parsed.cast(), port }
}
///|
/// Checks that the components can be put back together unambiguously.
fn build_ri(
scheme : Scheme?,
authority : IAuth?,
path : String,
query : String?,
fragment : String?,
) -> Ri raise BuildError {
if authority is Some(_) {
if !path.is_empty() && !path.has_prefix("/") {
raise NonemptyRootlessPath
}
} else {
if path.has_prefix("//") {
raise PathStartsWithDoubleSlash
}
if scheme is None {
let first_segment = match path.split_once("/") {
Some((first, _)) => first.to_owned()
None => path
}
if first_segment.contains(":") {
raise FirstPathSegmentContainsColon
}
}
}
ri(scheme~, authority~, path~, query~, fragment~)
}
///|
/// Builds a URI from its components.
///
/// # Errors
///
/// Raises [`BuildError`] unless both of the following hold.
///
/// - When an authority is present, the path is empty or starts with `'/'`.
/// - When no authority is present, the path does not start with `"//"`.
///
/// ```mbt check
/// test {
/// let uri = @uri.Uri::build(
/// scheme=@uri.Scheme::new_or_panic("foo"),
/// authority=@uri.Authority::make(
/// host=@uri.Host::RegName(@uri.EStr::new_or_panic("example.com")),
/// userinfo=@uri.EStr::new_or_panic("user"),
/// port=@uri.EStr::new_or_panic("8042"),
/// ),
/// path=@uri.EStr::new_or_panic("/over/there"),
/// query=@uri.EStr::new_or_panic("name=ferret"),
/// fragment=@uri.EStr::new_or_panic("nose"),
/// )
/// inspect(
/// uri,
/// content="foo://user@example.com:8042/over/there?name=ferret#nose",
/// )
/// }
/// ```
pub fn Uri::build(
scheme~ : Scheme,
path~ : EStr[@enc.Path],
authority? : Authority[@enc.Userinfo, @enc.RegName],
query? : EStr[@enc.Query],
fragment? : EStr[@enc.Fragment],
) -> Uri raise BuildError {
uri_of_ri(
build_ri(
Some(scheme),
opt_map(authority, a => a.cast()),
path.as_str(),
opt_map(query, v => v.as_str()),
opt_map(fragment, v => v.as_str()),
),
)
}
///|
/// Builds a URI reference from its components.
///
/// # Errors
///
/// Raises [`BuildError`] unless all of the following hold.
///
/// - When an authority is present, the path is empty or starts with `'/'`.
/// - When no authority is present, the path does not start with `"//"`.
/// - When neither a scheme nor an authority is present, the first path
/// segment does not contain `':'`.
pub fn UriRef::build(
path~ : EStr[@enc.Path],
scheme? : Scheme,
authority? : Authority[@enc.Userinfo, @enc.RegName],
query? : EStr[@enc.Query],
fragment? : EStr[@enc.Fragment],
) -> UriRef raise BuildError {
uri_ref_of_ri(
build_ri(
scheme,
opt_map(authority, a => a.cast()),
path.as_str(),
opt_map(query, v => v.as_str()),
opt_map(fragment, v => v.as_str()),
),
)
}
///|
/// Builds an IRI from its components. See [`Uri::build`].
pub fn Iri::build(
scheme~ : Scheme,
path~ : EStr[@enc.IPath],
authority? : Authority[@enc.IUserinfo, @enc.IRegName],
query? : EStr[@enc.IQuery],
fragment? : EStr[@enc.IFragment],
) -> Iri raise BuildError {
iri_of_ri(
build_ri(
Some(scheme),
authority,
path.as_str(),
opt_map(query, v => v.as_str()),
opt_map(fragment, v => v.as_str()),
),
)
}
///|
/// Builds an IRI reference from its components. See [`UriRef::build`].
pub fn IriRef::build(
path~ : EStr[@enc.IPath],
scheme? : Scheme,
authority? : Authority[@enc.IUserinfo, @enc.IRegName],
query? : EStr[@enc.IQuery],
fragment? : EStr[@enc.IFragment],
) -> IriRef raise BuildError {
iri_ref_of_ri(
build_ri(
scheme,
authority,
path.as_str(),
opt_map(query, v => v.as_str()),
opt_map(fragment, v => v.as_str()),
),
)
}