1use darling::FromMeta;
2use errors::InvalidAttributeParams;
3use pavex_bp_schema::{CloningPolicy, Lifecycle, MethodGuard};
4
5pub mod atoms;
6pub mod errors;
7pub mod model;
8
9pub 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 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
240fn 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
268fn parse_outer_attrs(attrs: &str) -> syn::Result<Vec<syn::Attribute>> {
271 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}