1use std::hash::{Hash, Hasher};
2
3use crate::chars::SCHEME;
4use crate::error::{ErrorKind, InvalidUrl};
5
6#[repr(transparent)]
11pub struct Scheme(str);
12
13str_type!(Scheme);
14
15impl Scheme {
16 pub const HTTP: &'static Scheme = Scheme::from_str_unchecked("http");
18 pub const HTTPS: &'static Scheme = Scheme::from_str_unchecked("https");
20 pub const WS: &'static Scheme = Scheme::from_str_unchecked("ws");
22 pub const WSS: &'static Scheme = Scheme::from_str_unchecked("wss");
24 pub const AMQP: &'static Scheme = Scheme::from_str_unchecked("amqp");
26 pub const AMQPS: &'static Scheme = Scheme::from_str_unchecked("amqps");
28 pub const MQTT: &'static Scheme = Scheme::from_str_unchecked("mqtt");
30 pub const MQTTS: &'static Scheme = Scheme::from_str_unchecked("mqtts");
32
33 pub fn new(src: &str) -> Result<&Scheme, InvalidUrl> {
42 let bytes = src.as_bytes();
43 match bytes.first() {
44 None => Err(InvalidUrl::at(ErrorKind::Empty, 0)),
45 Some(b) if !b.is_ascii_alphabetic() => Err(InvalidUrl::at(ErrorKind::InvalidScheme, 0)),
46 _ => match bytes.iter().position(|b| !SCHEME.contains(*b)) {
47 Some(i) => Err(InvalidUrl::at(ErrorKind::InvalidScheme, i)),
48 None => Ok(Scheme::from_str_unchecked(src)),
49 },
50 }
51 }
52
53 pub fn default_port(&self) -> Option<u16> {
63 self.known().map(|(_, port, _)| port)
64 }
65
66 pub(crate) fn is_special(&self) -> bool {
69 self.known().is_some_and(|(_, _, special)| special)
70 }
71
72 fn known(&self) -> Option<(&'static str, u16, bool)> {
73 const KNOWN: [(&str, u16, bool); 9] = [
74 ("http", 80, true),
75 ("https", 443, true),
76 ("ws", 80, true),
77 ("wss", 443, true),
78 ("ftp", 21, true),
79 ("amqp", 5672, false),
80 ("amqps", 5671, false),
81 ("mqtt", 1883, false),
82 ("mqtts", 8883, false),
83 ];
84 KNOWN
85 .into_iter()
86 .find(|(s, _, _)| s.eq_ignore_ascii_case(&self.0))
87 }
88}
89
90impl PartialEq for Scheme {
91 fn eq(&self, other: &Scheme) -> bool {
92 self.0.eq_ignore_ascii_case(&other.0)
93 }
94}
95
96impl Eq for Scheme {}
97
98impl PartialEq<str> for Scheme {
99 fn eq(&self, other: &str) -> bool {
100 self.0.eq_ignore_ascii_case(other)
101 }
102}
103
104impl PartialEq<Scheme> for str {
105 fn eq(&self, other: &Scheme) -> bool {
106 self.eq_ignore_ascii_case(&other.0)
107 }
108}
109
110impl Hash for Scheme {
111 fn hash<H: Hasher>(&self, state: &mut H) {
112 for b in self.0.bytes() {
113 state.write_u8(b.to_ascii_lowercase());
114 }
115 state.write_u8(0xff);
116 }
117}