Skip to main content

tablo_core/values/
scalar.rs

1//! [`FormScalar`]: a type one form key reads and writes, and the blanket impls over
2//! [`TypedValue`] and `Option`.
3
4use jiff::tz::TimeZone;
5
6use super::TypedValue;
7
8/// A type one form key reads and writes: `String`, every [`TypedValue`] type,
9/// every enum deriving [`Options`](crate::Options), and an `Option` of any of them.
10///
11/// The value the parse sees is trimmed and non-empty; an empty submission is the
12/// record-form field's **blank answer** instead, and a field with none is required.
13///
14/// A text field binds a path of any `FormScalar` type
15/// ([`Field::text`](crate::Field::text)), and the form derives read and write
16/// every field that is not `#[form(embed)]` through it.
17#[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    /// The `type` attribute of the text control that edits it.
26    const INPUT_TYPE: &'static str = "text";
27
28    /// The control's `step` attribute, when its input type takes one.
29    const STEP: Option<&'static str> = None;
30
31    /// The control's `value` for a stored value's form spelling, shown in the request's time zone
32    /// `zone`: a [`TypedValue`]'s [`input_value`](TypedValue::input_value), and the spelling
33    /// itself for any other type.
34    fn input_value(stored: &str, zone: &TimeZone) -> String {
35        let _ = zone;
36        stored.to_string()
37    }
38
39    /// The form spelling of what the control posted, read in the request's time zone `zone`: a
40    /// [`TypedValue`]'s [`form_value`](TypedValue::form_value), and the posted text itself for
41    /// any other type.
42    fn form_value(posted: &str, zone: &TimeZone) -> String {
43        let _ = zone;
44        posted.to_string()
45    }
46
47    /// Parse a trimmed, non-empty submission, or return the error message.
48    fn parse_form(value: &str) -> std::result::Result<Self, crate::Text>;
49
50    /// The form spelling of a stored value.
51    fn to_form(&self) -> String;
52
53    /// What a column cell and a group header read: the form spelling, or an
54    /// `Options` enum's label.
55    fn to_label(&self) -> crate::Text {
56        self.to_form().into()
57    }
58
59    /// The color a badge shows the value in: an `Options` enum's `#[option(color = "…")]`, and
60    /// `None` for any other type.
61    fn color(&self) -> Option<crate::Color> {
62        None
63    }
64
65    /// How `self` orders against `other` for a text field's [`min`](crate::TextField::min) and
66    /// [`max`](crate::TextField::max): a [`TypedValue`]'s [`order`](TypedValue::order), and
67    /// `None` for any other type.
68    fn order(&self, _other: &Self) -> Option<std::cmp::Ordering> {
69        None
70    }
71
72    /// The type a text field's comparison with another field orders by: `Self`, and `T` for an
73    /// `Option<T>`, so an optional end date compares with a required start date.
74    #[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/// A form scalar whose `Option` is one too: every [`TypedValue`] type, and every enum deriving
122/// [`Options`](crate::Options), which implements it.
123#[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/// Compiles only for a form scalar: the form derives call it, spanned on a
180/// field's type, so a field of another type fails there.
181#[doc(hidden)]
182pub fn assert_form_scalar<T: FormScalar>() {}