use formars::prelude::*;
#[derive(Debug)]
struct StartsWith(char);
impl Rule<str> for StartsWith {
fn name(&self) -> &'static str {
"starts_with"
}
fn validate(&self, value: &str) -> Option<formars::formars_core::rule::RefineRejection> {
if value.starts_with(self.0) {
None
} else {
Some(formars::formars_core::rule::RefineRejection {
code: Some(IssueCode::Refine),
message: "must start with the expected char".into(),
params: Vec::new(),
})
}
}
}
fn signup_schema() -> ObjectSchema {
object()
.field("email", string().min(8).email())
.field("age", coerced::<u32>())
}
fn input(email: &str, age: &str) -> formars::formars_core::value::Object {
let mut o = formars::formars_core::value::Object::new();
o.insert("email", Value::from(email));
o.insert("age", Value::from(age));
o
}
#[test]
fn one_import_program_builds_parses_and_reports() {
let schema = signup_schema();
let parsed = schema
.parse(&input("user@example.com", "36"))
.expect("valid input must parse");
assert_eq!(parsed.get("age"), Some(&Value::I64(36)));
let Err(err) = string().min(8).parse(&"short".to_string()) else {
panic!("too-short input must fail");
};
let first = err.first().expect("at least one issue on failure");
assert_eq!(first.code, IssueCode::Min);
assert_eq!(first.path, FieldPath::ROOT);
}
#[test]
fn invalid_input_accumulates_path_addressed_issues() {
let Err(err) = signup_schema().parse(&input("user@example.com", "abc")) else {
panic!("non-coercible age must fail");
};
let ages: Vec<_> = err
.issues_for(&FieldPath::key("age"))
.map(|i| i.code.clone())
.collect();
assert_eq!(ages, vec![IssueCode::Coerce]);
assert_eq!(err.issues.len(), 1, "single violated constraint");
}
#[test]
fn custom_rule_composes_like_builtins() {
let schema = string().min(2).rule(StartsWith('a'));
assert!(schema.parse(&"apple".to_string()).is_ok());
let Err(err) = schema.parse(&"pear".to_string()) else {
panic!("rule rejection must fail");
};
assert_eq!(err.first().expect("issue").code, IssueCode::Refine);
}
#[test]
fn core_prelude_inventory_is_the_closed_17_name_list() {
fn accept<T>(_: T) {}
fn bridges<T: FormBridge>(v: &T) -> Value {
v.to_form_value()
}
fn parses<S: Schema>(schema: &S, input: &S::Input) -> bool {
schema.parse(input).is_ok()
}
fn ruled<T: ?Sized + Rule<str>>(r: &T) -> &str {
r.name()
}
fn companion_of<T>() -> Box<dyn DynSchema>
where
T: FormSchema,
T::Schema: 'static,
{
Box::new(<T as FormSchema>::form_schema())
}
#[derive(Debug, Clone)]
struct Composed(ObjectSchema);
impl AsRef<ObjectSchema> for Composed {
fn as_ref(&self) -> &ObjectSchema {
&self.0
}
}
accept(coerced::<u32>());
accept(string());
accept(bool());
accept(number::<i64>());
let _: ObjectSchema = object();
let _: FieldPath = FieldPath::key("path");
let _: FormaError = FormaError::default();
let _: FormaIssue = FormaIssue {
path: FieldPath::ROOT,
code: IssueCode::Required,
message: "missing".into(),
params: Vec::new(),
};
let root_code = IssueCode::Min;
assert_eq!(root_code, IssueCode::Min);
assert_eq!(bridges(&7u32), Value::I64(7));
assert_eq!(bridges(&true), Value::Bool(true));
assert_eq!(bridges(&String::from("x")), Value::from("x"));
assert!(parses(&string(), &"anything".to_string()));
assert_eq!(ruled(&StartsWith('a')), "starts_with");
let erased = companion_of::<Dummy>();
assert!(
erased.validate_value(&Value::Bool(false)).is_empty(),
"manual companion erases cleanly"
);
let nested = Nested::new(Composed(object()));
let composed = object().field("inner", nested);
let mut inner = formars::formars_core::value::Object::new();
inner.insert("qty", Value::from("1"));
let mut outer = formars::formars_core::value::Object::new();
outer.insert("inner", Value::Object(inner));
assert!(
composed.parse(&outer).is_ok(),
"nested composition validates"
);
let _: Value = Value::Null;
}
#[derive(Debug)]
struct Dummy;
#[derive(Debug)]
struct AlwaysOk;
impl Schema for AlwaysOk {
type Input = Dummy;
type Output = Dummy;
fn parse(&self, _input: &Dummy) -> Result<Dummy, FormaError> {
Ok(Dummy)
}
}
impl DynSchema for AlwaysOk {
fn validate_value(&self, _v: &Value) -> Vec<FormaIssue> {
Vec::new()
}
fn shape(&self) -> &formars::formars_core::schema::ShapeNode {
static SHAPE: formars::formars_core::schema::ShapeNode =
formars::formars_core::schema::ShapeNode {
kind: formars::formars_core::schema::ShapeKind::Bool,
constraints: Vec::new(),
};
&SHAPE
}
fn metadata(&self) -> &formars::formars_core::schema::FieldMeta {
static META: formars::formars_core::schema::FieldMeta =
formars::formars_core::schema::FieldMeta {
label: None,
description: None,
placeholder: None,
extra: Vec::new(),
};
&META
}
}
impl FormSchema for Dummy {
type Schema = AlwaysOk;
fn form_schema() -> AlwaysOk {
AlwaysOk
}
}
#[test]
fn form_schema_trait_quartet_works_without_derive() {
let companion = <Dummy as FormSchema>::form_schema();
assert!(
companion.validate_value(&Value::Bool(true)).is_empty(),
"erased companion accepts anything"
);
let out = companion.parse(&Dummy).expect("typed identity parse");
let _: Dummy = out;
}
#[test]
fn multi_path_identity_and_mirror_resolution() {
let via_prelude: Value = Value::from("same");
let via_mirror: formars::formars_core::value::Value = via_prelude;
let back: Value = via_mirror;
assert_eq!(back, Value::from("same"));
let mirrored = formars::formars_core::prelude::object()
.field("age", formars::formars_core::prelude::coerced::<u32>());
let local = object().field("age", coerced::<u32>());
let sample = input("ignored", "7");
assert_eq!(
mirrored.parse(&sample).map(|o| o.get("age").cloned()),
local.parse(&sample).map(|o| o.get("age").cloned())
);
}