feat: add hurl! and local_folder! macros to make Url easier to create #135

Merged
letian merged 2 commits from url_macros_to_the_rescue into master 2025-09-08 14:43:42 +00:00
5 changed files with 108 additions and 4 deletions
Showing only changes of commit 6119355681 - Show all commits

1
Cargo.lock generated
View File

@ -2389,6 +2389,7 @@ dependencies = [
"serde", "serde",
"serde_yaml", "serde_yaml",
"syn 2.0.105", "syn 2.0.105",
"url",
] ]
[[package]] [[package]]

View File

@ -13,25 +13,25 @@ use harmony::{
}, },
topology::K8sAnywhereTopology, topology::K8sAnywhereTopology,
}; };
use harmony_types::net::Url; use harmony_macros::remote_url;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let application = Arc::new(RustWebapp { let application = Arc::new(RustWebapp {
name: "harmony-example-rust-webapp".to_string(), name: "harmony-example-rust-webapp".to_string(),
domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), domain: remote_url!("https://rustapp.harmony.example.com"),
project_root: PathBuf::from("./webapp"), // Relative from 'harmony-path' param project_root: PathBuf::from("./webapp"), // Relative from 'harmony-path' param
framework: Some(RustWebFramework::Leptos), framework: Some(RustWebFramework::Leptos),
}); });
let discord_receiver = DiscordWebhook { let discord_receiver = DiscordWebhook {
name: "test-discord".to_string(), name: "test-discord".to_string(),
url: Url::Url(url::Url::parse("https://discord.doesnt.exist.com").unwrap()), url: remote_url!("https://discord.doesnt.exist.com"),
}; };
let webhook_receiver = WebhookReceiver { let webhook_receiver = WebhookReceiver {
name: "sample-webhook-receiver".to_string(), name: "sample-webhook-receiver".to_string(),
url: Url::Url(url::Url::parse("https://webhook-doesnt-exist.com").unwrap()), url: remote_url!("https://webhook-doesnt-exist.com"),
}; };
let app = ApplicationScore { let app = ApplicationScore {

View File

@ -15,6 +15,7 @@ serde = "1.0.217"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
syn = "2.0.90" syn = "2.0.90"
cidr.workspace = true cidr.workspace = true
url.workspace = true
[dev-dependencies] [dev-dependencies]
serde = { version = "1.0.217", features = ["derive"] } serde = { version = "1.0.217", features = ["derive"] }

View File

@ -145,3 +145,71 @@ pub fn cidrv4(input: TokenStream) -> TokenStream {
panic!("Invalid IPv4 CIDR : {}", cidr_str); panic!("Invalid IPv4 CIDR : {}", cidr_str);
} }
/// Creates a `harmony_types::net::Url::Url` from a string literal.
///
/// This macro parses the input string as a URL at compile time and will cause a
/// compilation error if the string is not a valid URL.
///
/// # Example
///
/// ```
/// use harmony_types::net::Url;
/// use harmony_macros::remote_url;
///
/// let remote_url = remote_url!("https://example.com/path");
///
/// let expected_url = url::Url::parse("https://example.com/path").unwrap();
/// assert!(matches!(remote_url, Url::Url(expected_url)));
/// ```
///
/// The following example will fail to compile:
///
/// ```rust,compile_fail
/// use harmony_macros::remote_url;
///
/// // This is not a valid URL and will cause a compilation error.
/// let _invalid = remote_url!("not a valid url");
/// ```
#[proc_macro]
pub fn remote_url(input: TokenStream) -> TokenStream {
let input_lit = parse_macro_input!(input as LitStr);
let url_str = input_lit.value();
match ::url::Url::parse(&url_str) {
Ok(_) => {
let expanded = quote! {
::harmony_types::net::Url::Url(::url::Url::parse(#input_lit).unwrap())
};
TokenStream::from(expanded)
}
Err(e) => {
let err_msg = format!("Invalid URL: {e}");
syn::Error::new(input_lit.span(), err_msg)
.to_compile_error()
.into()
}
}
}
/// Creates a `harmony_types::net::Url::LocalFolder` from a string literal.
///
/// # Example
///
/// ```
/// use harmony_types::net::Url;
/// use harmony_macros::local_folder;
///
/// let local_path = local_folder!("/var/data/files");
///
/// let expected_path = String::from("/var/data/files");
/// assert!(matches!(local_path, Url::LocalFolder(expected_path)));
/// ```
#[proc_macro]
pub fn local_folder(input: TokenStream) -> TokenStream {
let input_lit = parse_macro_input!(input as LitStr);
let expanded = quote! {
::harmony_types::net::Url::LocalFolder(#input_lit.to_string())
};
TokenStream::from(expanded)
}

View File

@ -51,6 +51,40 @@ impl TryFrom<String> for MacAddress {
pub type IpAddress = std::net::IpAddr; pub type IpAddress = std::net::IpAddr;
/// Represents a URL, which can either be a remote URL or a local file path.
///
/// For convenience, the `harmony_macros` crate provides `remote_url!` and `local_folder!`
/// macros to construct `Url` variants from string literals.
///
/// # Examples
///
/// ### Manual Construction
///
/// The following example demonstrates how to build `Url` variants directly. This is
/// the standard approach if you are not using the `harmony_macros` crate.
///
/// ```
/// // The `use` statement below is for the doc test. In a real project,
/// // you would use `use harmony_types::Url;`
/// # use harmony_types::net::Url;
/// let remote_url = Url::Url(url::Url::parse("https://example.com").unwrap());
/// let local_path = Url::LocalFolder("/var/data".to_string());
///
/// assert!(matches!(remote_url, Url::Url(_)));
/// assert!(matches!(local_path, Url::LocalFolder(_)));
/// ```
///
/// ### Usage with `harmony_macros`
///
/// If `harmony_macros` is a dependency, you can create `Url`s more concisely.
///
/// ```rust,ignore
/// use harmony_macros::{remote_url, local_folder};
/// use harmony_types::Url;
///
/// let remote_url = remote_url!("https://example.com");
/// let local_path = local_folder!("/var/data");
/// ```
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Url { pub enum Url {
LocalFolder(String), LocalFolder(String),