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