Skip to main content

axum_admin/
field.rs

1use std::fmt;
2use std::sync::Arc;
3
4impl fmt::Debug for dyn crate::validator::Validator {
5    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6        write!(f, "Validator")
7    }
8}
9
10impl fmt::Debug for dyn crate::validator::AsyncValidator {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        write!(f, "AsyncValidator")
13    }
14}
15
16/// Escape hatch for fully custom HTML widget rendering.
17pub trait Widget: Send + Sync {
18    /// Render this widget as an HTML string for a form input.
19    fn render_input(&self, name: &str, value: Option<&str>) -> String;
20    /// Render this widget as a display value for list views.
21    fn render_display(&self, value: Option<&str>) -> String;
22}
23
24impl fmt::Debug for dyn Widget {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(f, "Widget")
27    }
28}
29
30pub enum FieldType {
31    Text,
32    TextArea,
33    Email,
34    Password,
35    Number,
36    Float,
37    Boolean,
38    Date,
39    DateTime,
40    Select(Vec<(String, String)>),
41    ForeignKey {
42        adapter: Box<dyn crate::adapter::DataAdapter>,
43        value_field: String,
44        label_field: String,
45        limit: Option<u64>,
46        order_by: Option<String>,
47    },
48    ManyToMany {
49        adapter: Box<dyn crate::adapter::ManyToManyAdapter>,
50    },
51    File {
52        storage: Arc<dyn crate::storage::FileStorage>,
53        accept: Vec<String>, // empty = any type accepted
54    },
55    Image {
56        storage: Arc<dyn crate::storage::FileStorage>,
57        // accept is always image/*, validated server-side
58    },
59    Json,
60    Custom(Box<dyn Widget>),
61}
62
63impl std::fmt::Debug for FieldType {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        match self {
66            FieldType::Text => write!(f, "Text"),
67            FieldType::TextArea => write!(f, "TextArea"),
68            FieldType::Email => write!(f, "Email"),
69            FieldType::Password => write!(f, "Password"),
70            FieldType::Number => write!(f, "Number"),
71            FieldType::Float => write!(f, "Float"),
72            FieldType::Boolean => write!(f, "Boolean"),
73            FieldType::Date => write!(f, "Date"),
74            FieldType::DateTime => write!(f, "DateTime"),
75            FieldType::Select(opts) => write!(f, "Select({} options)", opts.len()),
76            FieldType::ForeignKey { value_field, label_field, limit, order_by, .. } => {
77                write!(f, "ForeignKey {{ value_field: {value_field:?}, label_field: {label_field:?}, limit: {limit:?}, order_by: {order_by:?} }}")
78            }
79            FieldType::ManyToMany { .. } => write!(f, "ManyToMany(..)"),
80            FieldType::File { accept, .. } => write!(f, "File(accept: {:?})", accept),
81            FieldType::Image { .. } => write!(f, "Image"),
82            FieldType::Json => write!(f, "Json"),
83            FieldType::Custom(_) => write!(f, "Custom(..)"),
84        }
85    }
86}
87
88#[derive(Debug)]
89pub struct Field {
90    pub name: String,
91    pub label: String,
92    pub field_type: FieldType,
93    pub readonly: bool,
94    pub hidden: bool,
95    pub list_only: bool,
96    pub form_only: bool,
97    pub required: bool,
98    pub help_text: Option<String>,
99    pub validators: Vec<Box<dyn crate::validator::Validator>>,
100    pub async_validators: Vec<Box<dyn crate::validator::AsyncValidator>>,
101}
102
103/// Capitalise first letter of a snake_case name for use as a default label.
104pub(crate) fn default_label(name: &str) -> String {
105    let mut chars = name.replace('_', " ").chars().collect::<Vec<_>>();
106    if let Some(c) = chars.first_mut() {
107        *c = c.to_uppercase().next().unwrap_or(*c);
108    }
109    chars.into_iter().collect()
110}
111
112impl Field {
113    pub(crate) fn new(name: &str, field_type: FieldType) -> Self {
114        Self {
115            label: default_label(name),
116            name: name.to_string(),
117            field_type,
118            readonly: false,
119            hidden: false,
120            list_only: false,
121            form_only: false,
122            required: false,
123            help_text: None,
124            validators: Vec::new(),
125            async_validators: Vec::new(),
126        }
127    }
128
129    pub fn text(name: &str) -> Self { Self::new(name, FieldType::Text) }
130    pub fn textarea(name: &str) -> Self { Self::new(name, FieldType::TextArea) }
131    pub fn email(name: &str) -> Self {
132        let mut f = Self::new(name, FieldType::Email);
133        f.validators.push(Box::new(crate::validator::EmailFormat));
134        f
135    }
136    pub fn password(name: &str) -> Self { Self::new(name, FieldType::Password) }
137    pub fn number(name: &str) -> Self { Self::new(name, FieldType::Number) }
138    pub fn float(name: &str) -> Self { Self::new(name, FieldType::Float) }
139    pub fn boolean(name: &str) -> Self { Self::new(name, FieldType::Boolean) }
140    pub fn date(name: &str) -> Self { Self::new(name, FieldType::Date) }
141    pub fn datetime(name: &str) -> Self { Self::new(name, FieldType::DateTime) }
142    pub fn json(name: &str) -> Self { Self::new(name, FieldType::Json) }
143
144    pub fn select(name: &str, options: Vec<(String, String)>) -> Self {
145        Self::new(name, FieldType::Select(options))
146    }
147
148    pub fn foreign_key(
149        name: &str,
150        label: &str,
151        adapter: Box<dyn crate::adapter::DataAdapter>,
152        value_field: &str,
153        label_field: &str,
154    ) -> Self {
155        let mut f = Self::new(name, FieldType::ForeignKey {
156            adapter,
157            value_field: value_field.to_string(),
158            label_field: label_field.to_string(),
159            limit: None,
160            order_by: None,
161        });
162        f.label = label.to_string();
163        f
164    }
165
166    pub fn custom(name: &str, widget: Box<dyn Widget>) -> Self {
167        Self::new(name, FieldType::Custom(widget))
168    }
169
170    pub fn many_to_many(name: &str, adapter: Box<dyn crate::adapter::ManyToManyAdapter>) -> Self {
171        Self::new(name, FieldType::ManyToMany { adapter })
172    }
173
174    pub fn file(name: &str, storage: Arc<dyn crate::storage::FileStorage>) -> Self {
175        Self::new(name, FieldType::File { storage, accept: vec![] })
176    }
177
178    pub fn image(name: &str, storage: Arc<dyn crate::storage::FileStorage>) -> Self {
179        Self::new(name, FieldType::Image { storage })
180    }
181
182    /// Set the accepted MIME types for a `File` field. Example: `vec!["application/pdf".to_string()]`.
183    /// Has no effect on non-File fields.
184    pub fn accept(mut self, types: Vec<String>) -> Self {
185        if let FieldType::File { ref mut accept, .. } = self.field_type {
186            *accept = types;
187        }
188        self
189    }
190
191    // Modifiers — all consume self and return Self for chaining
192    pub fn label(mut self, label: &str) -> Self { self.label = label.to_string(); self }
193    pub fn readonly(mut self) -> Self { self.readonly = true; self }
194    pub fn hidden(mut self) -> Self { self.hidden = true; self }
195    pub fn list_only(mut self) -> Self { self.list_only = true; self }
196    pub fn form_only(mut self) -> Self { self.form_only = true; self }
197    pub fn required(mut self) -> Self {
198        self.required = true;
199        self.validators.push(Box::new(crate::validator::Required));
200        self
201    }
202    pub fn help_text(mut self, text: &str) -> Self { self.help_text = Some(text.to_string()); self }
203
204    /// Add a custom synchronous validator.
205    pub fn validator(mut self, v: Box<dyn crate::validator::Validator>) -> Self {
206        self.validators.push(v);
207        self
208    }
209
210    /// Add a custom asynchronous validator (e.g. uniqueness checks).
211    pub fn async_validator(mut self, v: Box<dyn crate::validator::AsyncValidator>) -> Self {
212        self.async_validators.push(v);
213        self
214    }
215
216    pub fn min_length(mut self, n: usize) -> Self {
217        self.validators.push(Box::new(crate::validator::MinLength(n)));
218        self
219    }
220
221    pub fn max_length(mut self, n: usize) -> Self {
222        self.validators.push(Box::new(crate::validator::MaxLength(n)));
223        self
224    }
225
226    pub fn min_value(mut self, n: f64) -> Self {
227        self.validators.push(Box::new(crate::validator::MinValue(n)));
228        self
229    }
230
231    pub fn max_value(mut self, n: f64) -> Self {
232        self.validators.push(Box::new(crate::validator::MaxValue(n)));
233        self
234    }
235
236    pub fn regex(mut self, pattern: &str) -> Self {
237        self.validators.push(Box::new(crate::validator::RegexValidator::new(pattern)));
238        self
239    }
240
241    pub fn unique(mut self, adapter: Box<dyn crate::adapter::DataAdapter>, col: &str) -> Self {
242        self.async_validators.push(Box::new(crate::validator::Unique::new(adapter, col)));
243        self
244    }
245
246    pub fn fk_limit(mut self, n: u64) -> Self {
247        if let FieldType::ForeignKey { ref mut limit, .. } = self.field_type {
248            *limit = Some(n);
249        }
250        self
251    }
252
253    pub fn fk_order_by(mut self, field: &str) -> Self {
254        if let FieldType::ForeignKey { ref mut order_by, .. } = self.field_type {
255            *order_by = Some(field.to_string());
256        }
257        self
258    }
259}