use crate::schema::{DynSchema, Schema};
use crate::value::Value;
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a formars `FormSchema`",
label = "nested form fields require `#[derive(FormSchema)]` (crate `formars-derive`) or a manual `FormSchema` impl",
note = "add `#[derive(FormSchema)]` on `{Self}`, or use `#[form(schema = <expr>)]` on the field"
)]
pub trait FormSchema {
type Schema: Schema<Input = Self, Output = Self> + DynSchema;
#[must_use]
fn form_schema() -> Self::Schema;
}
pub trait FormBridge: Sized {
#[must_use]
fn to_form_value(&self) -> Value;
#[must_use]
fn from_validated(v: &Value) -> Option<Self>;
}
macro_rules! bridge_int {
($($t:ty),* $(,)?) => {$(
impl FormBridge for $t {
fn to_form_value(&self) -> Value {
crate::value::ToValue::to_value(*self)
}
fn from_validated(v: &Value) -> Option<Self> {
<$t>::try_from(v.as_i64()?).ok()
}
}
)*};
}
bridge_int!(i8, i16, i32, i64, u8, u16, u32);
impl FormBridge for f32 {
fn to_form_value(&self) -> Value {
Value::F64(f64::from(*self))
}
fn from_validated(v: &Value) -> Option<Self> {
#[allow( // exact inverse of a lossless widening
clippy::cast_possible_truncation,
reason = "to_form_value widened the f32 losslessly into F64; narrowing recovers the original bits"
)]
Some(v.as_f64()? as Self)
}
}
impl FormBridge for f64 {
fn to_form_value(&self) -> Value {
Value::F64(*self)
}
fn from_validated(v: &Value) -> Option<Self> {
v.as_f64()
}
}
impl FormBridge for String {
fn to_form_value(&self) -> Value {
Value::from(self.as_str())
}
fn from_validated(v: &Value) -> Option<Self> {
v.as_str().map(str::to_owned)
}
}
impl FormBridge for bool {
fn to_form_value(&self) -> Value {
Value::Bool(*self)
}
fn from_validated(v: &Value) -> Option<Self> {
v.as_bool()
}
}
#[cfg(test)]
mod tests {
use super::FormBridge;
use crate::value::Value;
macro_rules! rt {
($t:ty, $v:expr) => {{
let original = $v;
let wire = <$t as FormBridge>::to_form_value(&original);
assert_eq!(
<$t as FormBridge>::from_validated(&wire),
Some(original),
"{} round-trip failed",
stringify!($t)
);
}};
}
#[test]
fn a3_round_trips_full_primitive_matrix() {
rt!(String, String::from("ada"));
rt!(bool, true);
rt!(bool, false);
rt!(i8, -1i8);
rt!(i16, -2i16);
rt!(i32, -3i32);
rt!(i64, -4i64);
rt!(u8, 1u8);
rt!(u16, 2u16);
rt!(u32, 3u32);
rt!(f32, 0.5f32);
rt!(f64, 1.5f64);
}
#[test]
fn a3_to_form_value_variants_mirror_tovalue_matrix() {
assert_eq!(
<i32 as FormBridge>::to_form_value(&-3),
Value::I64(-3),
"integers bridge as I64"
);
assert_eq!(
<u8 as FormBridge>::to_form_value(&1),
Value::I64(1),
"unsigned bridges as I64"
);
assert_eq!(
<f32 as FormBridge>::to_form_value(&0.5),
Value::F64(0.5),
"floats bridge as F64"
);
assert_eq!(
<String as FormBridge>::to_form_value(&"hi".to_string()),
Value::String("hi".into())
);
assert_eq!(
<bool as FormBridge>::to_form_value(&true),
Value::Bool(true)
);
}
#[test]
fn a3_from_validated_rejects_wrong_variants() {
assert_eq!(<String as FormBridge>::from_validated(&Value::I64(1)), None);
assert_eq!(
<bool as FormBridge>::from_validated(&Value::String("x".into())),
None
);
assert_eq!(<i32 as FormBridge>::from_validated(&Value::F64(1.0)), None);
assert_eq!(<u32 as FormBridge>::from_validated(&Value::Null), None);
assert_eq!(
<f64 as FormBridge>::from_validated(&Value::Bool(true)),
None
);
}
#[test]
fn a3_from_validated_is_range_strict_for_narrow_integers() {
assert_eq!(<i8 as FormBridge>::from_validated(&Value::I64(300)), None);
assert_eq!(<u8 as FormBridge>::from_validated(&Value::I64(-1)), None);
assert_eq!(
<i64 as FormBridge>::from_validated(&Value::I64(i64::MIN)),
Some(i64::MIN)
);
}
}