tablo_core/schema/fields/
builders.rs1use super::{
5 super::{IntoSchema, OptionSource, Schema},
6 ChoiceControl, ControlKind, Field, TextControl, choice,
7};
8
9macro_rules! common_modifiers {
11 ($builder:ident) => {
12 impl $builder {
13 pub fn label(mut self, label: impl Into<String>) -> Self {
14 self.0.label = label.into();
15 self
16 }
17
18 pub fn required(mut self) -> Self {
19 self.0.required = true;
20 self
21 }
22
23 pub fn optional(mut self) -> Self {
24 self.0.required = false;
25 self
26 }
27
28 pub fn name(&self) -> &str {
29 &self.0.name
30 }
31 }
32
33 impl From<$builder> for Field {
34 fn from(builder: $builder) -> Field {
35 builder.0
36 }
37 }
38
39 impl IntoSchema for $builder {
40 fn into_schema(self) -> Schema {
41 self.0.into_schema()
42 }
43 }
44 };
45}
46
47pub struct TextField(pub(super) Field);
49
50pub struct ChoiceField(pub(super) Field);
52
53pub struct FileField(pub(super) Field);
55
56pub struct CustomField(pub(super) Field);
59
60common_modifiers!(TextField);
61common_modifiers!(ChoiceField);
62common_modifiers!(FileField);
63common_modifiers!(CustomField);
64
65impl TextField {
66 fn text(&mut self) -> &mut TextControl {
67 match &mut self.0.control {
68 ControlKind::Text(text) => text,
69 _ => unreachable!("a TextField holds a text control"),
70 }
71 }
72
73 pub fn email(mut self) -> Self {
75 self.0.rules.set_email();
76 self
77 }
78
79 pub fn unique(mut self) -> Self {
81 self.text().unique = true;
82 self
83 }
84
85 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
87 self.text().placeholder = Some(placeholder.into());
88 self
89 }
90
91 pub fn multiline(mut self, rows: u32) -> Self {
94 self.text().rows = Some(rows);
95 self
96 }
97}
98
99impl ChoiceField {
100 fn choice(&mut self) -> &mut ChoiceControl {
101 match &mut self.0.control {
102 ControlKind::Choice(choice) => choice,
103 _ => unreachable!("a ChoiceField holds a choice control"),
104 }
105 }
106
107 pub fn options(mut self, options: impl IntoOptions) -> Self {
112 self.choice().options = options.into_options();
113 self
114 }
115
116 pub fn searchable(mut self) -> Self {
118 self.choice().searchable = true;
119 self
120 }
121
122 pub fn relationship<R>(
125 mut self,
126 label: impl Fn(&R::Model) -> String + Send + Sync + 'static,
127 ) -> Self
128 where
129 R: OptionSource + 'static,
130 {
131 self.choice().relationship = Some(choice::Relationship::new::<R>(label));
132 self
133 }
134}
135
136pub trait IntoOptions {
138 fn into_options(self) -> Vec<(String, String)>;
139}
140
141impl IntoOptions for Vec<String> {
142 fn into_options(self) -> Vec<(String, String)> {
143 self.into_iter()
144 .map(|value| (value.clone(), value))
145 .collect()
146 }
147}
148
149impl IntoOptions for Vec<(String, String)> {
150 fn into_options(self) -> Vec<(String, String)> {
151 self
152 }
153}
154
155impl<const N: usize> IntoOptions for [&str; N] {
156 fn into_options(self) -> Vec<(String, String)> {
157 self.into_iter()
158 .map(|value| (value.to_string(), value.to_string()))
159 .collect()
160 }
161}
162
163#[cfg(test)]
165mod test_deref {
166 use super::{ChoiceField, CustomField, Field, FileField, TextField};
167
168 macro_rules! deref_field {
169 ($($builder:ident),*) => {$(
170 impl std::ops::Deref for $builder {
171 type Target = Field;
172 fn deref(&self) -> &Field {
173 &self.0
174 }
175 }
176 )*};
177 }
178
179 deref_field!(TextField, ChoiceField, FileField, CustomField);
180}