pathlink/
lib.rs

1//! A URI which supports IPv4, IPv6, domain names, and segmented [`Path`]s.
2
3use std::cmp::Ordering;
4use std::str::FromStr;
5use std::{fmt, iter};
6
7use derive_more::*;
8use get_size::GetSize;
9use get_size_derive::*;
10use smallvec::SmallVec;
11
12pub use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
13
14pub use hr_id::{label, Id, Label, ParseError};
15
16mod path;
17#[cfg(feature = "serialize")]
18mod serial;
19#[cfg(feature = "stream")]
20mod stream;
21
22pub use path::*;
23
24/// A port number
25pub type Port = u16;
26
27type Segments<T> = SmallVec<[T; 8]>;
28
29/// An owned or borrowed [`Link`] or [`Path`] which can be parsed as a URL.
30pub enum ToUrl<'a> {
31    Link(Link),
32    LinkRef(&'a Link),
33    Path(PathBuf),
34    PathRef(&'a [PathSegment]),
35}
36
37impl<'a> ToUrl<'a> {
38    /// Construct a new [`Link`] from this URL.
39    pub fn to_link(&self) -> Link {
40        match self {
41            Self::Link(link) => (*link).clone(),
42            Self::LinkRef(link) => (**link).clone(),
43            Self::Path(path) => path.clone().into(),
44            Self::PathRef(path) => PathBuf::from_slice(path).into(),
45        }
46    }
47
48    /// Borrow the [`Host`] component of this link, if any.
49    pub fn host(&self) -> Option<&Host> {
50        match self {
51            Self::Link(link) => link.host(),
52            Self::LinkRef(link) => link.host(),
53            _ => None,
54        }
55    }
56
57    /// Borrow the [`Path`] component of this link.
58    pub fn path(&self) -> &[PathSegment] {
59        match self {
60            Self::Link(link) => link.path(),
61            Self::LinkRef(link) => link.path(),
62            Self::Path(path) => path,
63            Self::PathRef(path) => path,
64        }
65    }
66
67    /// Construct a new URL of the given type from this link.
68    pub fn parse<Url>(&self) -> Result<Url, <Url as FromStr>::Err>
69    where
70        Url: FromStr,
71    {
72        self.to_string().parse()
73    }
74}
75
76impl<'a> fmt::Display for ToUrl<'a> {
77    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78        match self {
79            Self::Link(link) => fmt::Display::fmt(link, f),
80            Self::LinkRef(link) => fmt::Display::fmt(link, f),
81            Self::Path(path) => fmt::Display::fmt(path, f),
82            Self::PathRef(path) => {
83                if path.is_empty() {
84                    f.write_str("/")?;
85                }
86
87                for segment in path.iter() {
88                    write!(f, "/{segment}")?;
89                }
90
91                Ok(())
92            }
93        }
94    }
95}
96
97impl<'a> From<Link> for ToUrl<'a> {
98    fn from(link: Link) -> Self {
99        Self::Link(link)
100    }
101}
102
103impl<'a> From<&'a Link> for ToUrl<'a> {
104    fn from(link: &'a Link) -> Self {
105        Self::LinkRef(link)
106    }
107}
108
109impl<'a> From<PathBuf> for ToUrl<'a> {
110    fn from(path: PathBuf) -> Self {
111        Self::Path(path)
112    }
113}
114
115impl<'a> From<&'a [PathSegment]> for ToUrl<'a> {
116    fn from(path: &'a [PathSegment]) -> Self {
117        Self::PathRef(path.into())
118    }
119}
120
121/// The protocol portion of a [`Link`] (e.g. "http")
122#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, GetSize)]
123pub enum Protocol {
124    HTTP,
125}
126
127impl PartialOrd for Protocol {
128    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
129        Some(self.cmp(other))
130    }
131}
132
133impl Ord for Protocol {
134    fn cmp(&self, other: &Self) -> Ordering {
135        match (self, other) {
136            (Self::HTTP, Self::HTTP) => Ordering::Equal,
137        }
138    }
139}
140
141impl Default for Protocol {
142    fn default() -> Protocol {
143        Protocol::HTTP
144    }
145}
146
147impl fmt::Display for Protocol {
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        f.write_str(match self {
150            Self::HTTP => "http",
151        })
152    }
153}
154
155/// A network address
156#[derive(Clone, Debug, Display, Eq, PartialEq, Hash)]
157pub enum Address {
158    IPv4(Ipv4Addr),
159    IPv6(Ipv6Addr),
160    // TODO: international domain names with IDNA: https://docs.rs/idna/0.3.0/idna/
161}
162
163impl Default for Address {
164    fn default() -> Self {
165        Self::LOCALHOST
166    }
167}
168
169impl Address {
170    pub const LOCALHOST: Self = Self::IPv4(Ipv4Addr::LOCALHOST);
171
172    /// Return this [`Address`] as an [`IpAddr`].
173    pub fn as_ip(&self) -> Option<IpAddr> {
174        match self {
175            Self::IPv4(addr) => Some((*addr).into()),
176            Self::IPv6(addr) => Some((*addr).into()),
177        }
178    }
179
180    /// Return `true` if this is the [`Address`] of the local host.
181    pub fn is_localhost(&self) -> bool {
182        match self {
183            Self::IPv4(addr) => addr.is_loopback(),
184            Self::IPv6(addr) => addr.is_loopback(),
185        }
186    }
187}
188
189impl PartialOrd for Address {
190    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
191        Some(self.cmp(other))
192    }
193}
194
195impl Ord for Address {
196    fn cmp(&self, other: &Self) -> Ordering {
197        match (self, other) {
198            (Self::IPv4(this), Self::IPv4(that)) => this.cmp(that),
199            (Self::IPv6(this), Self::IPv6(that)) => this.cmp(that),
200            (Self::IPv4(_this), _) => Ordering::Less,
201            (Self::IPv6(_this), _) => Ordering::Greater,
202        }
203    }
204}
205
206impl GetSize for Address {
207    fn get_size(&self) -> usize {
208        match self {
209            Self::IPv4(_) => 4,
210            Self::IPv6(_) => 16,
211        }
212    }
213}
214
215impl From<Ipv4Addr> for Address {
216    fn from(addr: Ipv4Addr) -> Address {
217        Self::IPv4(addr)
218    }
219}
220
221impl From<Ipv6Addr> for Address {
222    fn from(addr: Ipv6Addr) -> Address {
223        Self::IPv6(addr)
224    }
225}
226
227impl From<IpAddr> for Address {
228    fn from(addr: IpAddr) -> Address {
229        match addr {
230            IpAddr::V4(addr) => Self::IPv4(addr),
231            IpAddr::V6(addr) => Self::IPv6(addr),
232        }
233    }
234}
235
236impl PartialEq<Ipv4Addr> for Address {
237    fn eq(&self, other: &Ipv4Addr) -> bool {
238        match self {
239            Self::IPv4(addr) => addr == other,
240            _ => false,
241        }
242    }
243}
244
245impl PartialEq<Ipv6Addr> for Address {
246    fn eq(&self, other: &Ipv6Addr) -> bool {
247        match self {
248            Self::IPv6(addr) => addr == other,
249            _ => false,
250        }
251    }
252}
253
254impl PartialEq<IpAddr> for Address {
255    fn eq(&self, other: &IpAddr) -> bool {
256        use IpAddr::*;
257
258        match other {
259            V4(addr) => self == addr,
260            V6(addr) => self == addr,
261        }
262    }
263}
264
265/// The host component of a [`Link`] (e.g. "http://127.0.0.1:8702")
266#[derive(Clone, Debug, Hash, Eq, PartialEq, GetSize)]
267pub struct Host {
268    protocol: Protocol,
269    address: Address,
270    port: Option<Port>,
271}
272
273impl Host {
274    /// Check if the address of this [`Host`] is the local host.
275    pub fn is_localhost(&self) -> bool {
276        self.address.is_localhost()
277    }
278
279    /// Check if this [`Host`] matches the host and port of the given `public_addr` or localhost.
280    pub fn is_loopback(&self, public_addr: Option<&Host>) -> bool {
281        if let Some(addr) = public_addr {
282            self == addr || (self.is_localhost() && self.port == addr.port)
283        } else {
284            self.is_localhost()
285        }
286    }
287
288    /// Return the [`Protocol`] to use with this [`Host`].
289    pub fn protocol(&self) -> Protocol {
290        self.protocol
291    }
292
293    /// Return the [`Address`] of this [`Host`].
294    pub fn address(&self) -> &Address {
295        &self.address
296    }
297
298    /// Return the [`Port`] which this [`Host`] is listening on.
299    pub fn port(&self) -> Option<Port> {
300        self.port
301    }
302}
303
304impl FromStr for Host {
305    type Err = ParseError;
306
307    fn from_str(s: &str) -> Result<Host, ParseError> {
308        if !s.starts_with("http://") {
309            return Err(format!("invalid protocol: {}", s).into());
310        }
311
312        let protocol = Protocol::HTTP;
313
314        let s = &s[7..];
315
316        let (address, port): (Address, Option<u16>) = if s.contains("::") {
317            let mut segments: Segments<&str> = s.split("::").collect();
318            let port: Option<u16> = if segments.last().unwrap().contains(':') {
319                let last_segment: Segments<&str> = segments.pop().unwrap().split(':').collect();
320                if last_segment.len() == 2 {
321                    segments.push(last_segment[0]);
322
323                    let port = last_segment[1].parse().map_err(|cause| {
324                        format!("{} is not a valid port number: {}", last_segment[1], cause)
325                    })?;
326
327                    Some(port)
328                } else {
329                    return Err(format!("invalid IPv6 address: {}", s).into());
330                }
331            } else {
332                None
333            };
334
335            let address = segments.join("::");
336            let address: Ipv6Addr = address.parse().map_err(|cause| {
337                ParseError::from(format!(
338                    "{} is not a valid IPv6 address: {}",
339                    address, cause
340                ))
341            })?;
342
343            (address.into(), port)
344        } else {
345            let (address, port) = if s.contains(':') {
346                let segments: Segments<&str> = s.split(':').collect();
347                if segments.len() == 2 {
348                    let port: u16 = segments[1].parse().map_err(|cause| {
349                        ParseError::from(format!(
350                            "{} is not a valid port number: {}",
351                            segments[1], cause
352                        ))
353                    })?;
354
355                    (segments[0], Some(port))
356                } else {
357                    return Err(format!("invalid network address: {}", s).into());
358                }
359            } else {
360                (s, None)
361            };
362
363            let address: Ipv4Addr = address.parse().map_err(|cause| {
364                ParseError::from(format!(
365                    "{} is not a valid IPv4 address: {}",
366                    address, cause
367                ))
368            })?;
369
370            (address.into(), port)
371        };
372
373        Ok(Host {
374            protocol,
375            address,
376            port,
377        })
378    }
379}
380
381impl PartialOrd for Host {
382    fn partial_cmp(&self, other: &Host) -> Option<Ordering> {
383        Some(self.cmp(other))
384    }
385}
386
387impl Ord for Host {
388    fn cmp(&self, other: &Self) -> Ordering {
389        match self.protocol.cmp(&other.protocol) {
390            Ordering::Equal => match self.address.cmp(&other.address) {
391                Ordering::Equal => self.port.cmp(&other.port),
392                ordering => ordering,
393            },
394            ordering => ordering,
395        }
396    }
397}
398
399impl From<(Protocol, Address)> for Host {
400    fn from(components: (Protocol, Address)) -> Self {
401        let (protocol, address) = components;
402        Self {
403            protocol,
404            address,
405            port: None,
406        }
407    }
408}
409
410impl From<(Address, Port)> for Host {
411    fn from(components: (Address, Port)) -> Self {
412        let (address, port) = components;
413        Self {
414            protocol: Protocol::default(),
415            address,
416            port: Some(port),
417        }
418    }
419}
420
421impl From<(Protocol, Address, Port)> for Host {
422    fn from(components: (Protocol, Address, Port)) -> Self {
423        let (protocol, address, port) = components;
424        Self {
425            protocol,
426            address,
427            port: Some(port),
428        }
429    }
430}
431
432impl From<(Protocol, Address, Option<Port>)> for Host {
433    fn from(components: (Protocol, Address, Option<Port>)) -> Self {
434        let (protocol, address, port) = components;
435        Self {
436            protocol,
437            address,
438            port,
439        }
440    }
441}
442
443impl fmt::Display for Host {
444    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
445        if let Some(port) = self.port {
446            write!(f, "{}://{}:{}", self.protocol, self.address, port)
447        } else {
448            write!(f, "{}://{}", self.protocol, self.address)
449        }
450    }
451}
452
453/// An HTTP Link with an optional [`Address`] and [`PathBuf`]
454#[derive(Clone, Default, Eq, Hash, PartialEq, GetSize)]
455pub struct Link {
456    host: Option<Host>,
457    path: PathBuf,
458}
459
460impl Link {
461    /// Create a new [`Link`] with the given [`Host`] and [`PathBuf`].
462    pub fn new(host: Host, path: PathBuf) -> Self {
463        Self {
464            host: Some(host),
465            path,
466        }
467    }
468
469    /// Consume this [`Link`] and return its [`Host`] and [`PathBuf`].
470    pub fn into_inner(self) -> (Option<Host>, PathBuf) {
471        (self.host, self.path)
472    }
473
474    /// Consume this [`Link`] and return its [`Host`].
475    pub fn into_host(self) -> Option<Host> {
476        self.host
477    }
478
479    /// Consume this [`Link`] and return its [`PathBuf`].
480    pub fn into_path(self) -> PathBuf {
481        self.path
482    }
483
484    /// Borrow this [`Link`]'s [`Host`], if it has one.
485    pub fn host(&self) -> Option<&Host> {
486        self.host.as_ref()
487    }
488
489    /// Borrow this [`Link`]'s path.
490    pub fn path(&self) -> &PathBuf {
491        &self.path
492    }
493
494    /// Borrow this [`Link`]'s path mutably.
495    pub fn path_mut(&mut self) -> &mut PathBuf {
496        &mut self.path
497    }
498
499    /// Append the given [`PathSegment`] to this [`Link`] and return it.
500    pub fn append<S: Into<PathSegment>>(mut self, segment: S) -> Self {
501        self.path = self.path.append(segment);
502        self
503    }
504}
505
506impl Extend<PathSegment> for Link {
507    fn extend<I: IntoIterator<Item = PathSegment>>(&mut self, iter: I) {
508        self.path.extend(iter)
509    }
510}
511
512impl PartialEq<[PathSegment]> for Link {
513    fn eq(&self, other: &[PathSegment]) -> bool {
514        if self.host.is_some() {
515            return false;
516        }
517
518        &self.path == other
519    }
520}
521
522impl PartialEq<String> for Link {
523    fn eq(&self, other: &String) -> bool {
524        self == other.as_str()
525    }
526}
527
528impl PartialEq<str> for Link {
529    fn eq(&self, other: &str) -> bool {
530        if other.is_empty() {
531            false
532        } else if other.starts_with('/') {
533            self.host.is_none() && &self.path == other
534        } else if other.ends_with('/') {
535            self.to_string() == other[..other.len() - 1]
536        } else {
537            self.to_string() == other
538        }
539    }
540}
541
542impl From<Host> for Link {
543    fn from(host: Host) -> Link {
544        Link {
545            host: Some(host),
546            path: PathBuf::default(),
547        }
548    }
549}
550
551impl From<PathLabel> for Link {
552    fn from(path: PathLabel) -> Self {
553        PathBuf::from(path).into()
554    }
555}
556
557impl From<PathBuf> for Link {
558    fn from(path: PathBuf) -> Link {
559        Link { host: None, path }
560    }
561}
562
563impl From<(Host, PathBuf)> for Link {
564    fn from(tuple: (Host, PathBuf)) -> Link {
565        let (host, path) = tuple;
566        Link {
567            host: Some(host),
568            path,
569        }
570    }
571}
572
573impl From<(Option<Host>, PathBuf)> for Link {
574    fn from(tuple: (Option<Host>, PathBuf)) -> Link {
575        let (host, path) = tuple;
576        Link { host, path }
577    }
578}
579
580impl FromStr for Link {
581    type Err = ParseError;
582
583    fn from_str(s: &str) -> Result<Link, ParseError> {
584        if s.starts_with('/') {
585            return Ok(Link {
586                host: None,
587                path: s.parse()?,
588            });
589        } else if !s.starts_with("http://") {
590            return Err(format!("cannot parse {} as a Link: invalid protocol", s).into());
591        }
592
593        let s = if s.ends_with('/') {
594            &s[..s.len() - 1]
595        } else {
596            s
597        };
598
599        let segments: Segments<&str> = s.split('/').collect();
600
601        if segments.is_empty() {
602            return Err(format!("invalid Link: {}", s).into());
603        }
604
605        let host: Host = segments[..3].join("/").parse()?;
606
607        let segments = &segments[3..];
608
609        let segments = segments
610            .iter()
611            .map(|s| s.parse())
612            .collect::<Result<Segments<PathSegment>, ParseError>>()?;
613
614        Ok(Link {
615            host: Some(host),
616            path: iter::FromIterator::from_iter(segments),
617        })
618    }
619}
620
621impl PartialOrd for Link {
622    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
623        Some(self.cmp(other))
624    }
625}
626
627impl Ord for Link {
628    fn cmp(&self, other: &Self) -> Ordering {
629        match (&self.host, &other.host) {
630            (None, None) => self.path.cmp(&other.path),
631            (Some(this), Some(that)) => match this.cmp(&that) {
632                Ordering::Equal => self.path.cmp(&other.path),
633                ordering => ordering,
634            },
635            (Some(_), _) => Ordering::Less,
636            (_, Some(_)) => Ordering::Greater,
637        }
638    }
639}
640
641impl TryFrom<Link> for PathBuf {
642    type Error = ParseError;
643
644    fn try_from(link: Link) -> Result<Self, Self::Error> {
645        if link.host.is_none() {
646            Ok(link.path)
647        } else {
648            Err(ParseError::from(format!(
649                "expected a path but found {}",
650                link
651            )))
652        }
653    }
654}
655
656impl fmt::Debug for Link {
657    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
658        if let Some(host) = &self.host {
659            write!(f, "{:?}{:?}", host, self.path)
660        } else {
661            fmt::Debug::fmt(&self.path, f)
662        }
663    }
664}
665
666impl fmt::Display for Link {
667    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
668        if let Some(host) = &self.host {
669            write!(f, "{}{}", host, self.path)
670        } else {
671            fmt::Display::fmt(&self.path, f)
672        }
673    }
674}
675
676#[cfg(feature = "hash")]
677impl<D: async_hash::Digest> async_hash::Hash<D> for Link {
678    fn hash(self) -> async_hash::Output<D> {
679        async_hash::Hash::<D>::hash(&self)
680    }
681}
682
683#[cfg(feature = "hash")]
684impl<'a, D: async_hash::Digest> async_hash::Hash<D> for &'a Link {
685    fn hash(self) -> async_hash::Output<D> {
686        if self == &Link::default() {
687            async_hash::default_hash::<D>()
688        } else {
689            async_hash::Hash::<D>::hash(self.to_string())
690        }
691    }
692}