Skip to main content

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}