templated_uri 0.2.1

Standards-compliant URI handling with templating, validation, and data classification
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_logo_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/templated_uri/logo.png")]
#![doc(html_favicon_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/templated_uri/favicon.ico")]

//! Standards-compliant URI handling with templating, validation, and data classification.
//!
//! This crate provides comprehensive URI manipulation capabilities designed for HTTP clients
//! and servers that need type-safe, efficient, and data classification-aware URI handling. It builds
//! on top of the standard `http` crate while adding additional validation guarantees, templating
//! capabilities, and data classification features.
//!
//! # Core Types
//!
//! The crate centers around several key abstractions:
//!
//! - [`Uri`] - Flexible URI type composed of an optional [`BaseUri`] and an optional path/query
//! - [`BaseUri`] - Lightweight type representing scheme, authority, and optional base path ([`BasePath`])
//! - [`PathAndQueryTemplate`] - RFC 6570 Level 3 compliant URI templating
//! - [`Escaped`] and [`EscapedString`] - Generic newtype wrapper proving a value is properly escaped for URI components
//!   by not containing any reserved characters
//!
//! # Basic Usage
//! ## Simple URI Construction
//!
//! ```rust
//! use templated_uri::{BaseUri, PathAndQuery, Uri};
//!
//! // Create the base (scheme + authority, optionally a path prefix)
//! let base_uri = BaseUri::from_static("https://api.example.com");
//!
//! // Create a path (can be static for zero-allocation)
//! let path: PathAndQuery = PathAndQuery::from_static("/api/v1/users");
//!
//! // Combine into complete URI
//! let uri = Uri::default().with_base(base_uri).with_path_and_query(path);
//! assert_eq!(
//!     uri.to_string().declassify_ref(),
//!     "https://api.example.com/api/v1/users"
//! );
//! ```
//!
//! ## Templated URIs
//!
//! For dynamic URIs with variable components, use the templating system:
//!
//! ```rust
//! use templated_uri::{BaseUri, EscapedString, PathAndQueryTemplate, Uri, templated};
//!
//! #[templated(template = "/users/{user_id}/posts/{post_id}", unredacted)]
//! #[derive(Clone)]
//! struct UserPostPath {
//!     user_id: u32,
//!     post_id: EscapedString,
//! }
//!
//! let path = UserPostPath {
//!     user_id: 42,
//!     post_id: EscapedString::escape("my-post"),
//! };
//!
//! let uri = Uri::default()
//!     .with_base(BaseUri::from_static("https://api.example.com"))
//!     .with_path_and_query(path);
//! ```
//!
//! # URI Escaping Guarantees
//!
//! The [`Escaped<T>`](Escaped) newtype wraps values that are guaranteed
//! to contain only valid URI characters. This prevents common URI injection vulnerabilities:
//!
//! ```rust
//! use templated_uri::EscapedString;
//!
//! // This will succeed - percent-encodes any invalid characters
//! let encoded = EscapedString::escape("hello world?foo=bar");
//! assert_eq!(encoded.as_str(), "hello%20world%3Ffoo%3Dbar");
//!
//! // This will succeed - contains only valid characters
//! let valid = EscapedString::try_new("hello-world_123").unwrap();
//! assert_eq!(valid.as_str(), "hello-world_123");
//!
//! // try_new() fails on URI-reserved characters
//! let invalid = EscapedString::try_new("hello world?foo=bar");
//! assert!(invalid.is_err());
//! ```
//!
//! Built-in valid types include numeric types (`u32`, `u64`, etc.), `Uuid` (with the `uuid` feature),
//! IP addresses, and validated [`EscapedString`] instances.
//!
//! # Telemetry Labels
//!
//! For complex templates, use the `label` attribute to provide a concise identifier
//! for telemetry. When present, the label takes precedence over the template string.
//!
//! ```rust
//! use templated_uri::{EscapedString, templated};
//!
//! #[templated(
//!     template = "/{org}/users/{user_id}/reports/{report_type}",
//!     label = "user_report",
//!     unredacted
//! )]
//! struct ReportPath {
//!     org: EscapedString,
//!     user_id: EscapedString,
//!     report_type: EscapedString,
//! }
//! ```
//!
//! # Data Classification
//!
//! The crate integrates with `data_privacy` to track data sensitivity levels
//! in URIs. This is particularly important for compliance and data security:
//!
//! ```rust
//! use data_privacy::Sensitive;
//! use templated_uri::{EscapedString, templated};
//!
//! #[templated(template = "/{org_id}/user/{user_id}/")]
//! #[derive(Clone)]
//! struct UserPath {
//!     #[unredacted]
//!     org_id: EscapedString,
//!     user_id: Sensitive<EscapedString>,
//! }
//! ```
//!
//! # RFC 6570 Template Compliance
//!
//! The templating system implements [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570)
//! Level 3 URI Template specification. Supported expansions include:
//!
//! - Simple string expansion: `{var}`
//! - Reserved string expansion: `{+var}`
//! - Path segments: `{/var}`
//! - Query parameters: `{?var}`
//! - Query continuation: `{&var}`
//!
//! Note: Fragment expansion (`{#var}`) from RFC 6570 is **not supported** because URI
//! fragments are stripped by the `http` crate and ignored by HTTP clients.
//!
//! Template variables must implement [`Escape`] (except for reserved expansions,
//! which use [`Raw`]) to ensure the resulting URI is valid.
//!
//! ## Undefined Values (`Option<T>`)
//!
//! Per [RFC 6570 section 2.3](https://datatracker.ietf.org/doc/html/rfc6570#section-2.3), template
//! variables may be *undefined*. Use `Option<T>` to model this: a `None` value is treated
//! as undefined and the variable (along with its prefix or separator) is omitted from the
//! rendered URI.
//!
//! ```rust
//! use templated_uri::{EscapedString, PathAndQueryTemplate, templated};
//!
//! #[templated(template = "/items{?query,limit}", unredacted)]
//! struct ItemSearch {
//!     query: EscapedString,
//!     limit: Option<u32>,
//! }
//!
//! // With limit defined:
//! let path = ItemSearch {
//!     query: EscapedString::from_static("rust"),
//!     limit: Some(10),
//! };
//! assert_eq!(path.render(), "/items?query=rust&limit=10");
//!
//! // With limit undefined:
//! let path = ItemSearch {
//!     query: EscapedString::from_static("rust"),
//!     limit: None,
//! };
//! assert_eq!(path.render(), "/items?query=rust");
//! ```
//!
//! # Integration with HTTP Ecosystem
//!
//! This crate seamlessly integrates with the broader Rust HTTP ecosystem by re-exporting
//! and building upon the standard [`http`](https://docs.rs/http/latest/http/) crate types. The resulting [`Uri`] can be converted
//! to an [`http::Uri`] for use with HTTP clients
//! and servers based on [`hyper`](https://docs.rs/hyper/latest/hyper/) like [`reqwest`](https://docs.rs/reqwest/latest/reqwest/).

pub mod _documentation;

mod base_path;
mod base_uri;
mod error;
mod escape;
mod escaped;
mod macros;
mod origin;
mod path_and_query;
mod path_and_query_template;
mod uri;

pub use base_path::BasePath;
pub use base_uri::BaseUri;
pub use error::UriError;
pub use escape::{Escape, Raw};
pub use escaped::{EscapeError, Escaped, EscapedString};
// Re-export the `http` crate so macro-generated code can refer to
// `http::uri::PathAndQuery` via `::templated_uri::http` without requiring
// downstream crates to depend on `http` directly.
#[doc(hidden)]
pub use http;
pub use http::uri::{Authority, Scheme};
pub use macros::{Escape, Raw, templated};
pub use origin::Origin;
pub use path_and_query::PathAndQuery;
pub use path_and_query_template::PathAndQueryTemplate;
pub use uri::Uri;