Initial commit

This commit is contained in:
Rasmus Melchior Jacobsen 2023-01-30 09:48:57 +01:00
commit ca61ad8b99
5 changed files with 223 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/Cargo.lock

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "tiny-url"
version = "0.1.0"
edition = "2021"
repository = "https://github.com/rmja/tinyurl"
authors = ["Rasmus Melchior Jacobsen <rmja@laesoe.org>"]
license = "Apache-2.0 or MIT"
keywords = ["embedded", "url", "no_std"]
[dependencies]

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# A simple Url primitive
[![crates.io](https://img.shields.io/crates/v/tinyurl.svg)](https://crates.io/crates/tinyurl)
This crate provides a simple `Url` type that can be used in embedded `no_std` environments.
If you are missing a feature or would like to add a new scheme, please raise an issue or a PR.
The crate runs on stable rust.
## Example
```rust
let url = Url::parse("http://localhost/foo/bar").unwrap();
assert_eq!(url.scheme(), UrlScheme::HTTP);
assert_eq!(url.host(), "localhost");
assert_eq!(url.port_or_default(), 80);
assert_eq!(url.path(), "/foo/bar");
```
The implementation is heavily inspired (close to copy/pase) from the Url type in [reqwless](https://github.com/drogue-iot/reqwless).

7
src/error.rs Normal file
View File

@ -0,0 +1,7 @@
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// The url did not start with <scheme>://
NoScheme,
/// The sceme in the url is not known
UnsupportedScheme,
}

185
src/lib.rs Normal file
View File

@ -0,0 +1,185 @@
#![no_std]
mod error;
pub use error::Error;
/// A parsed URL to extract different parts of the URL.
pub struct Url<'a> {
host: &'a str,
scheme: UrlScheme,
port: Option<u16>,
path: &'a str,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum UrlScheme {
/// HTTP scheme
HTTP,
/// HTTPS (HTTP + TLS) scheme
HTTPS,
/// MQTT scheme
MQTT,
/// MQTTS (MQTT + TLS) scheme
MQTTS,
}
impl UrlScheme {
/// Get the default port for scheme
pub const fn default_port(&self) -> u16 {
match self {
UrlScheme::HTTP => 80,
UrlScheme::HTTPS => 443,
UrlScheme::MQTT => 1883,
UrlScheme::MQTTS => 8883,
}
}
}
impl<'a> Url<'a> {
/// Parse the provided url
pub fn parse(url: &'a str) -> Result<Url<'a>, Error> {
let mut parts = url.split("://");
let scheme = parts.next().unwrap();
let host_port_path = parts.next().ok_or(Error::NoScheme)?;
let scheme = if scheme.eq_ignore_ascii_case("http") {
Ok(UrlScheme::HTTP)
} else if scheme.eq_ignore_ascii_case("https") {
Ok(UrlScheme::HTTPS)
} else {
Err(Error::UnsupportedScheme)
}?;
let (host, port, path) = if let Some(port_delim) = host_port_path.find(':') {
// Port is defined
let host = &host_port_path[..port_delim];
let rest = &host_port_path[port_delim..];
let (port, path) = if let Some(path_delim) = rest.find('/') {
let port = rest[1..path_delim].parse::<u16>().ok();
let path = &rest[path_delim..];
let path = if path.is_empty() { "/" } else { path };
(port, path)
} else {
let port = rest[1..].parse::<u16>().ok();
(port, "/")
};
(host, port, path)
} else {
let (host, path) = if let Some(needle) = host_port_path.find('/') {
let host = &host_port_path[..needle];
let path = &host_port_path[needle..];
(host, if path.is_empty() { "/" } else { path })
} else {
(host_port_path, "/")
};
(host, None, path)
};
Ok(Self {
scheme,
host,
path,
port,
})
}
/// Get the url scheme
pub fn scheme(&self) -> UrlScheme {
self.scheme
}
/// Get the url host
pub fn host(&self) -> &'a str {
self.host
}
/// Get the url port if specified
pub fn port(&self) -> Option<u16> {
self.port
}
/// Get the url port or the default port for the scheme
pub fn port_or_default(&self) -> u16 {
self.port.unwrap_or_else(|| self.scheme.default_port())
}
/// Get the url path
pub fn path(&self) -> &'a str {
self.path
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
#[test]
fn test_parse_no_scheme() {
assert_eq!(Error::NoScheme, Url::parse("").err().unwrap());
assert_eq!(Error::NoScheme, Url::parse("http:/").err().unwrap());
}
#[test]
fn test_parse_unsupported_scheme() {
assert_eq!(
Error::UnsupportedScheme,
Url::parse("something://").err().unwrap()
);
}
#[test]
fn test_parse_no_host() {
let url = Url::parse("http://").unwrap();
assert_eq!(url.scheme(), UrlScheme::HTTP);
assert_eq!(url.host(), "");
assert_eq!(url.port_or_default(), 80);
assert_eq!(url.path(), "/");
}
#[test]
fn test_parse_minimal() {
let url = Url::parse("http://localhost").unwrap();
assert_eq!(url.scheme(), UrlScheme::HTTP);
assert_eq!(url.host(), "localhost");
assert_eq!(url.port_or_default(), 80);
assert_eq!(url.path(), "/");
}
#[test]
fn test_parse_path() {
let url = Url::parse("http://localhost/foo/bar").unwrap();
assert_eq!(url.scheme(), UrlScheme::HTTP);
assert_eq!(url.host(), "localhost");
assert_eq!(url.port_or_default(), 80);
assert_eq!(url.path(), "/foo/bar");
}
#[test]
fn test_parse_port() {
let url = Url::parse("http://localhost:8088").unwrap();
assert_eq!(url.scheme(), UrlScheme::HTTP);
assert_eq!(url.host(), "localhost");
assert_eq!(url.port().unwrap(), 8088);
assert_eq!(url.path(), "/");
}
#[test]
fn test_parse_port_path() {
let url = Url::parse("http://localhost:8088/foo/bar").unwrap();
assert_eq!(url.scheme(), UrlScheme::HTTP);
assert_eq!(url.host(), "localhost");
assert_eq!(url.port().unwrap(), 8088);
assert_eq!(url.path(), "/foo/bar");
}
#[test]
fn test_parse_scheme() {
let url = Url::parse("https://localhost/").unwrap();
assert_eq!(url.scheme(), UrlScheme::HTTPS);
assert_eq!(url.host(), "localhost");
assert_eq!(url.port_or_default(), 443);
assert_eq!(url.path(), "/");
}
}