url-prefix 2.0.5

A library for creating URL prefix strings.
Documentation
/*!
# URL Prefix

This crate can be used to create URL prefix strings by inputting a protocol, a domain, a port number and a path without additional parsing.

## Why We Need This?

Sometimes 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:

```rust,ignore
let mut url_prefix = String::new();

if is_https {
    url_prefix.push_str("https://");
} else {
    url_prefix.push_str("http://");
}

url_prefix.push_str(domain);

if is_https && port != 443 || !is_https && port != 80 {
    url_prefix.push_str(":");
    url_prefix.push_str(&port.to_string());
}
```

Instead, we can easily use this crate to create URL prefix strings. For examples,

```rust
let prefix = url_prefix::create_prefix(url_prefix::Protocol::HTTPS, "magiclen.org", None, None::<String>);

assert_eq!("https://magiclen.org", prefix);
```

```rust
let prefix = url_prefix::create_prefix(url_prefix::Protocol::HTTPS, "magiclen.org", Some(8100), Some("url-prefix"));

assert_eq!("https://magiclen.org:8100/url-prefix", prefix);
```
*/

#![no_std]

extern crate alloc;

use alloc::string::String;
use core::fmt::Write;

macro_rules! impl_protocol {
    ( $($protocol:ident, $name:expr, $port:expr); * $(;)* ) => {
        /// A set of protocols for URLs.
        #[allow(clippy::upper_case_acronyms)]
        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
        pub enum Protocol {
            $(
                $protocol,
            )+
            /// Your own custom protocol created by giving a name and a default port number.
            Custom(String, u16)
        }

        impl Protocol{
            /// Get a predefined protocol from a name (case-insensitive).
            pub fn get_default_from_str<S: AsRef<str>>(s: S) -> Option<Self>{
                let s = s.as_ref();

                $(
                    if s.eq_ignore_ascii_case($name) {
                        return Some(Protocol::$protocol);
                    }
                )+

                None
            }

            /// Get the default port of this protocol.
            pub fn get_default_port(&self) -> u16 {
                match self {
                    $(
                        Protocol::$protocol => $port,
                    )+
                    Protocol::Custom(_, port) => *port
                }
            }

            /// Get the name of this protocol.
            pub fn get_name(&self) -> &str {
                match self {
                    $(
                        Protocol::$protocol => $name,
                    )+
                    Protocol::Custom(name, _) => name
                }
            }
        }
    };
}

impl_protocol! {
    HTTP, "http", 80;
    HTTPS, "https", 443;
    FTP, "ftp", 21;
    WS, "ws", 80;
    WSS, "wss", 443;
}

/// Create a URL prefix string.
/// If `port` is equal to the default port of the protocol, it will be omitted.
pub fn create_prefix(
    protocol: Protocol,
    domain: impl AsRef<str>,
    port: Option<u16>,
    path: Option<impl AsRef<str>>,
) -> String {
    let protocol_name = protocol.get_name();
    let domain = domain.as_ref();

    // reserve 3 bytes for "://", 6 bytes for the longest port part ":65535", and 1 byte for the slash before the path
    let mut prefix = String::with_capacity(
        protocol_name.len()
            + 3
            + domain.len()
            + 6
            + path.as_ref().map_or(0, |p| p.as_ref().len() + 1),
    );

    prefix.push_str(protocol_name);
    prefix.push_str("://");
    prefix.push_str(domain);

    if let Some(port) = port {
        if port != protocol.get_default_port() {
            write!(prefix, ":{port}").unwrap();
        }
    }

    if let Some(path) = path {
        slash_formatter::concat_with_slash_in_place(&mut prefix, path.as_ref());
    }

    prefix
}