url_prefix/lib.rs
1/*!
2# URL Prefix
3
4This crate can be used to create URL prefix strings by inputting a protocol, a domain, a port number and a path without additional parsing.
5
6## Why We Need This?
7
8Sometimes our web applications are run on different protocols(HTTP/HTTPS) and domains. And it is boring to write some code like below to format a URL:
9
10```rust,ignore
11let mut url_prefix = String::new();
12
13if is_https {
14 url_prefix.push_str("https://");
15} else {
16 url_prefix.push_str("http://");
17}
18
19url_prefix.push_str(domain);
20
21if is_https && port != 443 || !is_https && port != 80 {
22 url_prefix.push_str(":");
23 url_prefix.push_str(&port.to_string());
24}
25```
26
27Instead, we can easily use this crate to create URL prefix strings. For examples,
28
29```rust
30let prefix = url_prefix::create_prefix(url_prefix::Protocol::HTTPS, "magiclen.org", None, None::<String>);
31
32assert_eq!("https://magiclen.org", prefix);
33```
34
35```rust
36let prefix = url_prefix::create_prefix(url_prefix::Protocol::HTTPS, "magiclen.org", Some(8100), Some("url-prefix"));
37
38assert_eq!("https://magiclen.org:8100/url-prefix", prefix);
39```
40*/
41
42#![no_std]
43
44extern crate alloc;
45
46use alloc::string::String;
47use core::fmt::Write;
48
49macro_rules! impl_protocol {
50 ( $($protocol:ident, $name:expr, $port:expr); * $(;)* ) => {
51 /// A set of protocols for URLs.
52 #[allow(clippy::upper_case_acronyms)]
53 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
54 pub enum Protocol {
55 $(
56 $protocol,
57 )+
58 /// Your own custom protocol created by giving a name and a default port number.
59 Custom(String, u16)
60 }
61
62 impl Protocol{
63 /// Get a predefined protocol from a name (case-insensitive).
64 pub fn get_default_from_str<S: AsRef<str>>(s: S) -> Option<Self>{
65 let s = s.as_ref();
66
67 $(
68 if s.eq_ignore_ascii_case($name) {
69 return Some(Protocol::$protocol);
70 }
71 )+
72
73 None
74 }
75
76 /// Get the default port of this protocol.
77 pub fn get_default_port(&self) -> u16 {
78 match self {
79 $(
80 Protocol::$protocol => $port,
81 )+
82 Protocol::Custom(_, port) => *port
83 }
84 }
85
86 /// Get the name of this protocol.
87 pub fn get_name(&self) -> &str {
88 match self {
89 $(
90 Protocol::$protocol => $name,
91 )+
92 Protocol::Custom(name, _) => name
93 }
94 }
95 }
96 };
97}
98
99impl_protocol! {
100 HTTP, "http", 80;
101 HTTPS, "https", 443;
102 FTP, "ftp", 21;
103 WS, "ws", 80;
104 WSS, "wss", 443;
105}
106
107/// Create a URL prefix string.
108/// If `port` is equal to the default port of the protocol, it will be omitted.
109pub fn create_prefix(
110 protocol: Protocol,
111 domain: impl AsRef<str>,
112 port: Option<u16>,
113 path: Option<impl AsRef<str>>,
114) -> String {
115 let protocol_name = protocol.get_name();
116 let domain = domain.as_ref();
117
118 // reserve 3 bytes for "://", 6 bytes for the longest port part ":65535", and 1 byte for the slash before the path
119 let mut prefix = String::with_capacity(
120 protocol_name.len()
121 + 3
122 + domain.len()
123 + 6
124 + path.as_ref().map_or(0, |p| p.as_ref().len() + 1),
125 );
126
127 prefix.push_str(protocol_name);
128 prefix.push_str("://");
129 prefix.push_str(domain);
130
131 if let Some(port) = port {
132 if port != protocol.get_default_port() {
133 write!(prefix, ":{port}").unwrap();
134 }
135 }
136
137 if let Some(path) = path {
138 slash_formatter::concat_with_slash_in_place(&mut prefix, path.as_ref());
139 }
140
141 prefix
142}