pavexc_attr_parser/
lib.rs

1use darling::FromMeta;
2use errors::InvalidAttributeParams;
3use pavex_bp_schema::{CloningPolicy, Lifecycle, MethodGuard};
4
5pub mod atoms;
6pub mod errors;
7pub mod model;
8
9/// Parse a raw `pavex` diagnostic attribute into the specification for a Pavex component.
10///
11/// It returns `None` for:
12/// - attributes that don't belong to the `diagnostic::pavex` namespace (e.g. `#[inline]`)
13/// - attributes that don't parse successfully into `syn::Attribute`
14pub fn parse<'a, I>(attrs: I) -> Result<Option<AnnotationProperties>, errors::AttributeParserError>
15where
16    I: Iterator<Item = &'a str>,
17{
18    let mut component = None;
19    let attrs = attrs
20        .filter_map(|a| match parse_outer_attrs(a) {
21            Ok(attrs) => Some(attrs.into_iter()),
22            Err(_) => None,
23        })
24        .flatten();
25    for attr in attrs {
26        let Some(sub_path) = strip_pavex_path_prefix(attr.path()) else {
27            continue;
28        };
29        let Some(component_kind) = sub_path.get_ident() else {
30            return Err(errors::UnknownPavexAttribute::new(attr.path()).into());
31        };
32        let Ok(kind) = AnnotationKind::parse(component_kind) else {
33            return Err(errors::UnknownPavexAttribute::new(attr.path()).into());
34        };
35        let c = AnnotationProperties::from_meta(kind, &attr.meta)?;
36        if component.is_some() {
37            return Err(errors::AttributeParserError::MultiplePavexAttributes);
38        } else {
39            component = Some(c);
40        }
41    }
42    Ok(component)
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
46pub enum AnnotationProperties {
47    Constructor {
48        id: String,
49        lifecycle: Lifecycle,
50        cloning_policy: Option<CloningPolicy>,
51        allow_unused: Option<bool>,
52        allow_error_fallback: Option<bool>,
53    },
54    Prebuilt {
55        id: String,
56        allow_unused: Option<bool>,
57        cloning_policy: Option<CloningPolicy>,
58    },
59    Config {
60        id: String,
61        key: String,
62        cloning_policy: Option<CloningPolicy>,
63        default_if_missing: Option<bool>,
64        include_if_unused: Option<bool>,
65    },
66    WrappingMiddleware {
67        id: String,
68        allow_error_fallback: Option<bool>,
69    },
70    PreProcessingMiddleware {
71        id: String,
72        allow_error_fallback: Option<bool>,
73    },
74    PostProcessingMiddleware {
75        id: String,
76        allow_error_fallback: Option<bool>,
77    },
78    ErrorObserver {
79        id: String,
80    },
81    ErrorHandler {
82        id: String,
83        error_ref_input_index: usize,
84        default: Option<bool>,
85    },
86    Route {
87        id: String,
88        method: MethodGuard,
89        path: String,
90        allow_error_fallback: Option<bool>,
91    },
92    Fallback {
93        id: String,
94        allow_error_fallback: Option<bool>,
95    },
96    Methods,
97}
98
99impl AnnotationProperties {
100    fn from_meta(kind: AnnotationKind, item: &syn::Meta) -> Result<Self, InvalidAttributeParams> {
101        use AnnotationKind::*;
102        use model::*;
103
104        match kind {
105            Constructor => ConstructorProperties::from_meta(item).map(Into::into),
106            Config => ConfigProperties::from_meta(item).map(Into::into),
107            WrappingMiddleware => WrappingMiddlewareProperties::from_meta(item).map(Into::into),
108            PreProcessingMiddleware => {
109                PreProcessingMiddlewareProperties::from_meta(item).map(Into::into)
110            }
111            PostProcessingMiddleware => {
112                PostProcessingMiddlewareProperties::from_meta(item).map(Into::into)
113            }
114            ErrorObserver => ErrorObserverProperties::from_meta(item).map(Into::into),
115            ErrorHandler => ErrorHandlerProperties::from_meta(item).map(Into::into),
116            Prebuilt => PrebuiltProperties::from_meta(item).map(Into::into),
117            Route => RouteProperties::from_meta(item).map(Into::into),
118            Fallback => FallbackProperties::from_meta(item).map(Into::into),
119            Methods => Ok(AnnotationProperties::Methods),
120        }
121        .map_err(|e| InvalidAttributeParams::new(e, kind))
122    }
123
124    /// Return the id of this component, if one was set.
125    pub fn id(&self) -> Option<&str> {
126        use AnnotationProperties::*;
127
128        match self {
129            WrappingMiddleware { id, .. }
130            | PreProcessingMiddleware { id, .. }
131            | PostProcessingMiddleware { id, .. }
132            | ErrorObserver { id }
133            | ErrorHandler { id, .. }
134            | Route { id, .. }
135            | Config { id, .. }
136            | Prebuilt { id, .. }
137            | Constructor { id, .. }
138            | Fallback { id, .. } => Some(id.as_str()),
139            Methods => None,
140        }
141    }
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub enum AnnotationKind {
146    Constructor,
147    Config,
148    WrappingMiddleware,
149    PreProcessingMiddleware,
150    PostProcessingMiddleware,
151    ErrorObserver,
152    ErrorHandler,
153    Prebuilt,
154    Route,
155    Fallback,
156    Methods,
157}
158
159impl std::fmt::Display for AnnotationKind {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        match self {
162            AnnotationKind::Constructor => write!(f, "constructor"),
163            AnnotationKind::Config => write!(f, "config"),
164            AnnotationKind::WrappingMiddleware => write!(f, "wrap"),
165            AnnotationKind::PreProcessingMiddleware => write!(f, "pre_process"),
166            AnnotationKind::PostProcessingMiddleware => write!(f, "post_process"),
167            AnnotationKind::ErrorObserver => write!(f, "error_observer"),
168            AnnotationKind::ErrorHandler => write!(f, "error_handler"),
169            AnnotationKind::Prebuilt => write!(f, "prebuilt"),
170            AnnotationKind::Route => write!(f, "route"),
171            AnnotationKind::Fallback => write!(f, "fallback"),
172            AnnotationKind::Methods => write!(f, "methods"),
173        }
174    }
175}
176
177impl AnnotationKind {
178    fn parse(ident: &syn::Ident) -> Result<AnnotationKind, ()> {
179        match ident.to_string().as_str() {
180            "constructor" => Ok(AnnotationKind::Constructor),
181            "config" => Ok(AnnotationKind::Config),
182            "wrap" => Ok(AnnotationKind::WrappingMiddleware),
183            "post_process" => Ok(AnnotationKind::PostProcessingMiddleware),
184            "pre_process" => Ok(AnnotationKind::PreProcessingMiddleware),
185            "error_observer" => Ok(AnnotationKind::ErrorObserver),
186            "prebuilt" => Ok(AnnotationKind::Prebuilt),
187            "route" => Ok(AnnotationKind::Route),
188            "fallback" => Ok(AnnotationKind::Fallback),
189            "error_handler" => Ok(AnnotationKind::ErrorHandler),
190            "methods" => Ok(AnnotationKind::Methods),
191            _ => Err(()),
192        }
193    }
194
195    pub fn diagnostic_attribute(&self) -> &'static str {
196        use AnnotationKind::*;
197
198        match self {
199            Constructor => "pavex::diagnostic::constructor",
200            Config => "pavex::diagnostic::config",
201            WrappingMiddleware => "pavex::diagnostic::wrap",
202            PreProcessingMiddleware => "pavex::diagnostic::pre_process",
203            PostProcessingMiddleware => "pavex::diagnostic::post_process",
204            ErrorObserver => "pavex::diagnostic::error_observer",
205            ErrorHandler => "pavex::diagnostic::error_handler",
206            Prebuilt => "pavex::diagnostic::prebuilt",
207            Route => "pavex::diagnostic::route",
208            Fallback => "pavex::diagnostic::fallback",
209            Methods => "pavex::diagnostic::methods",
210        }
211    }
212}
213
214impl AnnotationProperties {
215    pub fn attribute(&self) -> &'static str {
216        self.kind().diagnostic_attribute()
217    }
218
219    pub fn kind(&self) -> AnnotationKind {
220        match self {
221            AnnotationProperties::Constructor { .. } => AnnotationKind::Constructor,
222            AnnotationProperties::Config { .. } => AnnotationKind::Config,
223            AnnotationProperties::WrappingMiddleware { .. } => AnnotationKind::WrappingMiddleware,
224            AnnotationProperties::PreProcessingMiddleware { .. } => {
225                AnnotationKind::PreProcessingMiddleware
226            }
227            AnnotationProperties::PostProcessingMiddleware { .. } => {
228                AnnotationKind::PostProcessingMiddleware
229            }
230            AnnotationProperties::ErrorObserver { .. } => AnnotationKind::ErrorObserver,
231            AnnotationProperties::Prebuilt { .. } => AnnotationKind::Prebuilt,
232            AnnotationProperties::Route { .. } => AnnotationKind::Route,
233            AnnotationProperties::Fallback { .. } => AnnotationKind::Fallback,
234            AnnotationProperties::ErrorHandler { .. } => AnnotationKind::ErrorHandler,
235            AnnotationProperties::Methods => AnnotationKind::Methods,
236        }
237    }
238}
239
240/// Strip the `diagnostic::pavex` prefix from a path.
241///
242/// It returns `None` if the path doesn't start with `diagnostic::pavex`.
243/// It returns `Some` otherwise, yielding the remaining path segments.
244fn strip_pavex_path_prefix(path: &syn::Path) -> Option<syn::Path> {
245    if path.segments.len() < 2 {
246        return None;
247    }
248
249    let prefix = &path.segments[0];
250    let pavex = &path.segments[1];
251
252    if prefix.ident == "diagnostic"
253        && prefix.arguments.is_empty()
254        && pavex.ident == "pavex"
255        && pavex.arguments.is_empty()
256    {
257        let remaining_segments = path.segments.iter().skip(2).cloned().collect();
258        let remaining_path = syn::Path {
259            leading_colon: path.leading_colon,
260            segments: remaining_segments,
261        };
262        Some(remaining_path)
263    } else {
264        None
265    }
266}
267
268/// Extract outer attributes from a string of attributes.
269/// I.e. attributes that start with `#[` rather than `#![`.
270fn parse_outer_attrs(attrs: &str) -> syn::Result<Vec<syn::Attribute>> {
271    /// `syn` doesn't let you parse outer attributes directly since `syn::Attribute` doesn't
272    /// implement `syn::parse::Parse`.
273    /// We use this thin wrapper to be able to invoke `syn::Attribute::parse_outer` on it.
274    struct OuterAttributes(Vec<syn::Attribute>);
275
276    impl syn::parse::Parse for OuterAttributes {
277        fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
278            input.call(syn::Attribute::parse_outer).map(OuterAttributes)
279        }
280    }
281    syn::parse_str::<OuterAttributes>(attrs).map(|a| a.0)
282}