From 8fe4dcd8c97f9b939f18a30a127f34e77b27e7b8 Mon Sep 17 00:00:00 2001 From: yassun7010 Date: Sat, 22 Jun 2024 18:46:10 +0900 Subject: [PATCH 1/5] feat: replace fluent l10n format. --- serde_valid/tests/deprecated_fluent_test.rs | 54 ++++++++++++++++++ serde_valid/tests/fluent_test.rs | 4 +- serde_valid_derive/src/attribute.rs | 10 ++++ .../src/attribute/common/message_format.rs | 57 ++++++++++++++++++- serde_valid_derive/src/error.rs | 43 ++++++++++++++ 5 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 serde_valid/tests/deprecated_fluent_test.rs diff --git a/serde_valid/tests/deprecated_fluent_test.rs b/serde_valid/tests/deprecated_fluent_test.rs new file mode 100644 index 0000000..c1479ec --- /dev/null +++ b/serde_valid/tests/deprecated_fluent_test.rs @@ -0,0 +1,54 @@ +#[cfg(feature = "fluent")] +mod tests { + use fluent::{FluentBundle, FluentResource}; + use serde::Deserialize; + use serde_json::json; + use serde_valid::{fluent::Localize, Validate}; + use unic_langid::LanguageIdentifier; + + fn get_bundle(source: impl Into) -> FluentBundle { + let res = FluentResource::try_new(source.into()).expect("Failed to parse an FTL string."); + + let langid_en: LanguageIdentifier = "en-US".parse().expect("Parsing failed"); + let mut bundle = FluentBundle::new(vec![langid_en]); + bundle.add_resource(res).unwrap(); + + bundle + } + + #[test] + fn fluent_error() { + #[derive(Debug, Deserialize, Validate)] + struct Test { + #[validate(minimum = 5, fluent("hello-world"))] + a: u32, + #[validate(maximum = 10, fluent("intro", name = "taro"))] + b: u32, + } + + let test = Test { a: 1, b: 11 }; + let a = test.validate().unwrap_err().localize(&get_bundle( + ["hello-world = Hello, world!", "intro = Welcome, { $name }."].join("\n"), + )); + + assert_eq!( + a.to_string(), + json!({ + "errors": [], + "properties": { + "a": { + "errors": [ + "Hello, world!" + ] + }, + "b": { + "errors": [ + "Welcome, \u{2068}taro\u{2069}." + ] + } + } + }) + .to_string() + ); + } +} diff --git a/serde_valid/tests/fluent_test.rs b/serde_valid/tests/fluent_test.rs index c1479ec..6225566 100644 --- a/serde_valid/tests/fluent_test.rs +++ b/serde_valid/tests/fluent_test.rs @@ -20,9 +20,9 @@ mod tests { fn fluent_error() { #[derive(Debug, Deserialize, Validate)] struct Test { - #[validate(minimum = 5, fluent("hello-world"))] + #[validate(minimum = 5, message_l10n = fluent("hello-world"))] a: u32, - #[validate(maximum = 10, fluent("intro", name = "taro"))] + #[validate(maximum = 10, message_l10n = fluent("intro", name = "taro"))] b: u32, } diff --git a/serde_valid_derive/src/attribute.rs b/serde_valid_derive/src/attribute.rs index 6e02277..f9067bd 100644 --- a/serde_valid_derive/src/attribute.rs +++ b/serde_valid_derive/src/attribute.rs @@ -145,9 +145,19 @@ enum_str! { } } +#[cfg(not(feature = "fluent"))] +enum_str! { + pub enum MetaNameValueCustomMessage { + Message = "message", + MessageFn = "message_fn", + } +} + +#[cfg(feature = "fluent")] enum_str! { pub enum MetaNameValueCustomMessage { Message = "message", MessageFn = "message_fn", + MessageL10n = "message_l10n", } } diff --git a/serde_valid_derive/src/attribute/common/message_format.rs b/serde_valid_derive/src/attribute/common/message_format.rs index ee192d2..f59cb6f 100644 --- a/serde_valid_derive/src/attribute/common/message_format.rs +++ b/serde_valid_derive/src/attribute/common/message_format.rs @@ -84,7 +84,7 @@ fn extract_custom_message_format_from_meta_list( }), #[cfg(feature = "fluent")] message_type @ (MetaListCustomMessage::I18n | MetaListCustomMessage::Fluent) => { - get_fluent_message(message_type, path, &message_fn_define) + get_fluent_message_from_meta(message_type, path, &message_fn_define) } } } @@ -96,6 +96,11 @@ fn extract_custom_message_format_from_name_value( match custom_message_type { MetaNameValueCustomMessage::Message => get_message(&name_value.value), MetaNameValueCustomMessage::MessageFn => get_message_fn_from_meta_name_value(name_value), + #[cfg(feature = "fluent")] + MetaNameValueCustomMessage::MessageL10n => match &name_value.value { + syn::Expr::Call(call) => get_fluent_message_from_call_expr(call), + _ => Err(vec![crate::Error::l10n_need_fn_call(&name_value.value)]), + }, } } @@ -151,7 +156,7 @@ fn get_message(expr: &syn::Expr) -> Result, crate::E } #[cfg(feature = "fluent")] -fn get_fluent_message( +fn get_fluent_message_from_meta( message_type: &MetaListCustomMessage, path: &syn::Path, fn_define: &CommaSeparatedNestedMetas, @@ -210,6 +215,54 @@ fn get_fluent_message( } } +#[cfg(feature = "fluent")] +fn get_fluent_message_from_call_expr( + fn_define: &syn::ExprCall, +) -> Result, crate::Errors> { + use quote::ToTokens; + + if fn_define.func.to_token_stream().to_string() != "fluent" { + Err(vec![crate::Error::l10n_fn_name_not_allow(&fn_define.func)])? + }; + + let mut fn_args = fn_define.args.iter(); + let fluent_id = match fn_args.next() { + Some(syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(fluent_id), + .. + })) => fluent_id, + Some(expr) => Err(vec![crate::Error::fluent_id_must_be_str_lit(expr)])?, + None => Err(vec![crate::Error::fluent_id_not_found( + &fn_define.paren_token, + )])?, + }; + + let mut errors = vec![]; + let fluent_args = TokenStream::from_iter(fn_args.filter_map(|arg| { + if let syn::Expr::Assign(assign) = arg { + let key = &assign.left.to_token_stream().to_string(); + let value = &assign.right; + Some(quote!((#key, ::serde_valid::export::fluent::FluentValue::from(#value)))) + } else { + errors.push(crate::Error::fluent_allow_arg(arg)); + None + } + })); + + if errors.is_empty() { + Ok(WithWarnings::new(quote!( + ::serde_valid::validation::error::Format::Fluent( + ::serde_valid::fluent::Message{ + id: #fluent_id, + args: vec![#fluent_args] + } + ) + ))) + } else { + Err(errors) + } +} + #[cfg(feature = "fluent")] fn get_fluent_id(nested_meta: &NestedMeta) -> Option<&syn::LitStr> { match nested_meta { diff --git a/serde_valid_derive/src/error.rs b/serde_valid_derive/src/error.rs index 7f5fe7c..65cdcd0 100644 --- a/serde_valid_derive/src/error.rs +++ b/serde_valid_derive/src/error.rs @@ -348,6 +348,8 @@ impl Error { let candidates = &(MetaPathCustomMessage::iter().map(|x| x.name())) .chain(MetaListCustomMessage::iter().map(|x| x.name())) .chain(MetaNameValueCustomMessage::iter().map(|x| x.name())) + .unique() + .sorted() .collect::>(); let filterd_candidates = @@ -471,6 +473,47 @@ impl Error { ) } + #[cfg(feature = "fluent")] + pub fn l10n_need_fn_call(expr: &syn::Expr) -> Self { + Self::new( + expr.span(), + "#[validate(..., message_l10n = ???)] needs fn calling.".to_string(), + ) + } + + #[cfg(feature = "fluent")] + pub fn l10n_fn_name_not_allow(fn_name: &syn::Expr) -> Self { + Self::new( + fn_name.span(), + "#[validate(..., message_l10n = ???(...))] allows only \"fluent\".".to_string(), + ) + } + + #[cfg(feature = "fluent")] + pub fn fluent_id_must_be_str_lit(expr: &syn::Expr) -> Self { + Self::new( + expr.span(), + "#[validate(..., message_l10n = fluent(???, ...))] allow only string literal of the fluent id.", + ) + } + + #[cfg(feature = "fluent")] + pub fn fluent_id_not_found(paren: &syn::token::Paren) -> Self { + Self::new( + paren.span.span(), + "#[validate(..., message_l10n = fluent(???))] need the fluent id.", + ) + } + + #[cfg(feature = "fluent")] + pub fn fluent_allow_arg(expr: &syn::Expr) -> Self { + Self::new( + expr.span(), + "#[validate(..., message_l10n = fluent(..., ???))] allows only \"key=value\" of the fluent arg." + .to_string(), + ) + } + pub fn literal_only(span: impl Spanned) -> Self { Self::new(span.span(), "Allow literal only.") } From 036b524667b46a7ac0d86e2f3de222281c09b2ea Mon Sep 17 00:00:00 2001 From: yassun7010 Date: Sat, 22 Jun 2024 18:50:15 +0900 Subject: [PATCH 2/5] feat: readme. --- serde_valid/README.md | 3 ++- serde_valid/src/lib.rs | 3 ++- serde_valid/tests/fluent_test.rs | 4 ++-- ...{deprecated_fluent_test.rs => message_l10n_fluent_test.rs} | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) rename serde_valid/tests/{deprecated_fluent_test.rs => message_l10n_fluent_test.rs} (90%) diff --git a/serde_valid/README.md b/serde_valid/README.md index 1340433..79f6b9f 100644 --- a/serde_valid/README.md +++ b/serde_valid/README.md @@ -184,7 +184,8 @@ use serde_valid::{fluent::Localize, Validate}; #[derive(Validate)] struct Data ( - #[validate(min_length = 3, fluent("name-min-length", min_length = 3))] + #[validate(min_length = 3, message_l10n = fluent("name-min-length", min_length = 3))] + // Or Just `#[validate(min_length = 3, fluent("name-min-length", min_length = 3))]`. String, ); diff --git a/serde_valid/src/lib.rs b/serde_valid/src/lib.rs index 8ea62c3..3832250 100644 --- a/serde_valid/src/lib.rs +++ b/serde_valid/src/lib.rs @@ -193,7 +193,8 @@ //! //! #[derive(Validate)] //! struct Data ( -//! #[validate(min_length = 3, fluent("name-min-length", min_length = 3))] +//! #[validate(min_length = 3, message_l10n = fluent("name-min-length", min_length = 3))] +//! // Or Just `#[validate(min_length = 3, fluent("name-min-length", min_length = 3))]`. //! String, //! ); //! diff --git a/serde_valid/tests/fluent_test.rs b/serde_valid/tests/fluent_test.rs index 6225566..c1479ec 100644 --- a/serde_valid/tests/fluent_test.rs +++ b/serde_valid/tests/fluent_test.rs @@ -20,9 +20,9 @@ mod tests { fn fluent_error() { #[derive(Debug, Deserialize, Validate)] struct Test { - #[validate(minimum = 5, message_l10n = fluent("hello-world"))] + #[validate(minimum = 5, fluent("hello-world"))] a: u32, - #[validate(maximum = 10, message_l10n = fluent("intro", name = "taro"))] + #[validate(maximum = 10, fluent("intro", name = "taro"))] b: u32, } diff --git a/serde_valid/tests/deprecated_fluent_test.rs b/serde_valid/tests/message_l10n_fluent_test.rs similarity index 90% rename from serde_valid/tests/deprecated_fluent_test.rs rename to serde_valid/tests/message_l10n_fluent_test.rs index c1479ec..6225566 100644 --- a/serde_valid/tests/deprecated_fluent_test.rs +++ b/serde_valid/tests/message_l10n_fluent_test.rs @@ -20,9 +20,9 @@ mod tests { fn fluent_error() { #[derive(Debug, Deserialize, Validate)] struct Test { - #[validate(minimum = 5, fluent("hello-world"))] + #[validate(minimum = 5, message_l10n = fluent("hello-world"))] a: u32, - #[validate(maximum = 10, fluent("intro", name = "taro"))] + #[validate(maximum = 10, message_l10n = fluent("intro", name = "taro"))] b: u32, } From 78c607f73ffd37f5a9e2d0aa5360affac64fda32 Mon Sep 17 00:00:00 2001 From: yassun7010 Date: Sat, 22 Jun 2024 18:52:57 +0900 Subject: [PATCH 3/5] feat: update README. --- serde_valid/README.md | 5 ++++- serde_valid/src/lib.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/serde_valid/README.md b/serde_valid/README.md index 79f6b9f..c99bae9 100644 --- a/serde_valid/README.md +++ b/serde_valid/README.md @@ -176,6 +176,10 @@ assert_eq!( You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature. +Allow the following attributes: +- `#[validate(..., message_l10n = fluent("message-id", key1 = value1, ...))]` +- `#[validate(..., fluent("message-id", key1 = value1, ...))]` + ```rust use unic_langid::LanguageIdentifier; use serde_json::json; @@ -185,7 +189,6 @@ use serde_valid::{fluent::Localize, Validate}; #[derive(Validate)] struct Data ( #[validate(min_length = 3, message_l10n = fluent("name-min-length", min_length = 3))] - // Or Just `#[validate(min_length = 3, fluent("name-min-length", min_length = 3))]`. String, ); diff --git a/serde_valid/src/lib.rs b/serde_valid/src/lib.rs index 3832250..f76e177 100644 --- a/serde_valid/src/lib.rs +++ b/serde_valid/src/lib.rs @@ -176,6 +176,10 @@ //! //! You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature. //! +//! Allow the following attributes: +//! - `#[validate(..., message_l10n = fluent("message-id", key1 = value1, ...))]` +//! - `#[validate(..., fluent("message-id", key1 = value1, ...))]` +//! //! ```rust //! # #[cfg(feature = "fluent")] { //! # use fluent::{FluentBundle, FluentResource}; @@ -194,7 +198,6 @@ //! #[derive(Validate)] //! struct Data ( //! #[validate(min_length = 3, message_l10n = fluent("name-min-length", min_length = 3))] -//! // Or Just `#[validate(min_length = 3, fluent("name-min-length", min_length = 3))]`. //! String, //! ); //! From 4391f9b942fd9e2c7f87e665fe67070575d9b187 Mon Sep 17 00:00:00 2001 From: yassun7010 Date: Sat, 22 Jun 2024 18:53:50 +0900 Subject: [PATCH 4/5] docs: update. --- serde_valid/README.md | 2 +- serde_valid/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/serde_valid/README.md b/serde_valid/README.md index c99bae9..8ce785d 100644 --- a/serde_valid/README.md +++ b/serde_valid/README.md @@ -177,8 +177,8 @@ assert_eq!( You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature. Allow the following attributes: +- `#[validate(..., fluent("message-id", key1 = value1, ...))]` - `#[validate(..., message_l10n = fluent("message-id", key1 = value1, ...))]` -- `#[validate(..., fluent("message-id", key1 = value1, ...))]` ```rust use unic_langid::LanguageIdentifier; diff --git a/serde_valid/src/lib.rs b/serde_valid/src/lib.rs index f76e177..1b82b39 100644 --- a/serde_valid/src/lib.rs +++ b/serde_valid/src/lib.rs @@ -177,8 +177,8 @@ //! You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature. //! //! Allow the following attributes: +//! - `#[validate(..., fluent("message-id", key1 = value1, ...))]` //! - `#[validate(..., message_l10n = fluent("message-id", key1 = value1, ...))]` -//! - `#[validate(..., fluent("message-id", key1 = value1, ...))]` //! //! ```rust //! # #[cfg(feature = "fluent")] { From d1741c500898ca665a9b7e2b6fa70b048353e125 Mon Sep 17 00:00:00 2001 From: yassun7010 Date: Sat, 22 Jun 2024 18:54:20 +0900 Subject: [PATCH 5/5] chore: docs. --- serde_valid/README.md | 2 +- serde_valid/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/serde_valid/README.md b/serde_valid/README.md index 8ce785d..e71d215 100644 --- a/serde_valid/README.md +++ b/serde_valid/README.md @@ -188,7 +188,7 @@ use serde_valid::{fluent::Localize, Validate}; #[derive(Validate)] struct Data ( - #[validate(min_length = 3, message_l10n = fluent("name-min-length", min_length = 3))] + #[validate(min_length = 3, fluent("name-min-length", min_length = 3))] String, ); diff --git a/serde_valid/src/lib.rs b/serde_valid/src/lib.rs index 1b82b39..992cf3b 100644 --- a/serde_valid/src/lib.rs +++ b/serde_valid/src/lib.rs @@ -197,7 +197,7 @@ //! //! #[derive(Validate)] //! struct Data ( -//! #[validate(min_length = 3, message_l10n = fluent("name-min-length", min_length = 3))] +//! #[validate(min_length = 3, fluent("name-min-length", min_length = 3))] //! String, //! ); //!