Initial commit
This commit is contained in:
commit
ca61ad8b99
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal 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
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# A simple Url primitive
|
||||
[](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
7
src/error.rs
Normal 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
185
src/lib.rs
Normal 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(), "/");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user