1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct FieldDefinition {
8 pub name: String,
10 pub label: String,
12 pub field_type: FieldType,
14 pub widget: WidgetType,
16 pub required: bool,
18 pub readonly: bool,
20 pub primary_key: bool,
22 pub list_display: bool,
24 pub searchable: bool,
26 pub filterable: bool,
28 pub sortable: bool,
30 pub default: Option<String>,
32 pub help_text: Option<String>,
34 pub placeholder: Option<String>,
36 pub validators: Vec<ValidatorType>,
38 pub choices: Option<Vec<Choice>>,
40 pub foreign_key: Option<ForeignKeyRef>,
42 pub max_length: Option<usize>,
44 pub min_value: Option<f64>,
46 pub max_value: Option<f64>,
48}
49
50impl FieldDefinition {
51 pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
53 let name = name.into();
54 let label = name
55 .replace('_', " ")
56 .split_whitespace()
57 .map(|w| {
58 let mut chars = w.chars();
59 match chars.next() {
60 Some(c) => c.to_uppercase().chain(chars).collect(),
61 None => String::new(),
62 }
63 })
64 .collect::<Vec<_>>()
65 .join(" ");
66
67 Self {
68 name,
69 label,
70 widget: field_type.default_widget(),
71 field_type,
72 required: false,
73 readonly: false,
74 primary_key: false,
75 list_display: true,
76 searchable: false,
77 filterable: false,
78 sortable: true,
79 default: None,
80 help_text: None,
81 placeholder: None,
82 validators: Vec::new(),
83 choices: None,
84 foreign_key: None,
85 max_length: None,
86 min_value: None,
87 max_value: None,
88 }
89 }
90
91 pub fn required(mut self) -> Self {
93 self.required = true;
94 self
95 }
96
97 pub fn readonly(mut self) -> Self {
99 self.readonly = true;
100 self
101 }
102
103 pub fn primary_key(mut self) -> Self {
105 self.primary_key = true;
106 self.readonly = true;
107 self
108 }
109
110 pub fn label(mut self, label: impl Into<String>) -> Self {
112 self.label = label.into();
113 self
114 }
115
116 pub fn widget(mut self, widget: WidgetType) -> Self {
118 self.widget = widget;
119 self
120 }
121
122 pub fn help_text(mut self, text: impl Into<String>) -> Self {
124 self.help_text = Some(text.into());
125 self
126 }
127
128 pub fn placeholder(mut self, text: impl Into<String>) -> Self {
130 self.placeholder = Some(text.into());
131 self
132 }
133
134 pub fn validator(mut self, validator: ValidatorType) -> Self {
136 self.validators.push(validator);
137 self
138 }
139
140 pub fn choices(mut self, choices: Vec<Choice>) -> Self {
142 self.choices = Some(choices);
143 self.widget = WidgetType::Select;
144 self
145 }
146
147 pub fn foreign_key(
149 mut self,
150 model: impl Into<String>,
151 display_field: impl Into<String>,
152 ) -> Self {
153 self.foreign_key = Some(ForeignKeyRef {
154 model: model.into(),
155 display_field: display_field.into(),
156 });
157 self.widget = WidgetType::ForeignKey;
158 self
159 }
160
161 pub fn searchable(mut self) -> Self {
163 self.searchable = true;
164 self
165 }
166
167 pub fn filterable(mut self) -> Self {
169 self.filterable = true;
170 self
171 }
172
173 pub fn hide_from_list(mut self) -> Self {
175 self.list_display = false;
176 self
177 }
178
179 pub fn no_sort(mut self) -> Self {
181 self.sortable = false;
182 self
183 }
184
185 pub fn max_length(mut self, len: usize) -> Self {
187 self.max_length = Some(len);
188 self
189 }
190
191 pub fn range(mut self, min: f64, max: f64) -> Self {
193 self.min_value = Some(min);
194 self.max_value = Some(max);
195 self
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
201pub enum FieldType {
202 Integer,
204 BigInteger,
206 Float,
208 Decimal,
210 String,
212 Text,
214 Boolean,
216 Date,
218 Time,
220 DateTime,
222 Uuid,
224 Json,
226 Binary,
228 Email,
230 Url,
232 IpAddress,
234 Enum,
236 ForeignKey,
238 ManyToMany,
240}
241
242impl FieldType {
243 pub fn default_widget(&self) -> WidgetType {
245 match self {
246 Self::Integer | Self::BigInteger => WidgetType::NumberInput,
247 Self::Float | Self::Decimal => WidgetType::NumberInput,
248 Self::String => WidgetType::TextInput,
249 Self::Text => WidgetType::Textarea,
250 Self::Boolean => WidgetType::Checkbox,
251 Self::Date => WidgetType::DatePicker,
252 Self::Time => WidgetType::TimePicker,
253 Self::DateTime => WidgetType::DateTimePicker,
254 Self::Uuid => WidgetType::TextInput,
255 Self::Json => WidgetType::JsonEditor,
256 Self::Binary => WidgetType::FileUpload,
257 Self::Email => WidgetType::EmailInput,
258 Self::Url => WidgetType::UrlInput,
259 Self::IpAddress => WidgetType::TextInput,
260 Self::Enum => WidgetType::Select,
261 Self::ForeignKey => WidgetType::ForeignKey,
262 Self::ManyToMany => WidgetType::MultiSelect,
263 }
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
269pub enum WidgetType {
270 TextInput,
272 PasswordInput,
274 EmailInput,
276 UrlInput,
278 NumberInput,
280 Textarea,
282 RichText,
284 Checkbox,
286 Radio,
288 Select,
290 MultiSelect,
292 DatePicker,
294 TimePicker,
296 DateTimePicker,
298 ColorPicker,
300 FileUpload,
302 ImageUpload,
304 ForeignKey,
306 JsonEditor,
308 CodeEditor,
310 Hidden,
312 ReadOnly,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct Choice {
319 pub value: String,
321 pub label: String,
323 pub disabled: bool,
325}
326
327impl Choice {
328 pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
330 Self {
331 value: value.into(),
332 label: label.into(),
333 disabled: false,
334 }
335 }
336
337 pub fn from_value(value: impl Into<String>) -> Self {
339 let value = value.into();
340 Self {
341 label: value.clone(),
342 value,
343 disabled: false,
344 }
345 }
346
347 pub fn disabled(mut self) -> Self {
349 self.disabled = true;
350 self
351 }
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct ForeignKeyRef {
357 pub model: String,
359 pub display_field: String,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize)]
365pub enum ValidatorType {
366 Required,
368 Email,
370 Url,
372 MinLength(usize),
374 MaxLength(usize),
376 MinValue(f64),
378 MaxValue(f64),
380 Pattern(String),
382 Custom(String),
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_field_definition() {
392 let field = FieldDefinition::new("user_name", FieldType::String)
393 .required()
394 .searchable()
395 .max_length(100);
396
397 assert_eq!(field.name, "user_name");
398 assert_eq!(field.label, "User Name");
399 assert!(field.required);
400 assert!(field.searchable);
401 assert_eq!(field.max_length, Some(100));
402 }
403
404 #[test]
405 fn test_choice() {
406 let choice = Choice::new("active", "Active");
407 assert_eq!(choice.value, "active");
408 assert_eq!(choice.label, "Active");
409 assert!(!choice.disabled);
410 }
411
412 #[test]
413 fn test_field_type_widget() {
414 assert_eq!(FieldType::String.default_widget(), WidgetType::TextInput);
415 assert_eq!(FieldType::Boolean.default_widget(), WidgetType::Checkbox);
416 assert_eq!(
417 FieldType::DateTime.default_widget(),
418 WidgetType::DateTimePicker
419 );
420 }
421}