Skip to main content

protify/
decl_macros.rs

1// For docs
2#[allow(unused)]
3use crate::*;
4
5macro_rules! pluralize {
6	($count:expr) => {
7		if $count != 1 { "s" } else { "" }
8	};
9}
10
11macro_rules! custom_error_messages_method {
12	($kind:ident) => {
13		paste! {
14		  /// Adds a map with custom error messages to the underlying validator.
15		  ///
16		  /// If a violation has no custom error message attached to it, it uses the default error message.
17			#[inline]
18		  pub fn with_error_messages(
19			mut self,
20			error_messages: impl IntoIterator<Item = ([< $kind Violation >], impl Into<FixedStr>)>,
21		  ) -> [< $kind ValidatorBuilder >]<SetErrorMessages<S>>
22		where
23		  S::ErrorMessages: IsUnset,
24		  {
25				self.data.error_messages = Some(collect_error_messages(error_messages));
26
27				[< $kind ValidatorBuilder >] {
28					_state: PhantomData,
29					data: self.data,
30				}
31		  }
32		}
33	};
34}
35
36#[doc(hidden)]
37#[cfg(feature = "inventory")]
38#[macro_export]
39macro_rules! register_proto_data {
40  ($($tokens:tt)*) => {
41    $crate::inventory::submit! { $($tokens)* }
42  };
43}
44
45#[doc(hidden)]
46#[cfg(not(feature = "inventory"))]
47#[macro_export]
48macro_rules! register_proto_data {
49	($($tokens:tt)*) => {};
50}
51
52/// This macro can be used to generate a [`ProtoOption`] with a concise syntax.
53///
54/// The input can be a single `key => value`, where the key should support [`Into`] [`FixedStr`]  and the value should support [`Into`] [`OptionValue`], or a bracketed series or key-value pairs to generate an [`OptionValue::Message`] for the value.
55///
56/// # Examples
57///
58/// ```
59/// use protify::*;
60///
61/// let option = proto_option!("is_cool" => true);
62/// assert_eq!(option, ProtoOption { name: "is_cool".into(), value: true.into() });
63///
64/// let object_like_option = proto_option!("is_cool" => { "answer" => true });
65/// assert_eq!(object_like_option.name, "is_cool");
66/// assert_eq!(object_like_option.value, OptionValue::Message(option_message!("answer" => true)));
67/// ```
68#[macro_export]
69macro_rules! proto_option {
70  ( $name:expr => { $($key:expr => $val:expr),* $(,)? } ) => {
71    $crate::ProtoOption {
72      name: $name.into(),
73      value: $crate::OptionValue::Message($crate::option_message!($($key => $val),*)),
74    }
75  };
76
77  ($name:expr => $val:expr) => {
78    $crate::ProtoOption {
79      name: $name.into(),
80      value: $val.into(),
81    }
82  };
83}
84
85/// This macro can be used to create an [`OptionValue::List`] from an iterator of items that implement [`Into`] [`OptionValue`].
86///
87/// # Examples
88///
89/// ```
90/// use protify::*;
91///
92/// let list = option_list!([ 1, 2 ]);
93/// assert!(matches!(list, OptionValue::List(_)));
94/// ```
95#[macro_export]
96macro_rules! option_list {
97	($list:expr) => {
98		$crate::OptionValue::new_list($list)
99	};
100}
101
102/// This macro can be used to create an object-like protobuf option. It follows the syntax of crates like maplit, for creating key-value pairs.
103///
104/// # Examples
105///
106/// ```
107/// use protify::*;
108///
109/// let option = option_message!("is_cool" => true);
110/// let value = option.get("is_cool").unwrap();
111///
112/// assert_eq!(value, &OptionValue::Bool(true));
113/// ```
114#[macro_export]
115macro_rules! option_message {
116  ($($key:expr => $val:expr),* $(,)?) => {
117    {
118      let mut builder = $crate::OptionMessageBuilder::new();
119      $(
120        builder.set($key, $val);
121      )*
122      builder.build()
123    }
124  };
125
126  ($msg:expr) => {
127    $crate::OptionValue::new_message($msg)
128  }
129}
130
131macro_rules! length_rule_value {
132	($name:literal, $value:expr) => {
133		&LengthRuleValue {
134			name: $name,
135			value: $value,
136		}
137	};
138}
139
140/// Brings a pre-defined proto file handle in scope so that it can be picked up by the proto items defined in the module where it's called.
141///
142/// It **retains** the `extern_path` of the original file, so it should be used only if the items are meant to be re-exported by the module where the original file is defined.
143///
144/// For more information about how to manage proto files, visit the [dedicated section](crate::guide::managing_files).
145///
146/// # Examples
147///
148/// ```rust
149/// use protify::*;
150/// mod example {
151///   use super::*;
152///
153///   proto_package!(MY_PKG, name = "my_pkg");
154///   define_proto_file!(MY_FILE, name = "my_file.proto", package = MY_PKG);
155///
156///   pub use re_exported::Msg;
157///
158///   pub fn mod_path() -> &'static str {
159///     module_path!()
160///   }
161///
162///   mod re_exported {
163///     use super::MY_FILE;
164///     use protify::*;
165///
166///     // The file is now in scope, and will be picked up automatically by all items defined in this module
167///     // The items will have the extern path of the parent, so `::cratename::example`
168///     inherit_proto_file!(MY_FILE);
169///
170///     // This will have the extern path `::cratename::example::Msg`
171///     #[proto_message]
172///     pub struct Msg {
173///       pub id: i32
174///     }
175///   }
176/// }
177///
178/// fn main() {
179///   assert_eq!(example::Msg::proto_schema().rust_path, &format!("::{}::Msg", example::mod_path()));
180/// }
181/// ```
182#[macro_export]
183macro_rules! inherit_proto_file {
184	($file:path) => {
185		#[doc(hidden)]
186		#[allow(unused)]
187		const __PROTO_FILE: $crate::FileReference = ::protify::FileReference {
188			name: <$file as ::protify::FileSchema>::NAME,
189			package: <$file as ::protify::FileSchema>::PACKAGE,
190			extern_path: <$file as ::protify::FileSchema>::EXTERN_PATH,
191		};
192	};
193}
194
195/// Brings a pre-defined proto file handle in scope so that it can be picked up by the proto items defined in the module where it's called.
196///
197/// The items defined in the module where this is called will have the `extern_path` set to the output of `module_path!()`. For re-exported items that are meant to inherit the same path as the parent module, use the [`inherit_proto_file`] macro instead.
198///
199/// For more information about how to manage proto files, visit the [dedicated section](crate::guide::managing_files).
200///
201/// # Examples
202///
203/// ```rust
204/// use protify::*;
205///
206/// mod example {
207///   use super::*;
208///   proto_package!(MY_PKG, name = "my_pkg");
209///   define_proto_file!(MY_FILE, name = "my_file.proto", package = MY_PKG);
210///
211///   pub mod submod {
212///     use super::MY_FILE;
213///     use protify::*;
214///
215///     pub fn mod_path() -> &'static str {
216///       module_path!()
217///     }
218///
219///     // The file is now in scope, and will be picked up automatically by all items defined in this module
220///     // The items will have the extern path of the `module_path!()` output in here, so `::cratename::example::submod`
221///     use_proto_file!(MY_FILE);
222///
223///     // This will have the extern path `::cratename::example::submod::Msg`
224///     #[proto_message]
225///     pub struct Msg {
226///       pub id: i32
227///     }
228///   }
229/// }
230///
231/// fn main() {
232///   assert_eq!(example::submod::Msg::proto_schema().rust_path, &format!("::{}::Msg", example::submod::mod_path()));
233/// }
234/// ```
235#[macro_export]
236macro_rules! use_proto_file {
237	($file:path) => {
238		#[doc(hidden)]
239		#[allow(unused)]
240		const __PROTO_FILE: $crate::FileReference = ::protify::FileReference {
241			name: <$file as ::protify::FileSchema>::NAME,
242			package: <$file as ::protify::FileSchema>::PACKAGE,
243			extern_path: ::core::module_path!(),
244		};
245	};
246}
247
248macro_rules! handle_ignore_always {
249	($ignore:expr) => {
250		if matches!($ignore, Ignore::Always) {
251			return Ok(IsValid::Yes);
252		}
253	};
254}
255
256macro_rules! handle_ignore_if_zero_value {
257	($ignore:expr, $condition:expr) => {
258		if matches!($ignore, Ignore::IfZeroValue) && $condition {
259			return Ok(IsValid::Yes);
260		}
261	};
262}
263
264macro_rules! impl_testing_methods {
265	() => {
266		#[cfg(feature = "cel")]
267		#[inline(never)]
268		#[cold]
269		fn check_cel_programs_with(&self, val: Self::Target) -> Result<(), Vec<CelError>> {
270			if !self.cel.is_empty() {
271				test_programs(&self.cel, val)
272			} else {
273				Ok(())
274			}
275		}
276
277		#[cfg(feature = "cel")]
278		#[inline(never)]
279		#[cold]
280		#[doc(hidden)]
281		fn __check_cel_programs(&self) -> Result<(), Vec<CelError>> {
282			self.check_cel_programs_with(Self::Target::default())
283		}
284
285		#[doc(hidden)]
286		#[inline(never)]
287		#[cold]
288		fn __cel_rules(&self) -> Vec<CelRule> {
289			self.cel
290				.iter()
291				.map(|p| p.rule().clone())
292				.collect()
293		}
294	};
295}
296
297/// Defines a new [`CelProgram`].
298///
299/// The inputs, in positional order, are:
300/// - id (expr, Into<[`FixedStr`]>): The id of the specific CEL rule. It should be unique within the same message scope.
301/// - msg (expr, Into<[`FixedStr`]>): The error message associated with the given rule.
302/// - expr (expr, Into<[`FixedStr`]>): The actual CEL expression to use when validating the target.
303#[macro_export]
304macro_rules! cel_program {
305	(id = $id:expr, msg = $msg:expr, expr = $expr:expr) => {
306		$crate::CelRule {
307			id: $id.into(),
308			message: $msg.into(),
309			expression: $expr.into(),
310		}
311		.into()
312	};
313}
314
315macro_rules! impl_proto_type {
316	($rust_type:ty, $proto_type:ident) => {
317		impl AsProtoType for $rust_type {
318			#[inline]
319			fn proto_type() -> ProtoType {
320				ProtoType::Scalar(ProtoScalar::$proto_type)
321			}
322		}
323	};
324}
325
326macro_rules! impl_proto_map_key {
327	($rust_type:ty, $enum_ident:ident) => {
328		impl AsProtoMapKey for $rust_type {
329			#[doc(hidden)]
330			#[allow(private_interfaces)]
331			const SEALED: crate::Sealed = crate::Sealed;
332
333			#[doc(hidden)]
334			#[cold]
335			#[inline]
336			fn as_proto_map_key() -> ProtoMapKey {
337				ProtoMapKey::$enum_ident
338			}
339		}
340	};
341}