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 fn input_value(stored: &str, zone: &TimeZone) -> String {
35 let _ = zone;
36 stored.to_string()
37 }
38
39 fn form_value(posted: &str, zone: &TimeZone) -> String {
43 let _ = zone;
44 posted.to_string()
45 }
46
47 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text>;
49
50 fn to_form(&self) -> String;
52
53 fn to_label(&self) -> crate::Text {
56 self.to_form().into()
57 }
58
59 fn color(&self) -> Option<crate::Color> {
62 None
63 }
64
65 fn order(&self, _other: &Self) -> Option<std::cmp::Ordering> {
69 None
70 }
71
72 #[doc(hidden)]
75 fn compared_as() -> &'static str {
76 std::any::type_name::<Self>()
77 }
78}
79
80impl FormScalar for String {
81 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
82 Ok(value.to_string())
83 }
84
85 fn to_form(&self) -> String {
86 self.clone()
87 }
88}
89
90impl<T: TypedValue> FormScalar for T {
91 const INPUT_TYPE: &'static str = T::INPUT_TYPE;
92 const STEP: Option<&'static str> = T::STEP;
93
94 fn input_value(stored: &str, zone: &TimeZone) -> String {
95 T::input_value(stored, zone)
96 }
97
98 fn form_value(posted: &str, zone: &TimeZone) -> String {
99 T::form_value(posted, zone)
100 }
101
102 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
103 T::parse_input(value).ok_or_else(|| {
104 crate::text!(
105 "tablo-field-not-a",
106 value = value,
107 kind = crate::Text::message(T::NOUN)
108 )
109 })
110 }
111
112 fn to_form(&self) -> String {
113 self.to_string()
114 }
115
116 fn order(&self, other: &Self) -> Option<std::cmp::Ordering> {
117 TypedValue::order(self, other)
118 }
119}
120
121#[doc(hidden)]
124pub trait NullableScalar: FormScalar {}
125
126impl<T: TypedValue> NullableScalar for T {}
127
128impl<T: NullableScalar> FormScalar for Option<T> {
129 const INPUT_TYPE: &'static str = T::INPUT_TYPE;
130 const STEP: Option<&'static str> = T::STEP;
131
132 fn input_value(stored: &str, zone: &TimeZone) -> String {
133 T::input_value(stored, zone)
134 }
135
136 fn form_value(posted: &str, zone: &TimeZone) -> String {
137 T::form_value(posted, zone)
138 }
139
140 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
141 T::parse_form(value).map(Some)
142 }
143
144 fn to_form(&self) -> String {
145 self.as_ref().map(T::to_form).unwrap_or_default()
146 }
147
148 fn to_label(&self) -> crate::Text {
149 self.as_ref().map(T::to_label).unwrap_or_default()
150 }
151
152 fn color(&self) -> Option<crate::Color> {
153 self.as_ref().and_then(T::color)
154 }
155
156 fn order(&self, other: &Self) -> Option<std::cmp::Ordering> {
157 T::order(self.as_ref()?, other.as_ref()?)
158 }
159
160 fn compared_as() -> &'static str {
161 T::compared_as()
162 }
163}
164
165impl FormScalar for Option<String> {
166 fn parse_form(value: &str) -> std::result::Result<Self, crate::Text> {
167 Ok(Some(value.to_string()))
168 }
169
170 fn compared_as() -> &'static str {
171 String::compared_as()
172 }
173
174 fn to_form(&self) -> String {
175 self.clone().unwrap_or_default()
176 }
177}
178
179#[doc(hidden)]
182pub fn assert_form_scalar<T: FormScalar>() {}