use keelson_core::{Bind, Error, FromValue, ToValue, Value};
#[derive(Debug, Clone, PartialEq, Bind)]
struct UserId(i64);
#[derive(Debug, Clone, PartialEq, Bind)]
pub struct Email(pub String);
#[derive(Debug, Clone, PartialEq, Bind)]
struct Slug {
raw: String,
}
#[derive(Debug, Clone, PartialEq, Bind)]
struct Tagged<T>(T);
#[derive(Debug, Clone, PartialEq, Bind)]
struct Token(uuid::Uuid);
const _: () = keelson_exec::assert_bind::<UserId>();
const _: () = keelson_exec::assert_bind::<Email>();
const _: () = keelson_exec::assert_bind::<Slug>();
const _: () = keelson_exec::assert_bind::<Token>();
const _: () = keelson_exec::assert_bind::<Tagged<i64>>();
#[test]
fn a_tuple_newtype_delegates_both_ways() {
assert_eq!(UserId(7).to_value(), Value::I64(7));
assert_eq!(UserId::from_value(Value::I64(7)).unwrap(), UserId(7));
}
#[test]
fn a_named_single_field_struct_is_a_newtype_too() {
assert_eq!(
Slug { raw: "ada".into() }.to_value(),
Value::Text("ada".into())
);
assert_eq!(
Slug::from_value(Value::Text("ada".into())).unwrap(),
Slug { raw: "ada".into() }
);
}
#[test]
fn generic_newtypes_bind_at_every_inner_type() {
assert_eq!(Tagged(1i64).to_value(), Value::I64(1));
assert_eq!(
Tagged("x".to_owned()).to_value(),
Value::Text("x".to_owned())
);
assert_eq!(
Tagged::<i64>::from_value(Value::I64(1)).unwrap(),
Tagged(1i64)
);
}
#[test]
fn a_mapped_type_inside_a_newtype_keeps_its_variant() {
let u = uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
assert_eq!(Token(u).to_value(), Value::Uuid(u));
assert_eq!(Token::from_value(Value::Uuid(u)).unwrap(), Token(u));
}
#[test]
fn the_inner_types_from_value_semantics_are_inherited() {
assert_eq!(UserId::from_value(Value::I32(7)).unwrap(), UserId(7));
assert_eq!(UserId::from_value(Value::U8(7)).unwrap(), UserId(7));
}
#[test]
fn a_failure_is_the_inner_types_failure() {
let e = UserId::from_value(Value::Text("x".into())).unwrap_err();
assert!(
matches!(
e,
Error::TypeMismatch {
expected: "i64",
found: "text"
}
),
"{e}"
);
assert_eq!(e.to_string(), "cannot read text as i64");
let e = UserId::from_value(Value::Null).unwrap_err();
assert_eq!(e.to_string(), "cannot read NULL as i64");
}
#[test]
fn options_of_newtypes_bind_and_read() {
assert_eq!(Some(UserId(1)).to_value(), Value::I64(1));
assert_eq!(None::<UserId>.to_value(), Value::Null);
assert_eq!(Option::<UserId>::from_value(Value::Null).unwrap(), None);
assert_eq!(
Option::<UserId>::from_value(Value::I64(1)).unwrap(),
Some(UserId(1))
);
}
#[test]
fn newtypes_nest() {
#[derive(Debug, Clone, PartialEq, Bind)]
struct AdminId(UserId);
assert_eq!(AdminId(UserId(3)).to_value(), Value::I64(3));
assert_eq!(
AdminId::from_value(Value::I64(3)).unwrap(),
AdminId(UserId(3))
);
}
#[test]
fn a_private_inner_field_is_reachable_only_to_the_derive() {
mod private {
use keelson_core::Bind;
#[derive(Debug, Clone, PartialEq, Bind)]
pub(crate) struct Secret(String);
pub(crate) fn make(s: &str) -> Secret {
Secret(s.to_owned())
}
}
assert_eq!(private::make("s").to_value(), Value::Text("s".into()));
}
#[test]
fn any_bindable_inner_type_works() {
#[derive(Debug, Clone, PartialEq, Bind)]
struct Blob(Vec<u8>);
#[derive(Debug, Clone, PartialEq, Bind)]
struct Flag(bool);
#[derive(Debug, Clone, PartialEq, Bind)]
struct Ratio(f64);
assert_eq!(Blob(vec![1, 2]).to_value(), Value::Bytes(vec![1, 2]));
assert_eq!(Flag(true).to_value(), Value::Bool(true));
assert_eq!(Ratio(1.5).to_value(), Value::F64(1.5));
assert_eq!(
Blob::from_value(Value::Bytes(vec![1])).unwrap(),
Blob(vec![1])
);
}
#[test]
fn a_newtype_over_an_option_is_nullable() {
#[derive(Debug, Clone, PartialEq, Bind)]
struct Nickname(Option<String>);
assert_eq!(Nickname(None).to_value(), Value::Null);
assert_eq!(Nickname::from_value(Value::Null).unwrap(), Nickname(None));
}