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