tablo_core/values/
scalar.rs1use jiff::tz::TimeZone;
5
6use super::TypedValue;
7
8#[diagnostic::on_unimplemented(
18 message = "`{Self}` is not a form scalar",
19 label = "a form field of this type has no text spelling",
20 note = "a form scalar is `String`, a `TypedValue` type, an `Options` enum, or an `Option` of \
21 one; implement `TypedValue` for an app type, derive `Options` for a unit enum, or \
22 mark an `EmbeddedForm` value `#[form(embed)]`"
23)]
24pub trait FormScalar: Sized {
25 const INPUT_TYPE: &'static str = "text";
27
28 const STEP: Option<&'static str> = None;
30
31 const SORTABLE: bool = true;
34
35 fn input_value(stored: &str, zone: &TimeZone) -> String {
39 let _ = zone;
40 stored.to_string()
41 }
42
43 fn form_value(posted: &str, zone: &TimeZone) -> String {
47 let _ = zone;
48 posted.to_string()
49 }
50
51 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text>;
53
54 fn to_form(&self) -> String;
57
58 fn to_label(&self) -> crate::Text {
61 self.to_form().into()
62 }
63
64 fn color(&self) -> Option<crate::Color> {
67 None
68 }
69
70 fn icon(&self) -> Option<topcoat::icon::IconData> {
73 None
74 }
75
76 fn order(&self, _other: &Self) -> Option<std::cmp::Ordering> {
80 None
81 }
82
83 #[doc(hidden)]
86 fn compared_as() -> &'static str {
87 std::any::type_name::<Self>()
88 }
89}
90
91impl FormScalar for String {
92 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
93 Ok(value.to_string())
94 }
95
96 fn to_form(&self) -> String {
97 self.clone()
98 }
99}
100
101impl<T: TypedValue> FormScalar for T {
102 const INPUT_TYPE: &'static str = T::INPUT_TYPE;
103 const STEP: Option<&'static str> = T::STEP;
104 const SORTABLE: bool = T::SORTABLE;
105
106 fn input_value(stored: &str, zone: &TimeZone) -> String {
107 T::input_value(stored, zone)
108 }
109
110 fn form_value(posted: &str, zone: &TimeZone) -> String {
111 T::form_value(posted, zone)
112 }
113
114 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
115 T::parse_input(value).ok_or_else(|| {
116 crate::text!(
117 "tablo-field-not-a",
118 value = value,
119 kind = crate::Text::message(T::NOUN)
120 )
121 })
122 }
123
124 fn to_form(&self) -> String {
125 self.form_spelling()
126 }
127
128 fn order(&self, other: &Self) -> Option<std::cmp::Ordering> {
129 TypedValue::order(self, other)
130 }
131}
132
133#[doc(hidden)]
136pub trait NullableScalar: FormScalar {}
137
138impl<T: TypedValue> NullableScalar for T {}
139
140impl<T: NullableScalar> FormScalar for Option<T> {
141 const INPUT_TYPE: &'static str = T::INPUT_TYPE;
142 const STEP: Option<&'static str> = T::STEP;
143 const SORTABLE: bool = T::SORTABLE;
144
145 fn input_value(stored: &str, zone: &TimeZone) -> String {
146 T::input_value(stored, zone)
147 }
148
149 fn form_value(posted: &str, zone: &TimeZone) -> String {
150 T::form_value(posted, zone)
151 }
152
153 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
154 T::parse_form(value).map(Some)
155 }
156
157 fn to_form(&self) -> String {
158 self.as_ref().map(T::to_form).unwrap_or_default()
159 }
160
161 fn to_label(&self) -> crate::Text {
162 self.as_ref().map(T::to_label).unwrap_or_default()
163 }
164
165 fn color(&self) -> Option<crate::Color> {
166 self.as_ref().and_then(T::color)
167 }
168
169 fn icon(&self) -> Option<topcoat::icon::IconData> {
170 self.as_ref().and_then(T::icon)
171 }
172
173 fn order(&self, other: &Self) -> Option<std::cmp::Ordering> {
174 T::order(self.as_ref()?, other.as_ref()?)
175 }
176
177 fn compared_as() -> &'static str {
178 T::compared_as()
179 }
180}
181
182impl FormScalar for Option<String> {
183 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
184 Ok(Some(value.to_string()))
185 }
186
187 fn compared_as() -> &'static str {
188 String::compared_as()
189 }
190
191 fn to_form(&self) -> String {
192 self.clone().unwrap_or_default()
193 }
194}
195
196#[doc(hidden)]
199pub fn assert_form_scalar<T: FormScalar>() {}