From b9ccac04e95c9112f27eb30678538ab78f935cfa Mon Sep 17 00:00:00 2001 From: szagi3891 Date: Thu, 5 Mar 2026 21:52:58 +0100 Subject: [PATCH 1/3] test form --- demo/app/src/app/form_example/mod.rs | 222 +++++++++++++++++++++++++++ demo/app/src/app/mod.rs | 1 + demo/app/src/app/render.rs | 9 +- demo/app/src/app/route.rs | 4 + 4 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 demo/app/src/app/form_example/mod.rs diff --git a/demo/app/src/app/form_example/mod.rs b/demo/app/src/app/form_example/mod.rs new file mode 100644 index 00000000..517d7047 --- /dev/null +++ b/demo/app/src/app/form_example/mod.rs @@ -0,0 +1,222 @@ +use vertigo::{Computed, Value, bind, component, css, dom, transaction}; + +type ErrorLine = (Vec, String); + +#[derive(Clone)] +struct ValidatorError { + message: Option, + errors: Vec, +} + +trait FormNode { + fn result(&self, getter_name: impl Into) -> Computed>; +} + +struct ErrorBuilder { + errors: Vec<(Vec, String)>, +} + +impl ErrorBuilder { + pub fn new() -> ErrorBuilder { + ErrorBuilder { errors: Vec::new() } + } + + pub fn add(mut self, result: Result) -> Self { + if let Err(err) = result { + self.errors.extend(err.errors); + } + self + } + + pub fn export(self) -> ValidatorError { + ValidatorError { + message: None, + errors: self.errors, + } + } +} + +// type Erro/ + +//........................................................................................ +//........................................................................................ +//........................................................................................ + +#[derive(Clone)] +struct FormValue { + text: Value, + value: Value, + // jedną stronę --> + // w drugą stronę --> +} + +impl FormNode for FormValue { + fn result(&self, _getter_name: impl Into) -> Computed> { + todo!() + } +} + +//........................................................................................ + +#[derive(Clone)] +struct FormDate { + day: FormValue, + mounth: FormValue, + year: FormValue, + // error: Value>, +} + +#[derive(Clone, PartialEq)] +struct FormDate1 { + day: u8, + mounth: u8, + year: u16, +} + +impl FormNode for FormDate { + fn result( + &self, + _getter_name: impl Into, + ) -> Computed> { + let ggg = self.clone(); + + Computed::from(move |context| { + let day = ggg.day.result("day").get(context); + let mounth = ggg.mounth.result("mounth").get(context); + let year = ggg.year.result("year").get(context); + + if let (Ok(day), Ok(mounth), Ok(year)) = (day.clone(), mounth.clone(), year.clone()) { + return Ok(FormDate1 { day, mounth, year }); + } + + let errors = ErrorBuilder::new().add(day).add(mounth).add(year).export(); + Err(errors) + }) + } +} + +//........................................................................................ + +#[derive(Clone)] +struct FormModel { + name: FormValue, + birth: FormDate, + start_schools: FormDate, +} + +#[derive(Clone, PartialEq)] +struct FormModel1 { + name: String, + birth: FormDate1, + start_schools: FormDate1, +} + +impl FormNode for FormModel { + fn result( + &self, + _getter_name: impl Into, + ) -> Computed> { + todo!() + } +} + +//........................................................................................ + +/* + w input mogą występować jakieś stany "przejściowe" które nie są dozwolone, ale są + konieczne zeby przejść z jednego dozwolonego stanu do innego + + + zestawy róznych walidatorów jak ograć + np, datę jak zrealizować ... ? + +*/ + +#[component] +pub fn FormExample() { + let name = Value::new("ddadas".to_string()); + let email = Value::new("".to_string()); + + let on_submit = bind!(name, email, |_| { + transaction(|context| { + let name_val = name.get(context); + let email_val = email.get(context); + log::info!("Form submitted: name={}, email={}", name_val, email_val); + }); + }); + + let wrapper_css = css! {" + border: 1px solid #ccc; + padding: 20px; + margin: 20px; + border-radius: 8px; + max-width: 400px; + background-color: #f9f9f9; + display: flex; + flex-direction: column; + gap: 15px; + "}; + + let label_css = css! {" + display: flex; + flex-direction: column; + font-weight: bold; + gap: 5px; + "}; + + let input_css = css! {" + padding: 8px; + border: 1px solid #999; + border-radius: 4px; + font-weight: normal; + "}; + + let button_css = css! {" + padding: 10px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-weight: bold; + :hover { + background-color: #0056b3; + } + "}; + + dom! { +
+

"Form Example"

+ + + + + + +
+ "Current name: " { name } +
+
+ "Current email: " { email } +
+
+ } +} diff --git a/demo/app/src/app/mod.rs b/demo/app/src/app/mod.rs index 9f555673..84850f9b 100644 --- a/demo/app/src/app/mod.rs +++ b/demo/app/src/app/mod.rs @@ -1,6 +1,7 @@ mod chat; mod counters; mod dropfiles; +mod form_example; mod game_of_life; mod github_explorer; mod input; diff --git a/demo/app/src/app/render.rs b/demo/app/src/app/render.rs index 3b2bf959..bd2d7cd6 100644 --- a/demo/app/src/app/render.rs +++ b/demo/app/src/app/render.rs @@ -3,9 +3,10 @@ use vertigo::{Computed, Css, DomNode, KeyDownEvent, css, dom, get_driver, includ use crate::app::{self, counters::state_counters, state::state_route}; use super::{ - chat::Chat, counters::CountersDemo, dropfiles::DropFiles, game_of_life::GameOfLife, - github_explorer::GitHubExplorer, input::MyInput, js_api_access::JsApiAccess, list::ListDemo, - route::Route, styling::Styling, sudoku::Sudoku, todo::Todo, + chat::Chat, counters::CountersDemo, dropfiles::DropFiles, form_example::FormExample, + game_of_life::GameOfLife, github_explorer::GitHubExplorer, input::MyInput, + js_api_access::JsApiAccess, list::ListDemo, route::Route, styling::Styling, sudoku::Sudoku, + todo::Todo, }; fn css_menu_item(active: bool) -> Css { @@ -73,6 +74,7 @@ fn render_header() -> DomNode { { render_menu_item(Route::DropFile) } { render_menu_item(Route::JsApiAccess) } { render_menu_item(Route::List) } + { render_menu_item(Route::Form) } } @@ -126,6 +128,7 @@ pub fn render(state: &app::State) -> DomNode { Route::DropFile => dom! { }, Route::JsApiAccess => dom! { }, Route::List => dom! { }, + Route::Form => dom! { }, Route::NotFound => { get_driver().set_status(404); dom! {
"Page Not Found"
} diff --git a/demo/app/src/app/route.rs b/demo/app/src/app/route.rs index 1dc0c1c8..c4564957 100644 --- a/demo/app/src/app/route.rs +++ b/demo/app/src/app/route.rs @@ -16,6 +16,7 @@ pub enum Route { DropFile, JsApiAccess, List, + Form, NotFound, } @@ -33,6 +34,7 @@ impl Route { "/drop-file" => Self::DropFile, "/js-api-access" => Self::JsApiAccess, "/list" => Self::List, + "/form" => Self::Form, _ => Self::NotFound, } } @@ -50,6 +52,7 @@ impl Route { Self::DropFile => "Drop File", Self::JsApiAccess => "JS Api Access", Self::List => "List", + Self::Form => "Form", Self::NotFound => "Not Found", } } @@ -76,6 +79,7 @@ impl Display for Route { Self::DropFile => "/drop-file", Self::JsApiAccess => "/js-api-access", Self::List => "/list", + Self::Form => "/form", Self::NotFound => "/not-found", }; f.write_str(&get_driver().route_to_public(str)) From 380d774dd0444255c36f791d684ee5541fc4a914 Mon Sep 17 00:00:00 2001 From: szagi3891 Date: Thu, 5 Mar 2026 22:02:53 +0100 Subject: [PATCH 2/3] logika walidacji --- demo/app/src/app/form_example/mod.rs | 36 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/demo/app/src/app/form_example/mod.rs b/demo/app/src/app/form_example/mod.rs index 517d7047..69fca5a1 100644 --- a/demo/app/src/app/form_example/mod.rs +++ b/demo/app/src/app/form_example/mod.rs @@ -8,6 +8,15 @@ struct ValidatorError { errors: Vec, } +impl ValidatorError { + pub fn from_message(message: String) -> ValidatorError { + ValidatorError { + message: Some(message), + errors: Vec::new(), + } + } +} + trait FormNode { fn result(&self, getter_name: impl Into) -> Computed>; } @@ -21,10 +30,20 @@ impl ErrorBuilder { ErrorBuilder { errors: Vec::new() } } - pub fn add(mut self, result: Result) -> Self { + pub fn add(mut self, getter_name: String, result: Result) -> Self { if let Err(err) = result { self.errors.extend(err.errors); + + if let Some(message) = err.message { + self.errors.push((vec![getter_name], message)); + } } + + self + } + + pub fn set_error(mut self, getter_name: String, message: String) -> Self { + self.errors.push((vec![getter_name], message)); self } @@ -76,9 +95,10 @@ struct FormDate1 { impl FormNode for FormDate { fn result( &self, - _getter_name: impl Into, + getter_name: impl Into, ) -> Computed> { let ggg = self.clone(); + let getter_name = getter_name.into(); Computed::from(move |context| { let day = ggg.day.result("day").get(context); @@ -86,10 +106,20 @@ impl FormNode for FormDate { let year = ggg.year.result("year").get(context); if let (Ok(day), Ok(mounth), Ok(year)) = (day.clone(), mounth.clone(), year.clone()) { + if mounth == 2 && day > 29 { + return Err(ValidatorError::from_message( + "Luty nie moze mieć więcej niz 29 dni".into(), + )); + } + return Ok(FormDate1 { day, mounth, year }); } - let errors = ErrorBuilder::new().add(day).add(mounth).add(year).export(); + let errors = ErrorBuilder::new() + .add(getter_name.clone(), day) + .add(getter_name.clone(), mounth) + .add(getter_name.clone(), year) + .export(); Err(errors) }) } From 028b9ee29920c531663540a16031124a94d1f60c Mon Sep 17 00:00:00 2001 From: szagi3891 Date: Thu, 5 Mar 2026 22:17:57 +0100 Subject: [PATCH 3/3] =?UTF-8?q?propagowanie=20b=C5=82=C4=99d=C3=B3w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demo/app/src/app/form_example/mod.rs | 76 +++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/demo/app/src/app/form_example/mod.rs b/demo/app/src/app/form_example/mod.rs index 69fca5a1..29cbf212 100644 --- a/demo/app/src/app/form_example/mod.rs +++ b/demo/app/src/app/form_example/mod.rs @@ -11,8 +11,8 @@ struct ValidatorError { impl ValidatorError { pub fn from_message(message: String) -> ValidatorError { ValidatorError { + errors: Vec::new(), //vec![(vec![getter_name], message.clone())], message: Some(message), - errors: Vec::new(), } } } @@ -32,7 +32,10 @@ impl ErrorBuilder { pub fn add(mut self, getter_name: String, result: Result) -> Self { if let Err(err) = result { - self.errors.extend(err.errors); + for (mut path, message) in err.errors { + path.insert(0, getter_name.clone()); + self.errors.push((path, message)); + } if let Some(message) = err.message { self.errors.push((vec![getter_name], message)); @@ -108,6 +111,7 @@ impl FormNode for FormDate { if let (Ok(day), Ok(mounth), Ok(year)) = (day.clone(), mounth.clone(), year.clone()) { if mounth == 2 && day > 29 { return Err(ValidatorError::from_message( + // getter_name.clone(), "Luty nie moze mieć więcej niz 29 dni".into(), )); } @@ -250,3 +254,71 @@ pub fn FormExample() { } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_date_errors() { + let err_day = ValidatorError::from_message("invalid day".into()); + let err_month = ValidatorError::from_message("invalid month".into()); + + // Simulate FormDate failing + let date_errors = ErrorBuilder::new() + .add("day".into(), Err::<(), _>(err_day)) + .add("month".into(), Err::<(), _>(err_month)) + .export(); + + assert_eq!(date_errors.errors.len(), 2); + assert_eq!( + date_errors.errors[0], + (vec!["day".to_string()], "invalid day".to_string()) + ); + assert_eq!( + date_errors.errors[1], + (vec!["month".to_string()], "invalid month".to_string()) + ); + } + + #[test] + fn test_error_propagation() { + let err_day = ValidatorError::from_message("invalid day".into()); + let err_month = ValidatorError::from_message("invalid month".into()); + + // Simulate FormDate failing + let date_errors = ErrorBuilder::new() + .add("day".into(), Err::<(), _>(err_day)) + .add("month".into(), Err::<(), _>(err_month)) + .export(); + + // Simulate FormModel failing because of birth (FormDate) + let model_errors = ErrorBuilder::new() + .add("birth".into(), Err::<(), _>(date_errors)) + .export(); + + for (path, msg) in &model_errors.errors { + println!("Path: {:?}, Msg: {}", path, msg); + } + + // We expect: + // ["birth", "day"] from err_day (nested in date_errors) + // ["birth", "month"] from err_month (nested in date_errors) + + assert_eq!(model_errors.errors.len(), 2); + assert_eq!( + model_errors.errors[0], + ( + vec!["birth".to_string(), "day".to_string()], + "invalid day".to_string() + ) + ); + assert_eq!( + model_errors.errors[1], + ( + vec!["birth".to_string(), "month".to_string()], + "invalid month".to_string() + ) + ); + } +}