From 45df4d0d9b577fecee798d672695fe24ff57fb1b Mon Sep 17 00:00:00 2001 From: mo khan Date: Tue, 15 Jul 2025 16:37:08 -0600 Subject: feat: migrate from Cedar to SpiceDB authorization system This is a major architectural change that replaces the Cedar policy-based authorization system with SpiceDB's relation-based authorization. Key changes: - Migrate from Rust to Go implementation - Replace Cedar policies with SpiceDB schema and relationships - Switch from envoy `ext_authz` with Cedar to SpiceDB permission checks - Update build system and dependencies for Go ecosystem - Maintain Envoy integration for external authorization This change enables more flexible permission modeling through SpiceDB's Google Zanzibar inspired relation-based system, supporting complex hierarchical permissions that were difficult to express in Cedar. Breaking change: Existing Cedar policies and Rust-based configuration will no longer work and need to be migrated to SpiceDB schema. --- vendor/displaydoc/src/attr.rs | 137 -------------- vendor/displaydoc/src/expand.rs | 409 ---------------------------------------- vendor/displaydoc/src/fmt.rs | 159 ---------------- vendor/displaydoc/src/lib.rs | 186 ------------------ 4 files changed, 891 deletions(-) delete mode 100644 vendor/displaydoc/src/attr.rs delete mode 100644 vendor/displaydoc/src/expand.rs delete mode 100644 vendor/displaydoc/src/fmt.rs delete mode 100644 vendor/displaydoc/src/lib.rs (limited to 'vendor/displaydoc/src') diff --git a/vendor/displaydoc/src/attr.rs b/vendor/displaydoc/src/attr.rs deleted file mode 100644 index afda4b90..00000000 --- a/vendor/displaydoc/src/attr.rs +++ /dev/null @@ -1,137 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{Attribute, LitStr, Meta, Result}; - -#[derive(Clone)] -pub(crate) struct Display { - pub(crate) fmt: LitStr, - pub(crate) args: TokenStream, -} - -pub(crate) struct VariantDisplay { - pub(crate) r#enum: Option, - pub(crate) variant: Display, -} - -impl ToTokens for Display { - fn to_tokens(&self, tokens: &mut TokenStream) { - let fmt = &self.fmt; - let args = &self.args; - tokens.extend(quote! { - write!(formatter, #fmt #args) - }); - } -} - -impl ToTokens for VariantDisplay { - fn to_tokens(&self, tokens: &mut TokenStream) { - if let Some(ref r#enum) = self.r#enum { - r#enum.to_tokens(tokens); - tokens.extend(quote! { ?; write!(formatter, ": ")?; }); - } - self.variant.to_tokens(tokens); - } -} - -pub(crate) struct AttrsHelper { - ignore_extra_doc_attributes: bool, - prefix_enum_doc_attributes: bool, -} - -impl AttrsHelper { - pub(crate) fn new(attrs: &[Attribute]) -> Self { - let ignore_extra_doc_attributes = attrs - .iter() - .any(|attr| attr.path().is_ident("ignore_extra_doc_attributes")); - let prefix_enum_doc_attributes = attrs - .iter() - .any(|attr| attr.path().is_ident("prefix_enum_doc_attributes")); - - Self { - ignore_extra_doc_attributes, - prefix_enum_doc_attributes, - } - } - - pub(crate) fn display(&self, attrs: &[Attribute]) -> Result> { - let displaydoc_attr = attrs.iter().find(|attr| attr.path().is_ident("displaydoc")); - - if let Some(displaydoc_attr) = displaydoc_attr { - let lit = displaydoc_attr - .parse_args() - .expect("#[displaydoc(\"foo\")] must contain string arguments"); - let mut display = Display { - fmt: lit, - args: TokenStream::new(), - }; - - display.expand_shorthand(); - return Ok(Some(display)); - } - - let num_doc_attrs = attrs - .iter() - .filter(|attr| attr.path().is_ident("doc")) - .count(); - - if !self.ignore_extra_doc_attributes && num_doc_attrs > 1 { - panic!("Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive."); - } - - for attr in attrs { - if attr.path().is_ident("doc") { - let lit = match &attr.meta { - Meta::NameValue(syn::MetaNameValue { - value: - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }), - .. - }) => lit, - _ => unimplemented!(), - }; - - // Make an attempt at cleaning up multiline doc comments. - let doc_str = lit - .value() - .lines() - .map(|line| line.trim().trim_start_matches('*').trim()) - .collect::>() - .join("\n"); - - let lit = LitStr::new(doc_str.trim(), lit.span()); - - let mut display = Display { - fmt: lit, - args: TokenStream::new(), - }; - - display.expand_shorthand(); - return Ok(Some(display)); - } - } - - Ok(None) - } - - pub(crate) fn display_with_input( - &self, - r#enum: &[Attribute], - variant: &[Attribute], - ) -> Result> { - let r#enum = if self.prefix_enum_doc_attributes { - let result = self - .display(r#enum)? - .expect("Missing doc comment on enum with #[prefix_enum_doc_attributes]. Please remove the attribute or add a doc comment to the enum itself."); - - Some(result) - } else { - None - }; - - Ok(self - .display(variant)? - .map(|variant| VariantDisplay { r#enum, variant })) - } -} diff --git a/vendor/displaydoc/src/expand.rs b/vendor/displaydoc/src/expand.rs deleted file mode 100644 index 3b146f81..00000000 --- a/vendor/displaydoc/src/expand.rs +++ /dev/null @@ -1,409 +0,0 @@ -use super::attr::AttrsHelper; -use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; -use syn::{ - punctuated::Punctuated, - token::{Colon, Comma, PathSep, Plus, Where}, - Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Path, PathArguments, - PathSegment, PredicateType, Result, TraitBound, TraitBoundModifier, Type, TypeParam, - TypeParamBound, TypePath, WhereClause, WherePredicate, -}; - -use std::collections::HashMap; - -pub(crate) fn derive(input: &DeriveInput) -> Result { - let impls = match &input.data { - Data::Struct(data) => impl_struct(input, data), - Data::Enum(data) => impl_enum(input, data), - Data::Union(_) => Err(Error::new_spanned(input, "Unions are not supported")), - }?; - - let helpers = specialization(); - Ok(quote! { - #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] - const _: () = { - #helpers - #impls - }; - }) -} - -#[cfg(feature = "std")] -fn specialization() -> TokenStream { - quote! { - trait DisplayToDisplayDoc { - fn __displaydoc_display(&self) -> Self; - } - - impl DisplayToDisplayDoc for &T { - fn __displaydoc_display(&self) -> Self { - self - } - } - - // If the `std` feature gets enabled we want to ensure that any crate - // using displaydoc can still reference the std crate, which is already - // being compiled in by whoever enabled the `std` feature in - // `displaydoc`, even if the crates using displaydoc are no_std. - extern crate std; - - trait PathToDisplayDoc { - fn __displaydoc_display(&self) -> std::path::Display<'_>; - } - - impl PathToDisplayDoc for std::path::Path { - fn __displaydoc_display(&self) -> std::path::Display<'_> { - self.display() - } - } - - impl PathToDisplayDoc for std::path::PathBuf { - fn __displaydoc_display(&self) -> std::path::Display<'_> { - self.display() - } - } - } -} - -#[cfg(not(feature = "std"))] -fn specialization() -> TokenStream { - quote! {} -} - -fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result { - let ty = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let where_clause = generate_where_clause(&input.generics, where_clause); - - let helper = AttrsHelper::new(&input.attrs); - - let display = helper.display(&input.attrs)?.map(|display| { - let pat = match &data.fields { - Fields::Named(fields) => { - let var = fields.named.iter().map(|field| &field.ident); - quote!(Self { #(#var),* }) - } - Fields::Unnamed(fields) => { - let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{}", i)); - quote!(Self(#(#var),*)) - } - Fields::Unit => quote!(_), - }; - quote! { - impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { - fn fmt(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - // NB: This destructures the fields of `self` into named variables (for unnamed - // fields, it uses _0, _1, etc as above). The `#[allow(unused_variables)]` - // section means it doesn't have to parse the individual field references out of - // the docstring. - #[allow(unused_variables)] - let #pat = self; - #display - } - } - } - }); - - Ok(quote! { #display }) -} - -/// Create a `where` predicate for `ident`, without any [bound][TypeParamBound]s yet. -fn new_empty_where_type_predicate(ident: Ident) -> PredicateType { - let mut path_segments = Punctuated::::new(); - path_segments.push_value(PathSegment { - ident, - arguments: PathArguments::None, - }); - PredicateType { - lifetimes: None, - bounded_ty: Type::Path(TypePath { - qself: None, - path: Path { - leading_colon: None, - segments: path_segments, - }, - }), - colon_token: Colon { - spans: [Span::call_site()], - }, - bounds: Punctuated::::new(), - } -} - -/// Create a `where` clause that we can add [WherePredicate]s to. -fn new_empty_where_clause() -> WhereClause { - WhereClause { - where_token: Where { - span: Span::call_site(), - }, - predicates: Punctuated::::new(), - } -} - -enum UseGlobalPrefix { - LeadingColon, - #[allow(dead_code)] - NoLeadingColon, -} - -/// Create a path with segments composed of [Idents] *without* any [PathArguments]. -fn join_paths(name_segments: &[&str], use_global_prefix: UseGlobalPrefix) -> Path { - let mut segments = Punctuated::::new(); - assert!(!name_segments.is_empty()); - segments.push_value(PathSegment { - ident: Ident::new(name_segments[0], Span::call_site()), - arguments: PathArguments::None, - }); - for name in name_segments[1..].iter() { - segments.push_punct(PathSep { - spans: [Span::call_site(), Span::mixed_site()], - }); - segments.push_value(PathSegment { - ident: Ident::new(name, Span::call_site()), - arguments: PathArguments::None, - }); - } - Path { - leading_colon: match use_global_prefix { - UseGlobalPrefix::LeadingColon => Some(PathSep { - spans: [Span::call_site(), Span::mixed_site()], - }), - UseGlobalPrefix::NoLeadingColon => None, - }, - segments, - } -} - -/// Push `new_type_predicate` onto the end of `where_clause`. -fn append_where_clause_type_predicate( - where_clause: &mut WhereClause, - new_type_predicate: PredicateType, -) { - // Push a comma at the end if there are already any `where` predicates. - if !where_clause.predicates.is_empty() { - where_clause.predicates.push_punct(Comma { - spans: [Span::call_site()], - }); - } - where_clause - .predicates - .push_value(WherePredicate::Type(new_type_predicate)); -} - -/// Add a requirement for [core::fmt::Display] to a `where` predicate for some type. -fn add_display_constraint_to_type_predicate( - predicate_that_needs_a_display_impl: &mut PredicateType, -) { - // Create a `Path` of `::core::fmt::Display`. - let display_path = join_paths(&["core", "fmt", "Display"], UseGlobalPrefix::LeadingColon); - - let display_bound = TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path: display_path, - }); - if !predicate_that_needs_a_display_impl.bounds.is_empty() { - predicate_that_needs_a_display_impl.bounds.push_punct(Plus { - spans: [Span::call_site()], - }); - } - - predicate_that_needs_a_display_impl - .bounds - .push_value(display_bound); -} - -/// Map each declared generic type parameter to the set of all trait boundaries declared on it. -/// -/// These boundaries may come from the declaration site: -/// pub enum E { ... } -/// or a `where` clause after the parameter declarations: -/// pub enum E where T: MyTrait { ... } -/// This method will return the boundaries from both of those cases. -fn extract_trait_constraints_from_source( - where_clause: &WhereClause, - type_params: &[&TypeParam], -) -> HashMap> { - // Add trait bounds provided at the declaration site of type parameters for the struct/enum. - let mut param_constraint_mapping: HashMap> = type_params - .iter() - .map(|type_param| { - let trait_bounds: Vec = type_param - .bounds - .iter() - .flat_map(|bound| match bound { - TypeParamBound::Trait(trait_bound) => Some(trait_bound), - _ => None, - }) - .cloned() - .collect(); - (type_param.ident.clone(), trait_bounds) - }) - .collect(); - - // Add trait bounds from `where` clauses, which may be type parameters or types containing - // those parameters. - for predicate in where_clause.predicates.iter() { - // We only care about type and not lifetime constraints here. - if let WherePredicate::Type(ref pred_ty) = predicate { - let ident = match &pred_ty.bounded_ty { - Type::Path(TypePath { path, qself: None }) => match path.get_ident() { - None => continue, - Some(ident) => ident, - }, - _ => continue, - }; - // We ignore any type constraints that aren't direct references to type - // parameters of the current enum of struct definition. No types can be - // constrained in a `where` clause unless they are a type parameter or a generic - // type instantiated with one of the type parameters, so by only allowing single - // identifiers, we can be sure that the constrained type is a type parameter - // that is contained in `param_constraint_mapping`. - if let Some((_, ref mut known_bounds)) = param_constraint_mapping - .iter_mut() - .find(|(id, _)| *id == ident) - { - for bound in pred_ty.bounds.iter() { - // We only care about trait bounds here. - if let TypeParamBound::Trait(ref bound) = bound { - known_bounds.push(bound.clone()); - } - } - } - } - } - - param_constraint_mapping -} - -/// Hygienically add `where _: Display` to the set of [TypeParamBound]s for `ident`, creating such -/// a set if necessary. -fn ensure_display_in_where_clause_for_type(where_clause: &mut WhereClause, ident: Ident) { - for pred_ty in where_clause - .predicates - .iter_mut() - // Find the `where` predicate constraining the current type param, if it exists. - .flat_map(|predicate| match predicate { - WherePredicate::Type(pred_ty) => Some(pred_ty), - // We're looking through type constraints, not lifetime constraints. - _ => None, - }) - { - // Do a complicated destructuring in order to check if the type being constrained in this - // `where` clause is the type we're looking for, so we can use the mutable reference to - // `pred_ty` if so. - let matches_desired_type = matches!( - &pred_ty.bounded_ty, - Type::Path(TypePath { path, .. }) if Some(&ident) == path.get_ident()); - if matches_desired_type { - add_display_constraint_to_type_predicate(pred_ty); - return; - } - } - - // If there is no `where` predicate for the current type param, we will construct one. - let mut new_type_predicate = new_empty_where_type_predicate(ident); - add_display_constraint_to_type_predicate(&mut new_type_predicate); - append_where_clause_type_predicate(where_clause, new_type_predicate); -} - -/// For all declared type parameters, add a [core::fmt::Display] constraint, unless the type -/// parameter already has any type constraint. -fn ensure_where_clause_has_display_for_all_unconstrained_members( - where_clause: &mut WhereClause, - type_params: &[&TypeParam], -) { - let param_constraint_mapping = extract_trait_constraints_from_source(where_clause, type_params); - - for (ident, known_bounds) in param_constraint_mapping.into_iter() { - // If the type parameter has any constraints already, we don't want to touch it, to avoid - // breaking use cases where a type parameter only needs to impl `Debug`, for example. - if known_bounds.is_empty() { - ensure_display_in_where_clause_for_type(where_clause, ident); - } - } -} - -/// Generate a `where` clause that ensures all generic type parameters `impl` -/// [core::fmt::Display] unless already constrained. -/// -/// This approach allows struct/enum definitions deriving [crate::Display] to avoid hardcoding -/// a [core::fmt::Display] constraint into every type parameter. -/// -/// If the type parameter isn't already constrained, we add a `where _: Display` clause to our -/// display implementation to expect to be able to format every enum case or struct member. -/// -/// In fact, we would preferably only require `where _: Display` or `where _: Debug` where the -/// format string actually requires it. However, while [`std::fmt` defines a formal syntax for -/// `format!()`][format syntax], it *doesn't* expose the actual logic to parse the format string, -/// which appears to live in [`rustc_parse_format`]. While we use the [`syn`] crate to parse rust -/// syntax, it also doesn't currently provide any method to introspect a `format!()` string. It -/// would be nice to contribute this upstream in [`syn`]. -/// -/// [format syntax]: std::fmt#syntax -/// [`rustc_parse_format`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_parse_format/index.html -fn generate_where_clause(generics: &Generics, where_clause: Option<&WhereClause>) -> WhereClause { - let mut where_clause = where_clause.cloned().unwrap_or_else(new_empty_where_clause); - let type_params: Vec<&TypeParam> = generics.type_params().collect(); - ensure_where_clause_has_display_for_all_unconstrained_members(&mut where_clause, &type_params); - where_clause -} - -fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result { - let ty = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let where_clause = generate_where_clause(&input.generics, where_clause); - - let helper = AttrsHelper::new(&input.attrs); - - let displays = data - .variants - .iter() - .map(|variant| helper.display_with_input(&input.attrs, &variant.attrs)) - .collect::>>()?; - - if data.variants.is_empty() { - Ok(quote! { - impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { - fn fmt(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - unreachable!("empty enums cannot be instantiated and thus cannot be printed") - } - } - }) - } else if displays.iter().any(Option::is_some) { - let arms = data - .variants - .iter() - .zip(displays) - .map(|(variant, display)| { - let display = - display.ok_or_else(|| Error::new_spanned(variant, "missing doc comment"))?; - let ident = &variant.ident; - Ok(match &variant.fields { - Fields::Named(fields) => { - let var = fields.named.iter().map(|field| &field.ident); - quote!(Self::#ident { #(#var),* } => { #display }) - } - Fields::Unnamed(fields) => { - let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{}", i)); - quote!(Self::#ident(#(#var),*) => { #display }) - } - Fields::Unit => quote!(Self::#ident => { #display }), - }) - }) - .collect::>>()?; - Ok(quote! { - impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { - fn fmt(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - #[allow(unused_variables)] - match self { - #(#arms,)* - } - } - } - }) - } else { - Err(Error::new_spanned(input, "Missing doc comments")) - } -} diff --git a/vendor/displaydoc/src/fmt.rs b/vendor/displaydoc/src/fmt.rs deleted file mode 100644 index 5848557e..00000000 --- a/vendor/displaydoc/src/fmt.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::attr::Display; -use proc_macro2::TokenStream; -use quote::quote_spanned; -use syn::{Ident, LitStr}; - -macro_rules! peek_next { - ($read:ident) => { - match $read.chars().next() { - Some(next) => next, - None => return, - } - }; -} - -impl Display { - // Transform `"error {var}"` to `"error {}", var`. - pub(crate) fn expand_shorthand(&mut self) { - let span = self.fmt.span(); - let fmt = self.fmt.value(); - let mut read = fmt.as_str(); - let mut out = String::new(); - let mut args = TokenStream::new(); - - while let Some(brace) = read.find('{') { - out += &read[..=brace]; - read = &read[brace + 1..]; - - // skip cases where we find a {{ - if read.starts_with('{') { - out.push('{'); - read = &read[1..]; - continue; - } - - let next = peek_next!(read); - - let var = match next { - '0'..='9' => take_int(&mut read), - 'a'..='z' | 'A'..='Z' | '_' => take_ident(&mut read), - _ => return, - }; - - let ident = Ident::new(&var, span); - - let next = peek_next!(read); - - let arg = if cfg!(feature = "std") && next == '}' { - quote_spanned!(span=> , #ident.__displaydoc_display()) - } else { - quote_spanned!(span=> , #ident) - }; - - args.extend(arg); - } - - out += read; - self.fmt = LitStr::new(&out, self.fmt.span()); - self.args = args; - } -} - -fn take_int(read: &mut &str) -> String { - let mut int = String::new(); - int.push('_'); - for (i, ch) in read.char_indices() { - match ch { - '0'..='9' => int.push(ch), - _ => { - *read = &read[i..]; - break; - } - } - } - int -} - -fn take_ident(read: &mut &str) -> String { - let mut ident = String::new(); - for (i, ch) in read.char_indices() { - match ch { - 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch), - _ => { - *read = &read[i..]; - break; - } - } - } - ident -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - use proc_macro2::Span; - - fn assert(input: &str, fmt: &str, args: &str) { - let mut display = Display { - fmt: LitStr::new(input, Span::call_site()), - args: TokenStream::new(), - }; - display.expand_shorthand(); - assert_eq!(fmt, display.fmt.value()); - assert_eq!(args, display.args.to_string()); - } - - #[test] - fn test_expand() { - assert("fn main() {{ }}", "fn main() {{ }}", ""); - } - - #[test] - #[cfg_attr(not(feature = "std"), ignore)] - fn test_std_expand() { - assert( - "{v} {v:?} {0} {0:?}", - "{} {:?} {} {:?}", - ", v . __displaydoc_display () , v , _0 . __displaydoc_display () , _0", - ); - assert("error {var}", "error {}", ", var . __displaydoc_display ()"); - - assert( - "error {var1}", - "error {}", - ", var1 . __displaydoc_display ()", - ); - - assert( - "error {var1var}", - "error {}", - ", var1var . __displaydoc_display ()", - ); - - assert( - "The path {0}", - "The path {}", - ", _0 . __displaydoc_display ()", - ); - assert("The path {0:?}", "The path {:?}", ", _0"); - } - - #[test] - #[cfg_attr(feature = "std", ignore)] - fn test_nostd_expand() { - assert( - "{v} {v:?} {0} {0:?}", - "{} {:?} {} {:?}", - ", v , v , _0 , _0", - ); - assert("error {var}", "error {}", ", var"); - - assert("The path {0}", "The path {}", ", _0"); - assert("The path {0:?}", "The path {:?}", ", _0"); - - assert("error {var1}", "error {}", ", var1"); - - assert("error {var1var}", "error {}", ", var1var"); - } -} diff --git a/vendor/displaydoc/src/lib.rs b/vendor/displaydoc/src/lib.rs deleted file mode 100644 index 8824ff05..00000000 --- a/vendor/displaydoc/src/lib.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! This library provides a convenient derive macro for the standard library's -//! [`core::fmt::Display`] trait. -//! -//! [`core::fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html -//! -//! ```toml -//! [dependencies] -//! displaydoc = "0.2" -//! ``` -//! -//! *Compiler support: requires rustc 1.56+* -//! -//!
-//! -//! ## Example -//! -//! *Demonstration alongside the [`Error`][std::error::Error] derive macro from [`thiserror`](https://docs.rs/thiserror/1.0.25/thiserror/index.html), -//! to propagate source locations from [`io::Error`][std::io::Error] with the `#[source]` attribute:* -//! ```rust -//! use std::io; -//! use displaydoc::Display; -//! use thiserror::Error; -//! -//! #[derive(Display, Error, Debug)] -//! pub enum DataStoreError { -//! /// data store disconnected -//! Disconnect(#[source] io::Error), -//! /// the data for key `{0}` is not available -//! Redaction(String), -//! /// invalid header (expected {expected:?}, found {found:?}) -//! InvalidHeader { -//! expected: String, -//! found: String, -//! }, -//! /// unknown data store error -//! Unknown, -//! } -//! -//! let error = DataStoreError::Redaction("CLASSIFIED CONTENT".to_string()); -//! assert!("the data for key `CLASSIFIED CONTENT` is not available" == &format!("{}", error)); -//! ``` -//! *Note that although [`io::Error`][std::io::Error] implements `Display`, we do not add it to the -//! generated message for `DataStoreError::Disconnect`, since it is already made available via -//! `#[source]`. See further context on avoiding duplication in error reports at the rust blog -//! [here](https://github.com/yaahc/blog.rust-lang.org/blob/master/posts/inside-rust/2021-05-15-What-the-error-handling-project-group-is-working-towards.md#duplicate-information-issue).* -//! -//!
-//! -//! ## Details -//! -//! - A `fmt::Display` impl is generated for your enum if you provide -//! a docstring comment on each variant as shown above in the example. The -//! `Display` derive macro supports a shorthand for interpolating fields from -//! the error: -//! - `/// {var}` ⟶ `write!("{}", self.var)` -//! - `/// {0}` ⟶ `write!("{}", self.0)` -//! - `/// {var:?}` ⟶ `write!("{:?}", self.var)` -//! - `/// {0:?}` ⟶ `write!("{:?}", self.0)` -//! - This also works with structs and [generic types][crate::Display#generic-type-parameters]: -//! ```rust -//! # use displaydoc::Display; -//! /// oh no, an error: {0} -//! #[derive(Display)] -//! pub struct Error(pub E); -//! -//! let error: Error<&str> = Error("muahaha i am an error"); -//! assert!("oh no, an error: muahaha i am an error" == &format!("{}", error)); -//! ``` -//! -//! - Two optional attributes can be added to your types next to the derive: -//! -//! - `#[ignore_extra_doc_attributes]` makes the macro ignore any doc -//! comment attributes (or `///` lines) after the first. Multi-line -//! comments using `///` are otherwise treated as an error, so use this -//! attribute or consider switching to block doc comments (`/** */`). -//! -//! - `#[prefix_enum_doc_attributes]` combines the doc comment message on -//! your enum itself with the messages for each variant, in the format -//! “enum: variant”. When added to an enum, the doc comment on the enum -//! becomes mandatory. When added to any other type, it has no effect. -//! -//! - In case you want to have an independent doc comment, the -//! `#[displaydoc("...")` atrribute may be used on the variant or struct to -//! override it. -//! -//!
-//! -//! ## FAQ -//! -//! 1. **Is this crate `no_std` compatible?** -//! * Yes! This crate implements the [`core::fmt::Display`] trait, not the [`std::fmt::Display`] trait, so it should work in `std` and `no_std` environments. Just add `default-features = false`. -//! -//! 2. **Does this crate work with `Path` and `PathBuf` via the `Display` trait?** -//! * Yuuup. This crate uses @dtolnay's [autoref specialization technique](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) to add a special trait for types to get the display impl. It then specializes for `Path` and `PathBuf`, and when either of these types are found, it calls `self.display()` to get a `std::path::Display<'_>` type which can be used with the `Display` format specifier! -#![doc(html_root_url = "https://docs.rs/displaydoc/0.2.3")] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![warn( - rust_2018_idioms, - unreachable_pub, - bad_style, - dead_code, - improper_ctypes, - non_shorthand_field_patterns, - no_mangle_generic_items, - overflowing_literals, - path_statements, - patterns_in_fns_without_body, - unconditional_recursion, - unused, - unused_allocation, - unused_comparisons, - unused_parens, - while_true -)] -#![allow(clippy::try_err)] - -#[allow(unused_extern_crates)] -extern crate proc_macro; - -mod attr; -mod expand; -mod fmt; - -use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; - -/// [Custom `#[derive(...)]` macro](https://doc.rust-lang.org/edition-guide/rust-2018/macros/custom-derive.html) -/// for implementing [`fmt::Display`][core::fmt::Display] via doc comment attributes. -/// -/// ### Generic Type Parameters -/// -/// Type parameters to an enum or struct using this macro should *not* need to -/// have an explicit `Display` constraint at the struct or enum definition -/// site. A `Display` implementation for the `derive`d struct or enum is -/// generated assuming each type parameter implements `Display`, but that should -/// be possible without adding the constraint to the struct definition itself: -/// ```rust -/// use displaydoc::Display; -/// -/// /// oh no, an error: {0} -/// #[derive(Display)] -/// pub struct Error(pub E); -/// -/// // No need to require `E: Display`, since `displaydoc::Display` adds that implicitly. -/// fn generate_error(e: E) -> Error { Error(e) } -/// -/// assert!("oh no, an error: muahaha" == &format!("{}", generate_error("muahaha"))); -/// ``` -/// -/// ### Using [`Debug`][core::fmt::Debug] Implementations with Type Parameters -/// However, if a type parameter must instead be constrained with the -/// [`Debug`][core::fmt::Debug] trait so that some field may be printed with -/// `{:?}`, that constraint must currently still also be specified redundantly -/// at the struct or enum definition site. If a struct or enum field is being -/// formatted with `{:?}` via [`displaydoc`][crate], and a generic type -/// parameter must implement `Debug` to do that, then that struct or enum -/// definition will need to propagate the `Debug` constraint to every type -/// parameter it's instantiated with: -/// ```rust -/// use core::fmt::Debug; -/// use displaydoc::Display; -/// -/// /// oh no, an error: {0:?} -/// #[derive(Display)] -/// pub struct Error(pub E); -/// -/// // `E: Debug` now has to propagate to callers. -/// fn generate_error(e: E) -> Error { Error(e) } -/// -/// assert!("oh no, an error: \"cool\"" == &format!("{}", generate_error("cool"))); -/// -/// // Try this with a struct that doesn't impl `Display` at all, unlike `str`. -/// #[derive(Debug)] -/// pub struct Oh; -/// assert!("oh no, an error: Oh" == &format!("{}", generate_error(Oh))); -/// ``` -#[proc_macro_derive( - Display, - attributes(ignore_extra_doc_attributes, prefix_enum_doc_attributes, displaydoc) -)] -pub fn derive_error(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - expand::derive(&input) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} -- cgit v1.2.3