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