Skip to main content

url_static/
lib.rs

1// Copyright 2026 AverageHelper.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! url-static provides a simple [`url!`] macro for URL validation at compile-time.
10//!
11//! # Examples
12//!
13//! ```rust
14//! # use url::Url;
15//! use url_static::url;
16//!
17//! // ✅ These will for sure work:
18//! let api = url!("https://api.example.com");
19//! assert_eq!(api, Url::parse("https://api.example.com").unwrap());
20//!
21//! let test = url!("http://localhost/");
22//! assert_eq!(test, Url::parse("http://localhost/").unwrap());
23//!
24//! let mailto = url!("mailto:also_works_for_mailto@example.com");
25//! assert_eq!(mailto, Url::parse("mailto:also_works_for_mailto@example.com").unwrap());
26//!
27//! // ✅ It even works with other macros:
28//! let repo = url!(env!("CARGO_PKG_REPOSITORY"));
29//! assert_eq!(repo, Url::parse(env!("CARGO_PKG_REPOSITORY")).unwrap());
30//! ```
31//!
32//! ```compile_fail
33//! // ❌ This won't compile, because it'll never parse:
34//! # use url_static::url;
35//! let woops = url!("https:// api.example.com");
36//! ```
37//!
38#![cfg_attr(all(doc, not(doctest)), doc = "## Feature flags")]
39#![cfg_attr(all(doc, not(doctest)), doc = document_features::document_features!())]
40#![forbid(unsafe_code)]
41#![cfg_attr(feature = "unstable", feature(proc_macro_expand))]
42
43mod parsers;
44
45use crate::parsers::LitStrOrMacro;
46use proc_macro::TokenStream;
47use quote::{quote, quote_spanned};
48use syn::parse_macro_input;
49
50/// Ensures that the given expression is a valid URL string.
51///
52/// # Proc-macro
53/// Expands to [`url::Url::parse`] with the given value if successful.
54/// The value must be or resolve to a string literal that is valid
55/// according to the `url` crate's parser.
56///
57/// # Errors
58/// Causes a compiler error if the given value is not a string
59/// literal or a macro that we can resolve to a string literal,
60/// or if the value is not a well-formed URL.
61///
62/// # Examples
63/// ```rust
64/// # use url::Url;
65/// # use url_static::url;
66/// let api = url!("https://api.example.com/v0/sample");
67/// assert_eq!(api, Url::parse("https://api.example.com/v0/sample").unwrap());
68///
69/// let repo = url!(env!("CARGO_PKG_REPOSITORY"));
70/// assert_eq!(repo, Url::parse(env!("CARGO_PKG_REPOSITORY")).unwrap());
71/// ```
72#[proc_macro]
73pub fn url(tokens: TokenStream) -> TokenStream {
74	#[cfg(feature = "unstable")]
75	let tokens = match tokens.expand_expr() {
76		Ok(e) => e,
77		Err(err) => {
78			let message = format!("{err}");
79			return quote! { ::core::compile_error!(#message) }.into();
80		}
81	};
82	let thing = parse_macro_input!(tokens as LitStrOrMacro);
83	let value = match thing.value() {
84		Ok(v) => v,
85		Err(err) => return err.into_compile_error().into(),
86	};
87	parse_url(value, thing.span())
88}
89
90fn parse_url(value: String, span: proc_macro2::Span) -> TokenStream {
91	match url::Url::parse(&value) {
92		Ok(url) => {
93			let url_str: String = url.into();
94			quote! { ::url::Url::parse(#url_str).unwrap() }.into()
95		}
96		Err(err) => {
97			// Bad URL! This would fail at runtime.
98			let err_msg = format!("{err}");
99			quote_spanned! { span=> ::core::compile_error!(#err_msg) }.into()
100		}
101	}
102}
103
104/// This just ensures that our README examples get tested too:
105#[doc = include_str!("../README.md")]
106#[cfg(doctest)]
107struct ReadmeDoctests;
108
109/// To ensure that bad URLs fail to validate at compile time:
110/// ```compile_fail
111/// use url_static::url;
112/// let not_static = "http://localhost";
113/// url!(not_static);
114/// ```
115/// ```compile_fail
116/// use url_static::url;
117/// let nope = url!(true); // not a string literal
118/// ```
119/// ```compile_fail
120/// use url_static::url;
121/// let nope = url!(42); // not a string literal
122/// ```
123/// ```compile_fail
124/// use url_static::url;
125/// let nope = url!(4 2); // still not a string literal
126/// ```
127/// ```compile_fail
128/// use url_static::url;
129/// let nope = url!("a b"); // ???
130/// ```
131/// ```compile_fail
132/// use url_static::url;
133/// let nope = url!("scheme"); // ????
134/// ```
135/// ```compile_fail
136/// # use url_static::url;
137/// let bad = url!("what even is this?");
138/// ```
139#[cfg(doctest)]
140struct CompilationTests;