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//!
24//! ```compile_fail
25//! // ❌ This won't compile, because it'll never parse:
26//! # use url_static::url;
27//! let woops = url!("https:// api.example.com");
28//! ```
29//!
30#![cfg_attr(all(doc, not(doctest)), doc = "## Feature flags")]
31#![cfg_attr(all(doc, not(doctest)), doc = document_features::document_features!())]
32#![forbid(unsafe_code)]
33#![cfg_attr(feature = "unstable", feature(proc_macro_expand))]
34
35mod parsers;
36
37use crate::parsers::LitStrOrMacro;
38use proc_macro::TokenStream;
39use quote::{quote, quote_spanned};
40use syn::parse_macro_input;
41
42/// Ensures that the given expression is a valid URL string.
43///
44/// # Proc-macro
45/// Expands to [`url::Url::parse`] with the given value if successful.
46/// The value must be or resolve to a string literal that is valid
47/// according to the `url` crate's parser.
48///
49/// # Errors
50/// Causes a compiler error if the given value is not a string
51/// literal or a macro that we can resolve to a string literal,
52/// or if the value is not a well-formed URL.
53///
54/// # Examples
55/// ```rust
56/// # use url::Url;
57/// # use url_static::url;
58/// let api = url!("https://api.example.com/v0/sample");
59/// assert_eq!(api, Url::parse("https://api.example.com/v0/sample").unwrap());
60///
61/// let repo = url!(env!("CARGO_PKG_REPOSITORY"));
62/// assert_eq!(repo, Url::parse(env!("CARGO_PKG_REPOSITORY")).unwrap());
63/// ```
64#[proc_macro]
65pub fn url(tokens: TokenStream) -> TokenStream {
66	#[cfg(feature = "unstable")]
67	let tokens = match tokens.expand_expr() {
68		Ok(e) => e,
69		Err(err) => {
70			let message = format!("{err}");
71			return quote! { ::core::compile_error!(#message) }.into();
72		}
73	};
74	let thing = parse_macro_input!(tokens as LitStrOrMacro);
75	let value = match thing.value() {
76		Ok(v) => v,
77		Err(err) => return err.into_compile_error().into(),
78	};
79	parse_url(value, thing.span())
80}
81
82fn parse_url(value: String, span: proc_macro2::Span) -> TokenStream {
83	match url::Url::parse(&value) {
84		Ok(url) => {
85			let url_str: String = url.into();
86			quote! { ::url::Url::parse(#url_str).unwrap() }.into()
87		}
88		Err(err) => {
89			// Bad URL! This would fail at runtime.
90			let err_msg = format!("{err}");
91			quote_spanned! { span=> ::core::compile_error!(#err_msg) }.into()
92		}
93	}
94}
95
96/// This just ensures that our README examples get tested too:
97#[doc = include_str!("../README.md")]
98#[cfg(doctest)]
99struct ReadmeDoctests;
100
101/// To ensure that bad URLs fail to validate at compile time:
102/// ```compile_fail
103/// use url_static::url;
104/// let not_static = "http://localhost";
105/// url!(not_static);
106/// ```
107/// ```compile_fail
108/// use url_static::url;
109/// let nope = url!(true); // not a string literal
110/// ```
111/// ```compile_fail
112/// use url_static::url;
113/// let nope = url!(42); // not a string literal
114/// ```
115/// ```compile_fail
116/// use url_static::url;
117/// let nope = url!(4 2); // still not a string literal
118/// ```
119/// ```compile_fail
120/// use url_static::url;
121/// let nope = url!("a b"); // ???
122/// ```
123/// ```compile_fail
124/// use url_static::url;
125/// let nope = url!("scheme"); // ????
126/// ```
127/// ```compile_fail
128/// # use url_static::url;
129/// let bad = url!("what even is this?");
130/// ```
131#[cfg(doctest)]
132struct CompilationTests;