#![allow(missing_debug_implementations)]
pub mod state;
use crate::{
component::{Authority, IAuthority, Scheme},
encoding::{
encoder::{IRegName, Port, RegName},
EStr,
},
error::{BuildError, BuildErrorKind},
internal::{AuthMeta, HostMeta, Meta, RiRef},
parser,
};
use alloc::string::String;
use core::{fmt::Write, marker::PhantomData, num::NonZeroUsize};
use state::*;
#[cfg(feature = "net")]
use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[must_use]
pub struct Builder<R, S> {
inner: BuilderInner,
_marker: PhantomData<(R, S)>,
}
pub struct BuilderInner {
buf: String,
meta: Meta,
}
impl BuilderInner {
fn push_scheme(&mut self, v: &str) {
self.buf.push_str(v);
self.meta.scheme_end = NonZeroUsize::new(self.buf.len());
self.buf.push(':');
}
fn start_authority(&mut self) {
self.buf.push_str("//");
}
fn push_authority(&mut self, v: IAuthority<'_>) {
self.buf.push_str("//");
let start = self.buf.len();
self.buf.push_str(v.as_str());
let mut meta = v.meta();
meta.host_bounds.0 += start;
meta.host_bounds.1 += start;
self.meta.auth_meta = Some(meta);
}
fn push_userinfo(&mut self, v: &str) {
self.buf.push_str(v);
self.buf.push('@');
}
fn push_host(&mut self, meta: HostMeta, f: impl FnOnce(&mut String)) {
let start = self.buf.len();
f(&mut self.buf);
self.meta.auth_meta = Some(AuthMeta {
host_bounds: (start, self.buf.len()),
host_meta: meta,
});
}
fn push_path(&mut self, v: &str) {
self.meta.path_bounds.0 = self.buf.len();
self.buf.push_str(v);
self.meta.path_bounds.1 = self.buf.len();
}
fn push_query(&mut self, v: &str) {
self.buf.push('?');
self.buf.push_str(v);
self.meta.query_end = NonZeroUsize::new(self.buf.len());
}
fn push_fragment(&mut self, v: &str) {
self.buf.push('#');
self.buf.push_str(v);
}
fn validate(&self) -> Result<(), BuildError> {
fn first_segment_contains_colon(path: &str) -> bool {
path.split_once('/').map_or(path, |x| x.0).contains(':')
}
let (start, end) = self.meta.path_bounds;
let path = &self.buf[start..end];
if self.meta.auth_meta.is_some() {
if !path.is_empty() && !path.starts_with('/') {
return Err(BuildError(BuildErrorKind::NonAbemptyPath));
}
} else {
if path.starts_with("//") {
return Err(BuildError(BuildErrorKind::PathStartingWithDoubleSlash));
}
if self.meta.scheme_end.is_none() && first_segment_contains_colon(path) {
return Err(BuildError(BuildErrorKind::ColonInFirstPathSegment));
}
}
Ok(())
}
}
impl<R, S> Builder<R, S> {
pub(crate) fn new() -> Self {
Self {
inner: BuilderInner {
buf: String::new(),
meta: Meta::default(),
},
_marker: PhantomData,
}
}
}
impl<R, S> Builder<R, S> {
fn cast<T>(self) -> Builder<R, T>
where
S: To<T>,
{
Builder {
inner: self.inner,
_marker: PhantomData,
}
}
pub fn advance<T>(self) -> Builder<R, T>
where
S: AdvanceTo<T>,
{
self.cast()
}
pub fn optional<F, V, T>(self, f: F, opt: Option<V>) -> Builder<R, T>
where
F: FnOnce(Builder<R, S>, V) -> Builder<R, T>,
S: AdvanceTo<T>,
{
match opt {
Some(value) => f(self, value),
None => self.advance(),
}
}
}
impl<R, S: To<SchemeEnd>> Builder<R, S> {
pub fn scheme(mut self, scheme: &Scheme) -> Builder<R, SchemeEnd> {
self.inner.push_scheme(scheme.as_str());
self.cast()
}
}
impl<R: RiRef, S: To<AuthorityStart>> Builder<R, S> {
pub fn authority_with<F, T>(mut self, f: F) -> Builder<R, AuthorityEnd>
where
F: FnOnce(Builder<R, AuthorityStart>) -> Builder<R, T>,
T: To<AuthorityEnd>,
{
self.inner.start_authority();
f(self.cast()).cast()
}
pub fn authority(
mut self,
authority: Authority<'_, R::UserinfoE, R::RegNameE>,
) -> Builder<R, AuthorityEnd> {
self.inner.push_authority(authority.cast());
self.cast::<AuthorityEnd>()
}
}
impl<R: RiRef, S: To<UserinfoEnd>> Builder<R, S> {
pub fn userinfo(mut self, userinfo: &EStr<R::UserinfoE>) -> Builder<R, UserinfoEnd> {
self.inner.push_userinfo(userinfo.as_str());
self.cast()
}
}
pub trait AsHost<'a> {
fn push_to(self, b: &mut BuilderInner);
}
#[cfg(feature = "net")]
impl<'a> AsHost<'a> for Ipv4Addr {
fn push_to(self, b: &mut BuilderInner) {
b.push_host(HostMeta::Ipv4(self), |buf| {
write!(buf, "{self}").unwrap();
});
}
}
#[cfg(feature = "net")]
impl<'a> AsHost<'a> for Ipv6Addr {
fn push_to(self, b: &mut BuilderInner) {
b.push_host(HostMeta::Ipv6(self), |buf| {
write!(buf, "[{self}]").unwrap();
});
}
}
#[cfg(feature = "net")]
impl<'a> AsHost<'a> for IpAddr {
fn push_to(self, b: &mut BuilderInner) {
match self {
IpAddr::V4(addr) => addr.push_to(b),
IpAddr::V6(addr) => addr.push_to(b),
}
}
}
impl<'a> AsHost<'a> for &'a EStr<RegName> {
#[inline]
fn push_to(self, b: &mut BuilderInner) {
self.cast::<IRegName>().push_to(b);
}
}
impl<'a> AsHost<'a> for &'a EStr<IRegName> {
fn push_to(self, b: &mut BuilderInner) {
let meta = parser::parse_v4_or_reg_name(self.as_str().as_bytes());
b.push_host(meta, |buf| {
buf.push_str(self.as_str());
});
}
}
pub trait WithEncoder<E> {}
#[cfg(feature = "net")]
impl<E> WithEncoder<E> for Ipv4Addr {}
#[cfg(feature = "net")]
impl<E> WithEncoder<E> for Ipv6Addr {}
#[cfg(feature = "net")]
impl<E> WithEncoder<E> for IpAddr {}
impl WithEncoder<RegName> for &EStr<RegName> {}
impl WithEncoder<IRegName> for &EStr<IRegName> {}
impl<R: RiRef, S: To<HostEnd>> Builder<R, S> {
pub fn host<'a>(
mut self,
host: impl AsHost<'a> + WithEncoder<R::RegNameE>,
) -> Builder<R, HostEnd> {
host.push_to(&mut self.inner);
self.cast()
}
}
pub trait AsPort {
fn push_to(self, buf: &mut String);
}
impl AsPort for u16 {
fn push_to(self, buf: &mut String) {
write!(buf, ":{self}").unwrap();
}
}
impl AsPort for &EStr<Port> {
fn push_to(self, buf: &mut String) {
buf.push(':');
buf.push_str(self.as_str());
}
}
impl<R, S: To<PortEnd>> Builder<R, S> {
pub fn port(mut self, port: impl AsPort) -> Builder<R, PortEnd> {
port.push_to(&mut self.inner.buf);
self.cast()
}
#[cfg(fluent_uri_unstable)]
pub fn port_with_default(self, port: u16, default: u16) -> Builder<R, PortEnd> {
if port != default {
self.cast()
} else {
self.port(port)
}
}
}
impl<R: RiRef, S: To<PathEnd>> Builder<R, S> {
pub fn path(mut self, path: &EStr<R::PathE>) -> Builder<R, PathEnd> {
self.inner.push_path(path.as_str());
self.cast()
}
}
impl<R: RiRef, S: To<QueryEnd>> Builder<R, S> {
pub fn query(mut self, query: &EStr<R::QueryE>) -> Builder<R, QueryEnd> {
self.inner.push_query(query.as_str());
self.cast()
}
}
impl<R: RiRef, S: To<FragmentEnd>> Builder<R, S> {
pub fn fragment(mut self, fragment: &EStr<R::FragmentE>) -> Builder<R, FragmentEnd> {
self.inner.push_fragment(fragment.as_str());
self.cast()
}
}
impl<R: RiRef<Val = String>, S: To<End>> Builder<R, S> {
pub fn build(self) -> Result<R, BuildError> {
self.inner
.validate()
.map(|()| R::new(self.inner.buf, self.inner.meta))
}
}