From 5c51a558d984cf770c5026aff020527fa2020f04 Mon Sep 17 00:00:00 2001 From: Luis Alberto Santos Date: Sun, 19 Jan 2025 17:28:48 +0100 Subject: [PATCH 1/2] allow an optional context when using dive --- README.md | 1 + garde/tests/rules/dive_with_ctx.rs | 53 +++++++++++++++++++ garde/tests/rules/mod.rs | 1 + ...ules__rules__dive_with_ctx__invalid-2.snap | 13 +++++ .../rules__rules__dive_with_ctx__invalid.snap | 11 ++++ garde_derive/src/check.rs | 4 +- garde_derive/src/emit.rs | 10 +++- garde_derive/src/model.rs | 4 +- garde_derive/src/syntax.rs | 20 +++++-- 9 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 garde/tests/rules/dive_with_ctx.rs create mode 100644 garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid-2.snap create mode 100644 garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid.snap diff --git a/README.md b/README.md index 70d763d..297555c 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ if let Err(e) = data.validate() { Additional notes: - `required` is only available for `Option` fields. +- `dive` accepts an optional context: `#[garde(dive(&self.other_field))]` - The `` argument for `length` is [explained here](#length-modes) - For `length` and `range`: - If `equal` is defined, `min` and `max` must be omitted. diff --git a/garde/tests/rules/dive_with_ctx.rs b/garde/tests/rules/dive_with_ctx.rs new file mode 100644 index 0000000..69c946c --- /dev/null +++ b/garde/tests/rules/dive_with_ctx.rs @@ -0,0 +1,53 @@ +use super::util; + +#[derive(Clone, Copy, Debug, garde::Validate)] +#[garde(context((usize, usize) as ctx))] +struct Inner<'a> { + #[garde(length(min = ctx.0, max = ctx.1))] + field: &'a str, +} + +#[derive(Debug, garde::Validate)] +struct Test<'a> { + #[garde(skip)] + min: usize, + #[garde(skip)] + max: usize, + #[garde(dive(&(self.min, self.max)))] + inner: Inner<'a>, +} + +#[derive(Debug, garde::Validate)] +#[garde(context((usize, usize)))] +struct Test2<'a> { + #[garde(dive)] + inner: Inner<'a>, +} + +#[test] +fn valid() { + let inner = Inner { field: "asdf" }; + util::check_ok(&[Test2 { inner }], &(1, 5)); + util::check_ok( + &[Test { + min: 1, + max: 5, + inner, + }], + &(), + ); +} + +#[test] +fn invalid() { + let inner = Inner { field: "asdfgh" }; + util::check_fail!(&[Test2 { inner }], &(1, 5)); + util::check_fail!( + &[Test { + min: 1, + max: 5, + inner, + }], + &(), + ); +} diff --git a/garde/tests/rules/mod.rs b/garde/tests/rules/mod.rs index 4c81bc3..9bf6d9d 100644 --- a/garde/tests/rules/mod.rs +++ b/garde/tests/rules/mod.rs @@ -6,6 +6,7 @@ mod contains; mod credit_card; mod custom; mod dive; +mod dive_with_ctx; mod dive_with_rules; mod email; mod inner; diff --git a/garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid-2.snap b/garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid-2.snap new file mode 100644 index 0000000..c5a6490 --- /dev/null +++ b/garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid-2.snap @@ -0,0 +1,13 @@ +--- +source: garde/tests/./rules/dive_with_ctx.rs +expression: snapshot +snapshot_kind: text +--- +Test { + min: 1, + max: 5, + inner: Inner { + field: "asdfgh", + }, +} +inner.field: length is greater than 5 diff --git a/garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid.snap b/garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid.snap new file mode 100644 index 0000000..1564089 --- /dev/null +++ b/garde/tests/rules/snapshots/rules__rules__dive_with_ctx__invalid.snap @@ -0,0 +1,11 @@ +--- +source: garde/tests/./rules/dive_with_ctx.rs +expression: snapshot +snapshot_kind: text +--- +Test2 { + inner: Inner { + field: "asdfgh", + }, +} +inner.field: length is greater than 5 diff --git a/garde_derive/src/check.rs b/garde_derive/src/check.rs index 4bc2167..225fd54 100644 --- a/garde_derive/src/check.rs +++ b/garde_derive/src/check.rs @@ -264,7 +264,7 @@ fn check_field(field: model::Field, options: &model::Options) -> syn::Result apply!(alias = alias.value, span), // Message(message) => apply!(message = message, span), Code(code) => apply!(code = code.value, span), - Dive => apply!(dive = span, span), + Dive(ctx) => apply!(dive = (span, ctx), span), Custom(custom) => rule_set.custom_rules.push(custom), Required => apply!(Required(), span), Ascii => apply!(Ascii(), span), diff --git a/garde_derive/src/emit.rs b/garde_derive/src/emit.rs index 782df6f..2c59985 100644 --- a/garde_derive/src/emit.rs +++ b/garde_derive/src/emit.rs @@ -381,7 +381,7 @@ where false => None, }; let inner = match (&field.dive, &field.rule_set.inner) { - (Some(..), None) => Some(quote! { + (Some((_, None)), None) => Some(quote! { ::garde::validate::Validate::validate_into( &*__garde_binding, __garde_user_ctx, @@ -389,6 +389,14 @@ where __garde_report, ); }), + (Some((_, Some(ctx))), None) => Some(quote! { + ::garde::validate::Validate::validate_into( + &*__garde_binding, + #ctx, + &mut __garde_path, + __garde_report, + ); + }), (None, Some(inner)) => Some( Inner { rules_mod, diff --git a/garde_derive/src/model.rs b/garde_derive/src/model.rs index 683b99b..d3d9ec5 100644 --- a/garde_derive/src/model.rs +++ b/garde_derive/src/model.rs @@ -78,7 +78,7 @@ pub enum RawRuleKind { Rename(Str), // Message(Message), Code(Str), - Dive, + Dive(Option), Required, Ascii, Alphanumeric, @@ -184,7 +184,7 @@ pub struct ValidateField { // pub message: Option, pub code: Option, - pub dive: Option, + pub dive: Option<(Span, Option)>, pub rule_set: RuleSet, } diff --git a/garde_derive/src/syntax.rs b/garde_derive/src/syntax.rs index b969303..31199c4 100644 --- a/garde_derive/src/syntax.rs +++ b/garde_derive/src/syntax.rs @@ -251,7 +251,7 @@ impl Parse for model::RawRule { macro_rules! rules { (($input:ident, $ident:ident) { - $($name:literal => $rule:ident $(($content:ident))?,)* + $($name:literal => $rule:ident $(($content:ident))? $(( ? $content_opt:ident))?,)* }) => { match $ident.to_string().as_str() { $( @@ -259,10 +259,24 @@ impl Parse for model::RawRule { $( let $content; syn::parenthesized!($content in $input); + let $content = $content.parse()?; + )? + $( + let $content_opt = if $input.peek(syn::token::Paren) { + let $content_opt; + syn::parenthesized!($content_opt in $input); + if $content_opt.is_empty() { + None + } else { + Some($content_opt.parse()?) + } + } else { + None + }; )? Ok(model::RawRule { span: $ident.span(), - kind: model::RawRuleKind::$rule $(($content.parse()?))? + kind: model::RawRuleKind::$rule $(($content))? $(($content_opt))? }) } )* @@ -278,7 +292,7 @@ impl Parse for model::RawRule { "rename" => Rename(content), // "message" => Message(content), "code" => Code(content), - "dive" => Dive, + "dive" => Dive(? content), "required" => Required, "ascii" => Ascii, "alphanumeric" => Alphanumeric, From ecd9bac5ff9136601117f25a2dd73dbdd4e8594d Mon Sep 17 00:00:00 2001 From: Luis Alberto Santos Date: Sun, 19 Jan 2025 18:01:52 +0100 Subject: [PATCH 2/2] ctx is always a ref --- README.md | 2 +- garde/tests/rules/dive_with_ctx.rs | 2 +- garde_derive/src/emit.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 297555c..7667853 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ if let Err(e) = data.validate() { Additional notes: - `required` is only available for `Option` fields. -- `dive` accepts an optional context: `#[garde(dive(&self.other_field))]` +- `dive` accepts an optional context: `#[garde(dive(self.other_field))]` - The `` argument for `length` is [explained here](#length-modes) - For `length` and `range`: - If `equal` is defined, `min` and `max` must be omitted. diff --git a/garde/tests/rules/dive_with_ctx.rs b/garde/tests/rules/dive_with_ctx.rs index 69c946c..84c52d7 100644 --- a/garde/tests/rules/dive_with_ctx.rs +++ b/garde/tests/rules/dive_with_ctx.rs @@ -13,7 +13,7 @@ struct Test<'a> { min: usize, #[garde(skip)] max: usize, - #[garde(dive(&(self.min, self.max)))] + #[garde(dive((self.min, self.max)))] inner: Inner<'a>, } diff --git a/garde_derive/src/emit.rs b/garde_derive/src/emit.rs index 2c59985..9e2d1d2 100644 --- a/garde_derive/src/emit.rs +++ b/garde_derive/src/emit.rs @@ -392,7 +392,7 @@ where (Some((_, Some(ctx))), None) => Some(quote! { ::garde::validate::Validate::validate_into( &*__garde_binding, - #ctx, + &#ctx, &mut __garde_path, __garde_report, );