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(mut self, model: impl Into<String>, display_field: impl Into<String>) -> Self {
149 self.foreign_key = Some(ForeignKeyRef {
150 model: model.into(),
151 display_field: display_field.into(),
152 });
153 self.widget = WidgetType::ForeignKey;
154 self
155 }
156
157 pub fn searchable(mut self) -> Self {
159 self.searchable = true;
160 self
161 }
162
163 pub fn filterable(mut self) -> Self {
165 self.filterable = true;
166 self
167 }
168
169 pub fn hide_from_list(mut self) -> Self {
171 self.list_display = false;
172 self
173 }
174
175 pub fn no_sort(mut self) -> Self {
177 self.sortable = false;
178 self
179 }
180
181 pub fn max_length(mut self, len: usize) -> Self {
183 self.max_length = Some(len);
184 self
185 }
186
187 pub fn range(mut self, min: f64, max: f64) -> Self {
189 self.min_value = Some(min);
190 self.max_value = Some(max);
191 self
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
197pub enum FieldType {
198 Integer,
200 BigInteger,
202 Float,
204 Decimal,
206 String,
208 Text,
210 Boolean,
212 Date,
214 Time,
216 DateTime,
218 Uuid,
220 Json,
222 Binary,
224 Email,
226 Url,
228 IpAddress,
230 Enum,
232 ForeignKey,
234 ManyToMany,
236}
237
238impl FieldType {
239 pub fn default_widget(&self) -> WidgetType {
241 match self {
242 Self::Integer | Self::BigInteger => WidgetType::NumberInput,
243 Self::Float | Self::Decimal => WidgetType::NumberInput,
244 Self::String => WidgetType::TextInput,
245 Self::Text => WidgetType::Textarea,
246 Self::Boolean => WidgetType::Checkbox,
247 Self::Date => WidgetType::DatePicker,
248 Self::Time => WidgetType::TimePicker,
249 Self::DateTime => WidgetType::DateTimePicker,
250 Self::Uuid => WidgetType::TextInput,
251 Self::Json => WidgetType::JsonEditor,
252 Self::Binary => WidgetType::FileUpload,
253 Self::Email => WidgetType::EmailInput,
254 Self::Url => WidgetType::UrlInput,
255 Self::IpAddress => WidgetType::TextInput,
256 Self::Enum => WidgetType::Select,
257 Self::ForeignKey => WidgetType::ForeignKey,
258 Self::ManyToMany => WidgetType::MultiSelect,
259 }
260 }
261
262 pub fn sql_type(&self) -> &'static str {
264 match self {
265 Self::Integer => "INTEGER",
266 Self::BigInteger => "BIGINT",
267 Self::Float => "REAL",
268 Self::Decimal => "DECIMAL",
269 Self::String => "VARCHAR",
270 Self::Text => "TEXT",
271 Self::Boolean => "BOOLEAN",
272 Self::Date => "DATE",
273 Self::Time => "TIME",
274 Self::DateTime => "TIMESTAMP",
275 Self::Uuid => "UUID",
276 Self::Json => "JSON",
277 Self::Binary => "BLOB",
278 Self::Email => "VARCHAR",
279 Self::Url => "VARCHAR",
280 Self::IpAddress => "VARCHAR",
281 Self::Enum => "VARCHAR",
282 Self::ForeignKey => "INTEGER",
283 Self::ManyToMany => "INTEGER",
284 }
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
290pub enum WidgetType {
291 TextInput,
293 PasswordInput,
295 EmailInput,
297 UrlInput,
299 NumberInput,
301 Textarea,
303 RichText,
305 Checkbox,
307 Radio,
309 Select,
311 MultiSelect,
313 DatePicker,
315 TimePicker,
317 DateTimePicker,
319 ColorPicker,
321 FileUpload,
323 ImageUpload,
325 ForeignKey,
327 JsonEditor,
329 CodeEditor,
331 Hidden,
333 ReadOnly,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct Choice {
340 pub value: String,
342 pub label: String,
344 pub disabled: bool,
346}
347
348impl Choice {
349 pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
351 Self {
352 value: value.into(),
353 label: label.into(),
354 disabled: false,
355 }
356 }
357
358 pub fn from_value(value: impl Into<String>) -> Self {
360 let value = value.into();
361 Self {
362 label: value.clone(),
363 value,
364 disabled: false,
365 }
366 }
367
368 pub fn disabled(mut self) -> Self {
370 self.disabled = true;
371 self
372 }
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct ForeignKeyRef {
378 pub model: String,
380 pub display_field: String,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386pub enum ValidatorType {
387 Required,
389 Email,
391 Url,
393 MinLength(usize),
395 MaxLength(usize),
397 MinValue(f64),
399 MaxValue(f64),
401 Pattern(String),
403 Custom(String),
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn test_field_definition() {
413 let field = FieldDefinition::new("user_name", FieldType::String)
414 .required()
415 .searchable()
416 .max_length(100);
417
418 assert_eq!(field.name, "user_name");
419 assert_eq!(field.label, "User Name");
420 assert!(field.required);
421 assert!(field.searchable);
422 assert_eq!(field.max_length, Some(100));
423 }
424
425 #[test]
426 fn test_choice() {
427 let choice = Choice::new("active", "Active");
428 assert_eq!(choice.value, "active");
429 assert_eq!(choice.label, "Active");
430 assert!(!choice.disabled);
431 }
432
433 #[test]
434 fn test_field_type_widget() {
435 assert_eq!(FieldType::String.default_widget(), WidgetType::TextInput);
436 assert_eq!(FieldType::Boolean.default_widget(), WidgetType::Checkbox);
437 assert_eq!(FieldType::DateTime.default_widget(), WidgetType::DateTimePicker);
438 }
439}
440