fluent_uri/build/
imp.rs

1use super::BuildError;
2use crate::{
3    component::IAuthority,
4    imp::{AuthMeta, HostMeta, Meta},
5    parse,
6    pct_enc::{
7        encoder::{IRegName, Port, RegName},
8        EStr,
9    },
10};
11use alloc::string::String;
12use core::{fmt::Write, num::NonZeroUsize};
13
14#[cfg(feature = "net")]
15use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr};
16
17pub struct BuilderInner {
18    pub buf: String,
19    pub meta: Meta,
20}
21
22impl BuilderInner {
23    pub fn push_scheme(&mut self, v: &str) {
24        self.buf.push_str(v);
25        self.meta.scheme_end = NonZeroUsize::new(self.buf.len());
26        self.buf.push(':');
27    }
28
29    pub fn start_authority(&mut self) {
30        self.buf.push_str("//");
31    }
32
33    pub fn push_authority(&mut self, v: IAuthority<'_>) {
34        self.buf.push_str("//");
35        let start = self.buf.len();
36        self.buf.push_str(v.as_str());
37
38        let mut meta = v.meta();
39        meta.host_bounds.0 += start;
40        meta.host_bounds.1 += start;
41        self.meta.auth_meta = Some(meta);
42    }
43
44    pub fn push_userinfo(&mut self, v: &str) {
45        self.buf.push_str(v);
46        self.buf.push('@');
47    }
48
49    pub fn push_host(&mut self, meta: HostMeta, f: impl FnOnce(&mut String)) {
50        let start = self.buf.len();
51        f(&mut self.buf);
52        self.meta.auth_meta = Some(AuthMeta {
53            host_bounds: (start, self.buf.len()),
54            host_meta: meta,
55        });
56    }
57
58    pub fn push_path(&mut self, v: &str) {
59        self.meta.path_bounds.0 = self.buf.len();
60        self.buf.push_str(v);
61        self.meta.path_bounds.1 = self.buf.len();
62    }
63
64    pub fn push_query(&mut self, v: &str) {
65        self.buf.push('?');
66        self.buf.push_str(v);
67        self.meta.query_end = NonZeroUsize::new(self.buf.len());
68    }
69
70    pub fn push_fragment(&mut self, v: &str) {
71        self.buf.push('#');
72        self.buf.push_str(v);
73    }
74
75    pub fn validate(&self) -> Result<(), BuildError> {
76        fn first_segment_contains_colon(path: &str) -> bool {
77            path.split_once('/').map_or(path, |x| x.0).contains(':')
78        }
79
80        let (start, end) = self.meta.path_bounds;
81        let path = &self.buf[start..end];
82
83        if self.meta.auth_meta.is_some() {
84            if !path.is_empty() && !path.starts_with('/') {
85                return Err(BuildError::NonemptyRootlessPath);
86            }
87        } else {
88            if path.starts_with("//") {
89                return Err(BuildError::PathStartsWithDoubleSlash);
90            }
91            if self.meta.scheme_end.is_none() && first_segment_contains_colon(path) {
92                return Err(BuildError::FirstPathSegmentContainsColon);
93            }
94        }
95        Ok(())
96    }
97}
98
99pub trait AsHost<'a> {
100    fn push_to(self, b: &mut BuilderInner);
101}
102
103#[cfg(feature = "net")]
104impl<'a> AsHost<'a> for Ipv4Addr {
105    fn push_to(self, b: &mut BuilderInner) {
106        b.push_host(HostMeta::Ipv4(self), |buf| {
107            write!(buf, "{self}").unwrap();
108        });
109    }
110}
111
112#[cfg(feature = "net")]
113impl<'a> AsHost<'a> for Ipv6Addr {
114    fn push_to(self, b: &mut BuilderInner) {
115        b.push_host(HostMeta::Ipv6(self), |buf| {
116            write!(buf, "[{self}]").unwrap();
117        });
118    }
119}
120
121#[cfg(feature = "net")]
122impl<'a> AsHost<'a> for IpAddr {
123    fn push_to(self, b: &mut BuilderInner) {
124        match self {
125            IpAddr::V4(addr) => addr.push_to(b),
126            IpAddr::V6(addr) => addr.push_to(b),
127        }
128    }
129}
130
131impl<'a> AsHost<'a> for &'a EStr<RegName> {
132    #[inline]
133    fn push_to(self, b: &mut BuilderInner) {
134        self.cast::<IRegName>().push_to(b);
135    }
136}
137
138impl<'a> AsHost<'a> for &'a EStr<IRegName> {
139    fn push_to(self, b: &mut BuilderInner) {
140        let meta = parse::parse_v4_or_reg_name(self.as_str().as_bytes());
141        b.push_host(meta, |buf| {
142            buf.push_str(self.as_str());
143        });
144    }
145}
146
147pub trait WithEncoder<E> {}
148
149#[cfg(feature = "net")]
150impl<E> WithEncoder<E> for Ipv4Addr {}
151#[cfg(feature = "net")]
152impl<E> WithEncoder<E> for Ipv6Addr {}
153#[cfg(feature = "net")]
154impl<E> WithEncoder<E> for IpAddr {}
155
156impl WithEncoder<RegName> for &EStr<RegName> {}
157impl WithEncoder<IRegName> for &EStr<IRegName> {}
158
159pub trait AsPort {
160    fn push_to(self, buf: &mut String);
161}
162
163impl AsPort for u16 {
164    fn push_to(self, buf: &mut String) {
165        write!(buf, ":{self}").unwrap();
166    }
167}
168
169impl AsPort for &EStr<Port> {
170    fn push_to(self, buf: &mut String) {
171        buf.push(':');
172        buf.push_str(self.as_str());
173    }
174}