orbit/component/props/
props.rs1use std::fmt::{self, Display};
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
14pub enum PropValidationError {
15 MissingRequired(String),
17 InvalidValue {
19 name: String,
21 reason: String,
23 },
24 TypeMismatch {
26 name: String,
28 expected: String,
30 actual: String,
32 },
33 Multiple(Vec<PropValidationError>),
35}
36
37impl Display for PropValidationError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 PropValidationError::MissingRequired(name) => {
41 write!(f, "Missing required property: {name}")
42 }
43 PropValidationError::InvalidValue { name, reason } => {
44 write!(f, "Invalid value for property {name}: {reason}")
45 }
46 PropValidationError::TypeMismatch {
47 name,
48 expected,
49 actual,
50 } => write!(
51 f,
52 "Type mismatch for property {name}: expected {expected}, got {actual}"
53 ),
54 PropValidationError::Multiple(errors) => {
55 writeln!(f, "Multiple validation errors:")?;
56 for (i, error) in errors.iter().enumerate() {
57 writeln!(f, " {}. {}", i + 1, error)?;
58 }
59 Ok(())
60 }
61 }
62 }
63}
64
65impl std::error::Error for PropValidationError {}
66
67pub trait PropValidator<P> {
69 fn validate(&self, props: &P) -> Result<(), PropValidationError>;
71}
72
73pub enum PropValue<T> {
75 Value(T),
77 Default(fn() -> T),
79 Required,
81}
82
83impl<T: Clone> PropValue<T> {
84 pub fn get(&self) -> Result<T, PropValidationError> {
86 match self {
87 PropValue::Value(val) => Ok(val.clone()),
88 PropValue::Default(default_fn) => Ok(default_fn()),
89 PropValue::Required => Err(PropValidationError::MissingRequired(
90 "Property is required".to_string(),
91 )),
92 }
93 }
94
95 pub fn new(value: T) -> Self {
97 PropValue::Value(value)
98 }
99
100 pub fn new_default(default_fn: fn() -> T) -> Self {
102 PropValue::Default(default_fn)
103 }
104
105 pub fn new_required() -> Self {
107 PropValue::Required
108 }
109
110 pub fn set(&mut self, value: T) {
112 *self = PropValue::Value(value);
113 }
114
115 pub fn is_set(&self) -> bool {
117 matches!(self, PropValue::Value(_))
118 }
119}
120
121pub struct PropsBuilder<P> {
123 props: P,
125 validator: Option<Arc<dyn PropValidator<P> + Send + Sync>>,
127}
128
129impl<P> PropsBuilder<P> {
130 pub fn new(props: P) -> Self {
132 Self {
133 props,
134 validator: None,
135 }
136 }
137
138 pub fn with_validator<V>(mut self, validator: V) -> Self
140 where
141 V: PropValidator<P> + Send + Sync + 'static,
142 {
143 self.validator = Some(Arc::new(validator));
144 self
145 }
146
147 pub fn build(self) -> Result<P, PropValidationError> {
149 if let Some(validator) = self.validator {
150 validator.validate(&self.props)?;
151 }
152 Ok(self.props)
153 }
154}
155
156pub struct CompositeValidator<P> {
158 validators: Vec<Arc<dyn PropValidator<P> + Send + Sync>>,
160}
161
162impl<P> CompositeValidator<P> {
163 pub fn new() -> Self {
165 Self {
166 validators: Vec::new(),
167 }
168 }
169
170 pub fn add<V>(&mut self, validator: V)
172 where
173 V: PropValidator<P> + Send + Sync + 'static,
174 {
175 self.validators.push(Arc::new(validator));
176 }
177}
178
179impl<P> PropValidator<P> for CompositeValidator<P> {
180 fn validate(&self, props: &P) -> Result<(), PropValidationError> {
181 let mut errors = Vec::new();
182
183 for validator in &self.validators {
184 if let Err(err) = validator.validate(props) {
185 errors.push(err);
186 }
187 }
188
189 if errors.is_empty() {
190 Ok(())
191 } else if errors.len() == 1 {
192 Err(errors.remove(0))
193 } else {
194 Err(PropValidationError::Multiple(errors))
195 }
196 }
197}
198
199impl<P> Default for CompositeValidator<P> {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205#[macro_export]
207macro_rules! define_props {
208 (
209 $(#[$struct_meta:meta])*
210 $vis:vis struct $name:ident {
211 $(
212 $(#[$field_meta:meta])*
213 $field_vis:vis $field_name:ident: $field_type:ty
214 ),*
215 $(,)?
216 }
217 ) => {
218 $(#[$struct_meta])*
219 $vis struct $name {
220 $(
221 $(#[$field_meta])*
222 $field_vis $field_name: $field_type,
223 )*
224 }
225
226 impl $name {
227 pub fn builder() -> $crate::component::props::PropsBuilder<Self> {
229 let props = Self {
230 $(
231 $field_name: Default::default(),
232 )*
233 };
234 $crate::component::props::PropsBuilder::new(props)
235 }
236
237 $(
239 #[allow(dead_code)]
240 pub fn $field_name(mut self, value: $field_type) -> Self {
241 self.$field_name = value;
242 self
243 }
244 )*
245 }
246
247 impl Default for $name {
248 fn default() -> Self {
249 Self {
250 $(
251 $field_name: Default::default(),
252 )*
253 }
254 }
255 }
256 };
257}
258
259pub use paste;
261
262#[macro_export]
264macro_rules! validate_field {
265 ($props_type:ty, $field:ident, $condition:expr, $message:expr) => {
266 struct FieldValidator<T>(std::marker::PhantomData<T>);
267
268 impl $crate::component::props::PropValidator<$props_type> for FieldValidator<$props_type> {
269 fn validate(
270 &self,
271 props: &$props_type,
272 ) -> Result<(), $crate::component::props::PropValidationError> {
273 if !$condition(&props.$field) {
274 return Err(
275 $crate::component::props::PropValidationError::InvalidValue {
276 name: stringify!($field).to_string(),
277 reason: $message.to_string(),
278 },
279 );
280 }
281 Ok(())
282 }
283 }
284
285 FieldValidator::<$props_type>(std::marker::PhantomData)
286 };
287}
288
289#[macro_export]
291macro_rules! define_props_advanced {
292 (
293 $(#[$struct_meta:meta])*
294 $vis:vis struct $name:ident {
295 $(
296 $(#[required])?
297 $(#[$field_meta:meta])*
298 $field_vis:vis $field_name:ident: $field_type:ty
299 $(= $default:expr)?
300 ),*
301 $(,)?
302 }
303 ) => {
304 define_props! {
306 $(#[$struct_meta])*
307 $vis struct $name {
308 $(
309 $(#[$field_meta])*
310 $field_vis $field_name: $field_type
311 ),*
312 }
313 }
314
315 paste::paste! {
317 pub struct [<$name Builder>] {
318 $(
319 $field_name: $crate::component::props::PropValue<$field_type>,
320 )*
321 }
322
323 impl [<$name Builder>] {
324 pub fn new() -> Self {
325 Self {
326 $(
327 $field_name: define_props_advanced!(@field_init $field_name $(#[required])? $(= $default)?),
328 )*
329 }
330 }
331
332 $(
333 pub fn $field_name(mut self, value: $field_type) -> Self {
334 self.$field_name.set(value);
335 self
336 }
337 )*
338
339 pub fn build(self) -> Result<$name, $crate::component::props::PropValidationError> {
340 let mut errors = Vec::new();
341
342 $(
343 let $field_name = match self.$field_name.get() {
344 Ok(val) => val,
345 Err(_) => {
346 if define_props_advanced!(@is_required $field_name $(#[required])?) {
348 errors.push($crate::component::props::PropValidationError::MissingRequired(
349 stringify!($field_name).to_string()
350 ));
351 }
352 Default::default()
353 }
354 };
355 )*
356
357 if errors.is_empty() {
358 Ok($name {
359 $(
360 $field_name,
361 )*
362 })
363 } else if errors.len() == 1 {
364 Err(errors.remove(0))
365 } else {
366 Err($crate::component::props::PropValidationError::Multiple(errors))
367 }
368 }
369 }
370
371 impl Default for [<$name Builder>] {
372 fn default() -> Self {
373 Self::new()
374 }
375 }
376 }
377 };
378
379 (@field_init $field_name:ident #[required] = $default:expr) => {
381 $crate::component::props::PropValue::new_required()
382 };
383 (@field_init $field_name:ident #[required]) => {
384 $crate::component::props::PropValue::new_required()
385 };
386 (@field_init $field_name:ident = $default:expr) => {
387 $crate::component::props::PropValue::new_default(|| $default)
388 };
389 (@field_init $field_name:ident) => {
390 $crate::component::props::PropValue::new_default(Default::default)
391 };
392
393 (@is_required $field_name:ident #[required]) => { true };
395 (@is_required $field_name:ident) => { false };
396}