Skip to main content

url_static/
lib.rs

1//! url-static provides a simple [`url!`] macro for URL validation at compile-time.
2//!
3//! # Examples
4//!
5//! ```rust
6//! # use url::Url;
7//! use url_static::url;
8//!
9//! // ✅ These will for sure work:
10//! let api = url!("https://api.example.com");
11//! assert_eq!(api, Url::parse("https://api.example.com").unwrap());
12//!
13//! let test = url!("http://localhost/");
14//! assert_eq!(test, Url::parse("http://localhost/").unwrap());
15//!
16//! let mailto = url!("mailto:also_works_for_mailto@example.com");
17//! assert_eq!(mailto, Url::parse("mailto:also_works_for_mailto@example.com").unwrap());
18//!
19//! // ✅ It even works with other macros:
20//! let repo = url!(env!("CARGO_PKG_REPOSITORY"));
21//! assert_eq!(repo, Url::parse(env!("CARGO_PKG_REPOSITORY")).unwrap());
22//! ```
23//! ```compile_fail
24//! // ❌ This won't compile, because it'll never parse:
25//! # use url_static::url;
26//! let woops = url!("https:// api.example.com");
27//! ```
28//! ```compile_fail
29//! // ❌ This isn't even a URL:
30//! # use url_static::url;
31//! let bad = url!("what even is this?");
32//! ```
33//!
34//! ## Feature flags
35#![doc = document_features::document_features!()]
36#![forbid(unsafe_code)]
37#![cfg_attr(feature = "unstable", feature(proc_macro_expand))]
38
39mod parsers;
40
41use crate::parsers::LitStrOrMacro;
42use proc_macro::TokenStream;
43use quote::{quote, quote_spanned};
44use syn::parse_macro_input;
45
46/// Ensures that the given expression is a valid URL.
47///
48/// Expands to [`url::Url::parse`] if the input is a valid URL.
49#[proc_macro]
50pub fn url(tokens: TokenStream) -> TokenStream {
51	#[cfg(feature = "unstable")]
52	let tokens = match tokens.expand_expr() {
53		Ok(e) => e,
54		Err(err) => {
55			let message = format!("{err}");
56			return quote! { ::core::compile_error!(#message) }.into();
57		}
58	};
59	let thing = parse_macro_input!(tokens as LitStrOrMacro);
60	let value = match thing.value() {
61		Ok(v) => v,
62		Err(err) => return err.into_compile_error().into(),
63	};
64	parse_url(value, thing.span())
65}
66
67fn parse_url(value: String, span: proc_macro2::Span) -> TokenStream {
68	match url::Url::parse(&value) {
69		Ok(url) => {
70			let url_str: String = url.into();
71			quote! { ::url::Url::parse(#url_str).unwrap() }.into()
72		}
73		Err(err) => {
74			// Bad URL! This would fail at runtime.
75			let err_msg = format!("{err}");
76			quote_spanned! { span=> ::core::compile_error!(#err_msg) }.into()
77		}
78	}
79}
80
81/// This just ensures that our README examples get tested too:
82#[doc = include_str!("../README.md")]
83#[cfg(doctest)]
84struct ReadmeDoctests;
85
86/// To ensure that bad URLs fail to validate at compile time:
87/// ```compile_fail
88/// use url_static::url;
89/// let not_static = "http://localhost";
90/// url!(not_static);
91/// ```
92/// ```compile_fail
93/// use url_static::url;
94/// let nope = url!(true); // not a string literal
95/// ```
96/// ```compile_fail
97/// use url_static::url;
98/// let nope = url!(42); // not a string literal
99/// ```
100/// ```compile_fail
101/// use url_static::url;
102/// let nope = url!(4 2); // still not a string literal
103/// ```
104/// ```compile_fail
105/// use url_static::url;
106/// let nope = url!("a b"); // ???
107/// ```
108/// ```compile_fail
109/// use url_static::url;
110/// let nope = url!("scheme"); // ????
111/// ```
112#[cfg(doctest)]
113struct CompilationTests;