initial v0.3.0
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.env
|
||||
32
Cargo.lock
generated
Normal file
32
Cargo.lock
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urigen"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "urigen"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
indexmap = "2.6.0"
|
||||
18
src/error.rs
Normal file
18
src/error.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
SchemeRequired,
|
||||
PathRequired,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::SchemeRequired => write!(f, "A scheme is required in order to construct a URI"),
|
||||
Self::PathRequired => write!(f, "A path is required in order to construct a URI"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
166
src/lib.rs
Normal file
166
src/lib.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Represents an authority of a URI.
|
||||
///
|
||||
/// ```
|
||||
/// use urigen::Authority;
|
||||
///
|
||||
/// let auth = Authority::new("example.com")
|
||||
/// .userinfo("admin")
|
||||
/// .port(100)
|
||||
/// .build();
|
||||
///
|
||||
/// assert_eq!(auth, "admin@example.com:100");
|
||||
/// ```
|
||||
pub struct Authority {
|
||||
userinfo: Option<String>,
|
||||
host: String,
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
impl Authority {
|
||||
/// Instantiates a new Authority.
|
||||
pub fn new(host: &str) -> Self {
|
||||
Self {
|
||||
userinfo: None,
|
||||
host: host.to_string(),
|
||||
port: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the user info of the Authority.
|
||||
pub fn userinfo(mut self, info: &str) -> Self {
|
||||
self.userinfo = Some(info.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the port of the Authority
|
||||
pub fn port(mut self, port: u16) -> Self {
|
||||
self.port = Some(port);
|
||||
self
|
||||
}
|
||||
|
||||
/// Convert an Authority into a String representation.
|
||||
pub fn build(self) -> String {
|
||||
let userinfo = self.userinfo.map(|x| format!("{x}@")).unwrap_or("".to_string());
|
||||
let host = self.host;
|
||||
let port = self.port.map(|x| format!(":{x}")).unwrap_or("".to_string());
|
||||
|
||||
format!("{userinfo}{host}{port}")
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder type for a URI
|
||||
///
|
||||
/// ```
|
||||
/// use urigen::{URI, Authority};
|
||||
///
|
||||
/// let uri = URI::new()
|
||||
/// .scheme("https")
|
||||
/// .authority(Authority::new("example.com"))
|
||||
/// .path("/index.html")
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(uri, "https://example.com/index.html".to_string());
|
||||
/// ```
|
||||
pub struct URI {
|
||||
scheme: Option<String>,
|
||||
authority: Option<Authority>,
|
||||
path: Option<String>,
|
||||
query: IndexMap<String, String>,
|
||||
fragment: Option<String>,
|
||||
}
|
||||
|
||||
impl URI {
|
||||
/// Instantiates a new URI.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scheme: None,
|
||||
authority: None,
|
||||
path: None,
|
||||
query: IndexMap::new(),
|
||||
fragment: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the scheme for the URI
|
||||
pub fn scheme(mut self, scheme: &str) -> Self {
|
||||
self.scheme = Some(scheme.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets an authority for the URI
|
||||
pub fn authority(mut self, authority: Authority) -> Self {
|
||||
self.authority = Some(authority);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the path for the URI
|
||||
pub fn path(mut self, path: &str) -> Self {
|
||||
self.path = Some(path.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a query to the URI
|
||||
///
|
||||
/// Queries will appear in the output string of a URI in the order that they are added.
|
||||
///
|
||||
/// ```
|
||||
/// use urigen::{URI, Authority};
|
||||
///
|
||||
/// let uri = URI::new()
|
||||
/// .scheme("https")
|
||||
/// .authority(Authority::new("example.com"))
|
||||
/// .path("/v1/images/search")
|
||||
/// .add_query("limit", "10")
|
||||
/// .add_query("api_key", "API_KEY")
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(uri, "https://example.com/v1/images/search?limit=10&api_key=API_KEY");
|
||||
/// ```
|
||||
pub fn add_query<K: ToString, V: ToString>(mut self, key: K, value: V) -> Self {
|
||||
self.query.insert(key.to_string(), value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Helper function to add multiple queries to a URI.
|
||||
///
|
||||
/// ```
|
||||
/// use urigen::{URI, Authority};
|
||||
///
|
||||
/// let uri = URI::new()
|
||||
/// .scheme("https")
|
||||
/// .authority(Authority::new("example.com"))
|
||||
/// .path("/v1/images/search")
|
||||
/// .add_queries([("limit", "10"), ("api_key", "API_KEY")])
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(uri, "https://example.com/v1/images/search?limit=10&api_key=API_KEY");
|
||||
/// ```
|
||||
pub fn add_queries<K: ToString, V: ToString>(self, queries: impl IntoIterator<Item = (K, V)>) -> Self {
|
||||
queries.into_iter().fold(self, |acc, (key, value)| acc.add_query(key, value))
|
||||
}
|
||||
|
||||
/// Convert a URI into a String representation.
|
||||
pub fn build(self) -> Result<String> {
|
||||
let scheme = self.scheme.ok_or(error::Error::SchemeRequired)?;
|
||||
let authority = self.authority.map(|x| format!("//{}", x.build())).unwrap_or("".to_string());
|
||||
let path = self.path.ok_or(error::Error::PathRequired)?;
|
||||
let query = if self.query.len() > 0 {
|
||||
format!("?{}", self.query.into_iter().map(|(key, value)| format!("{key}={value}")).collect::<Vec<String>>().join("&"))
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let fragment = self.fragment.map(|x| format!("#{x}")).unwrap_or("".to_string());
|
||||
|
||||
Ok(format!("{scheme}:{authority}{path}{query}{fragment}"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user