Skip to content

Commit

Permalink
egui_form: allow passing &str instead of field_path!() for non nested…
Browse files Browse the repository at this point in the history
… fields
  • Loading branch information
lucasmerlin committed May 6, 2024
1 parent 1d40d3c commit c99b114
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 31 deletions.
10 changes: 6 additions & 4 deletions crates/egui_form/examples/garde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ struct Nested {
fn form_ui(ui: &mut egui::Ui, test: &mut Test) {
let mut form = Form::new().add_report(egui_form::garde::GardeReport::new(test.validate(&())));

FormField::new(&mut form, field_path!("user_name"))
FormField::new(&mut form, "user_name")
.label("User Name")
.ui(ui, egui::TextEdit::singleline(&mut test.user_name));
FormField::new(&mut form, field_path!("email"))
FormField::new(&mut form, "email")
.label("Email")
.ui(ui, egui::TextEdit::singleline(&mut test.email));
FormField::new(&mut form, field_path!("nested", "test"))
Expand Down Expand Up @@ -70,7 +70,7 @@ fn main() -> eframe::Result<()> {
mod tests {
use super::*;
use egui_form::garde::GardeReport;
use egui_form::EguiValidationReport;
use egui_form::{EguiValidationReport, IntoFieldPath};

#[test]
fn test() {
Expand All @@ -83,7 +83,9 @@ mod tests {

let report = GardeReport::new(test.validate(&()));

assert!(report.get_field_error(field_path!("user_name")).is_some());
assert!(report
.get_field_error("user_name".into_field_path())
.is_some());
assert!(report.get_field_error(field_path!("email")).is_some());
assert!(report
.get_field_error(field_path!("nested", "test"))
Expand Down
18 changes: 11 additions & 7 deletions crates/egui_form/examples/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ fn form_ui(ui: &mut egui::Ui, test: &mut Test) {
}),
);

FormField::new(&mut form, field_path!("user_name"))
FormField::new(&mut form, "user_name")
.label("User Name")
.ui(ui, egui::TextEdit::singleline(&mut test.user_name));
FormField::new(&mut form, field_path!("email"))
FormField::new(&mut form, "email")
.label("Email")
.ui(ui, egui::TextEdit::singleline(&mut test.email));
FormField::new(&mut form, field_path!("nested", "test"))
Expand Down Expand Up @@ -91,7 +91,7 @@ fn main() -> eframe::Result<()> {
mod tests {
use super::*;
use egui_form::validator::field_path;
use egui_form::EguiValidationReport;
use egui_form::{EguiValidationReport, IntoFieldPath};

#[test]
fn test_validate() {
Expand All @@ -104,13 +104,17 @@ mod tests {

let report = egui_form::validator::ValidatorReport::validate(test);

assert!(report.get_field_error(field_path!("user_name")).is_some());
assert!(report.get_field_error(field_path!("email")).is_some());
assert!(report
.get_field_error(field_path!("nested", "test"))
.get_field_error(field_path!("user_name").into_field_path())
.is_some());
assert!(report
.get_field_error(field_path!("vec", 0, "test"))
.get_field_error(field_path!("email").into_field_path())
.is_some());
assert!(report
.get_field_error(field_path!("nested", "test").into_field_path())
.is_some());
assert!(report
.get_field_error(field_path!("vec", 0, "test").into_field_path())
.is_some());

assert_eq!(report.error_count(), 4);
Expand Down
9 changes: 7 additions & 2 deletions crates/egui_form/src/form_field.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::form::FormFieldState;
use crate::validation_report::IntoFieldPath;
use crate::{EguiValidationReport, Form};
use egui::{Response, RichText, TextStyle, Widget};
use std::borrow::Cow;
Expand All @@ -17,11 +18,15 @@ impl<'a, 'f, Errors: EguiValidationReport> FormField<'a, 'f, Errors> {
/// Pass a [Form] and a reference to the field you want to validate.
/// If you use [crate::garde], just pass the field name / path as a string.
/// If you use [crate::validator], pass a field reference using the [crate::field_path] macro.
pub fn new<'c>(form: &'f mut Form<Errors>, field: Errors::FieldPath<'c>) -> Self {
pub fn new<'c, I: IntoFieldPath<Errors::FieldPath<'c>>>(
form: &'f mut Form<Errors>,
into_field_path: I,
) -> Self {
let field_path = into_field_path.into_field_path();
let error = form
.validation_results
.iter()
.find_map(|errors| errors.get_field_error(field));
.find_map(|errors| errors.get_field_error(field_path.clone()));

FormField {
error,
Expand Down
39 changes: 32 additions & 7 deletions crates/egui_form/src/garde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::borrow::Cow;
use std::collections::BTreeMap;

pub use crate::_garde_field_path as field_path;
use crate::validation_report::IntoFieldPath;
pub use garde;
use garde::Path;

Expand All @@ -11,15 +12,15 @@ use garde::Path;
/// ```rust
/// use garde::Path;
/// use egui_form::garde::field_path;
/// assert_eq!(field_path!("root", "vec", 0, "nested"), &Path::new("root")
/// assert_eq!(field_path!("root", "vec", 0, "nested"), Path::new("root")
/// .join("vec").join(0).join("nested"))
/// ```
#[macro_export]
macro_rules! _garde_field_path {
(
$($field:expr $(,)?)+
) => {
&$crate::garde::garde::Path::empty()
$crate::garde::garde::Path::empty()
$(
.join($field)
)+
Expand All @@ -36,7 +37,7 @@ impl GardeReport {
/// # Example
/// ```
/// use garde::Validate;
/// use egui_form::EguiValidationReport;
/// use egui_form::{EguiValidationReport, IntoFieldPath};
/// use egui_form::garde::{field_path, GardeReport};
/// #[derive(Validate)]
/// struct Test {
Expand All @@ -53,8 +54,8 @@ impl GardeReport {
///
/// let report = GardeReport::new(test.validate(&()));
///
/// assert!(report.get_field_error(field_path!("user_name")).is_some());
/// assert!(report.get_field_error(field_path!("tags", 1)).is_some());
/// assert!(report.get_field_error(field_path!("user_name").into_field_path()).is_some());
/// assert!(report.get_field_error(field_path!("tags", 1).into_field_path()).is_some());
/// ```
pub fn new(result: Result<(), garde::Report>) -> Self {
if let Err(errors) = result {
Expand All @@ -66,11 +67,11 @@ impl GardeReport {
}

impl EguiValidationReport for GardeReport {
type FieldPath<'a> = &'a Path;
type FieldPath<'a> = Path;
type Errors = BTreeMap<Path, garde::Error>;

fn get_field_error(&self, field: Self::FieldPath<'_>) -> Option<Cow<'static, str>> {
self.0.get(field).map(|e| e.to_string().into())
self.0.get(&field).map(|e| e.to_string().into())
}

fn has_errors(&self) -> bool {
Expand All @@ -89,3 +90,27 @@ impl EguiValidationReport for GardeReport {
}
}
}

impl IntoFieldPath<Path> for Path {
fn into_field_path(self) -> Path {
self
}
}

impl IntoFieldPath<Path> for &str {
fn into_field_path(self) -> Path {
Path::new(self)
}
}

impl IntoFieldPath<Path> for String {
fn into_field_path(self) -> Path {
Path::new(self)
}
}

impl IntoFieldPath<Path> for usize {
fn into_field_path(self) -> Path {
Path::new(self)
}
}
2 changes: 1 addition & 1 deletion crates/egui_form/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,4 @@ pub mod validator;

pub use form::Form;
pub use form_field::*;
pub use validation_report::EguiValidationReport;
pub use validation_report::{EguiValidationReport, IntoFieldPath};
8 changes: 7 additions & 1 deletion crates/egui_form/src/validation_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::borrow::Cow;
/// A trait telling egui_form how to parse validation errors.
pub trait EguiValidationReport {
/// The type used to identify fields.
type FieldPath<'a>: Copy;
type FieldPath<'a>: Clone;
/// The type of the errors.
type Errors;

Expand All @@ -19,3 +19,9 @@ pub trait EguiValidationReport {
/// Returns a reference to the errors.
fn get_errors(&self) -> Option<&Self::Errors>;
}

/// Helper trait to allow constructing non-nested FormFields without using the field_path!() macro
pub trait IntoFieldPath<T> {
/// Conver this type into a [T]
fn into_field_path(self) -> T;
}
51 changes: 42 additions & 9 deletions crates/egui_form/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,36 @@ use crate::EguiValidationReport;
use std::borrow::Cow;

pub use crate::_validator_field_path as field_path;
use crate::validation_report::IntoFieldPath;

use std::hash::Hash;
pub use validator;
use validator::{Validate, ValidationError, ValidationErrors, ValidationErrorsKind};

/// Represents either a field in a struct or a indexed field in a list.
/// Usually created with the [crate::field_path] macro.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PathItem {
pub enum PathItem<'a> {
/// Field in a struct.
Field(Cow<'static, str>),
Field(Cow<'a, str>),
/// Indexed field in a list.
Indexed(usize),
}

impl From<usize> for PathItem {
impl From<usize> for PathItem<'_> {
fn from(value: usize) -> Self {
PathItem::Indexed(value)
}
}

impl From<String> for PathItem {
impl From<String> for PathItem<'_> {
fn from(value: String) -> Self {
PathItem::Field(Cow::Owned(value))
}
}

impl From<&'static str> for PathItem {
fn from(value: &'static str) -> Self {
impl<'a> From<&'a str> for PathItem<'a> {
fn from(value: &'a str) -> Self {
PathItem::Field(Cow::Borrowed(value))
}
}
Expand Down Expand Up @@ -129,13 +131,26 @@ fn get_error_recursively<'a>(
}
}

/// Helper enum to allow passing non nested field paths as a &str, without using the field_path!() macro
#[doc(hidden)]
#[derive(Clone)]
pub enum ValidatorPathType<'a> {
Single(PathItem<'a>),
Borrowed(&'a [PathItem<'a>]),
}

impl EguiValidationReport for ValidatorReport {
type FieldPath<'a> = &'a [PathItem];
type FieldPath<'a> = ValidatorPathType<'a>;
type Errors = ValidationErrors;

fn get_field_error(&self, path: Self::FieldPath<'_>) -> Option<Cow<'static, str>> {
fn get_field_error(&self, into_path: Self::FieldPath<'_>) -> Option<Cow<'static, str>> {
let path = into_path.into_field_path();

let error = if let Some(errors) = &self.errors {
get_error_recursively(errors, path)
match path {
ValidatorPathType::Single(item) => get_error_recursively(errors, &[item]),
ValidatorPathType::Borrowed(path) => get_error_recursively(errors, path),
}
} else {
None
};
Expand Down Expand Up @@ -168,3 +183,21 @@ impl EguiValidationReport for ValidatorReport {
self.errors.as_ref()
}
}

impl<'a> IntoFieldPath<ValidatorPathType<'a>> for ValidatorPathType<'a> {
fn into_field_path(self) -> ValidatorPathType<'a> {
self
}
}

impl<'a> IntoFieldPath<ValidatorPathType<'a>> for &'a [PathItem<'a>] {
fn into_field_path(self) -> ValidatorPathType<'a> {
ValidatorPathType::Borrowed(self)
}
}

impl<'a> IntoFieldPath<ValidatorPathType<'a>> for &'a str {
fn into_field_path(self) -> ValidatorPathType<'a> {
ValidatorPathType::Single(PathItem::Field(Cow::Borrowed(self)))
}
}

0 comments on commit c99b114

Please sign in to comment.