Skip to content

Commit 213f75a

Browse files
authored
Merge pull request #48 from yassun7010/add_fluent_doc
Add fluent doc
2 parents 0007998 + 8b612e1 commit 213f75a

File tree

5 files changed

+137
-66
lines changed

5 files changed

+137
-66
lines changed
Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,3 @@
1-
use std::{
2-
any::{type_name, TypeId},
3-
cell::RefCell,
4-
collections::{HashMap, VecDeque},
5-
};
1+
mod context;
62

7-
use jsonschema::{
8-
output::{BasicOutput, ErrorDescription, OutputUnit},
9-
JSONSchema,
10-
};
11-
use schemars::gen::{SchemaGenerator, SchemaSettings};
12-
use serde_json::{Map, Value};
13-
14-
thread_local! {
15-
static CONTEXT: RefCell<SchemaContext> = RefCell::new(SchemaContext::new());
16-
}
17-
18-
pub(crate) struct SchemaContext {
19-
pub generator: SchemaGenerator,
20-
pub schemas: HashMap<TypeId, JSONSchema>,
21-
}
22-
23-
impl SchemaContext {
24-
pub fn new() -> Self {
25-
Self {
26-
generator: SchemaSettings::draft07()
27-
.with(|settings| settings.inline_subschemas = true)
28-
.into_generator(),
29-
schemas: HashMap::default(),
30-
}
31-
}
32-
33-
pub fn validate<T>(value: &Value) -> Result<(), VecDeque<OutputUnit<ErrorDescription>>>
34-
where
35-
T: crate::traits::validated::Deserialize + schemars::JsonSchema + 'static,
36-
{
37-
CONTEXT.with(|ctx| {
38-
let ctx = &mut *ctx.borrow_mut();
39-
let schema = ctx.schemas.entry(TypeId::of::<T>()).or_insert_with(|| {
40-
match jsonschema::JSONSchema::compile(
41-
&serde_json::to_value(ctx.generator.root_schema_for::<T>()).unwrap(),
42-
) {
43-
Ok(s) => s,
44-
Err(error) => {
45-
tracing::error!(
46-
%error,
47-
type_name = type_name::<T>(),
48-
"invalid JSON schema for type"
49-
);
50-
JSONSchema::compile(&Value::Object(Map::default())).unwrap()
51-
}
52-
}
53-
});
54-
55-
match schema.apply(value).basic() {
56-
BasicOutput::Valid(_) => Ok(()),
57-
BasicOutput::Invalid(v) => Err(v),
58-
}
59-
})
60-
}
61-
}
3+
pub(crate) use context::SchemaContext;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use std::{
2+
any::{type_name, TypeId},
3+
cell::RefCell,
4+
collections::{HashMap, VecDeque},
5+
};
6+
7+
use jsonschema::{
8+
output::{BasicOutput, ErrorDescription, OutputUnit},
9+
JSONSchema,
10+
};
11+
use schemars::gen::{SchemaGenerator, SchemaSettings};
12+
use serde_json::{Map, Value};
13+
14+
thread_local! {
15+
static CONTEXT: RefCell<SchemaContext> = RefCell::new(SchemaContext::new());
16+
}
17+
18+
pub(crate) struct SchemaContext {
19+
pub generator: SchemaGenerator,
20+
pub schemas: HashMap<TypeId, JSONSchema>,
21+
}
22+
23+
impl SchemaContext {
24+
pub fn new() -> Self {
25+
Self {
26+
generator: SchemaSettings::draft07()
27+
.with(|settings| settings.inline_subschemas = true)
28+
.into_generator(),
29+
schemas: HashMap::default(),
30+
}
31+
}
32+
33+
pub fn validate<T>(value: &Value) -> Result<(), VecDeque<OutputUnit<ErrorDescription>>>
34+
where
35+
T: crate::traits::validated::Deserialize + schemars::JsonSchema + 'static,
36+
{
37+
CONTEXT.with(|ctx| {
38+
let ctx = &mut *ctx.borrow_mut();
39+
let schema = ctx.schemas.entry(TypeId::of::<T>()).or_insert_with(|| {
40+
match jsonschema::JSONSchema::compile(
41+
&serde_json::to_value(ctx.generator.root_schema_for::<T>()).unwrap(),
42+
) {
43+
Ok(s) => s,
44+
Err(error) => {
45+
tracing::error!(
46+
%error,
47+
type_name = type_name::<T>(),
48+
"invalid JSON schema for type"
49+
);
50+
JSONSchema::compile(&Value::Object(Map::default())).unwrap()
51+
}
52+
}
53+
});
54+
55+
match schema.apply(value).basic() {
56+
BasicOutput::Valid(_) => Ok(()),
57+
BasicOutput::Invalid(v) => Err(v),
58+
}
59+
})
60+
}
61+
}

serde_valid/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,35 @@ assert_eq!(
158158
);
159159
```
160160

161+
### Fluent localization
162+
163+
You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature.
164+
165+
```rust
166+
use unic_langid::LanguageIdentifier;
167+
use serde_json::json;
168+
use serde_valid::{fluent::Localize, Validate};
169+
170+
#
171+
172+
#[derive(Validate)]
173+
struct Data (
174+
#[validate(min_length = 3, fluent("name-min-length", min_length = 3))]
175+
String,
176+
);
177+
178+
assert_eq!(
179+
Data("田中".to_string()).validate()
180+
.unwrap_err()
181+
.localize(&get_bundle("name-min-length = 名前の長さは { $min_length } 文字以上でないといけません。"))
182+
.to_string(),
183+
json!({
184+
"errors": ["名前の長さは \u{2068}3\u{2069} 文字以上でないといけません。"]
185+
})
186+
.to_string()
187+
);
188+
```
189+
161190
## Custom method
162191

163192
You can use your custom validation using by `#[validate(custom)]`.

serde_valid/src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,45 @@
158158
//! );
159159
//! ```
160160
//!
161+
//! ### Fluent localization
162+
//!
163+
//! You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature.
164+
//!
165+
//! ```rust
166+
//! # #[cfg(feature = "fluent")] {
167+
//! # use fluent::{FluentBundle, FluentResource};
168+
//! use unic_langid::LanguageIdentifier;
169+
//! use serde_json::json;
170+
//! use serde_valid::{fluent::Localize, Validate};
171+
//!
172+
//! # fn get_bundle(source: impl Into<String>) -> FluentBundle<FluentResource> {
173+
//! # let res = FluentResource::try_new(source.into()).expect("Failed to parse an FTL string.");
174+
//! # let langid_en: LanguageIdentifier = "ja_JP".parse().expect("Parsing failed");
175+
//! # let mut bundle = FluentBundle::new(vec![langid_en]);
176+
//! # bundle.add_resource(res).unwrap();
177+
//! # bundle
178+
//! # }
179+
//! #
180+
//!
181+
//! #[derive(Validate)]
182+
//! struct Data (
183+
//! #[validate(min_length = 3, fluent("name-min-length", min_length = 3))]
184+
//! String,
185+
//! );
186+
//!
187+
//! assert_eq!(
188+
//! Data("田中".to_string()).validate()
189+
//! .unwrap_err()
190+
//! .localize(&get_bundle("name-min-length = 名前の長さは { $min_length } 文字以上でないといけません。"))
191+
//! .to_string(),
192+
//! json!({
193+
//! "errors": ["名前の長さは \u{2068}3\u{2069} 文字以上でないといけません。"]
194+
//! })
195+
//! .to_string()
196+
//! );
197+
//! # }
198+
//! ```
199+
//!
161200
//! ## Custom method
162201
//!
163202
//! You can use your custom validation using by `#[validate(custom)]`.

serde_valid/tests/fluent_test.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ mod tests {
66
use serde_valid::{fluent::Localize, Validate};
77
use unic_langid::LanguageIdentifier;
88

9-
fn get_bundle() -> FluentBundle<FluentResource> {
10-
let ftl_string = ["hello-world = Hello, world!", "intro = Welcome, { $name }."]
11-
.join("\n")
12-
.to_string();
13-
let res = FluentResource::try_new(ftl_string).expect("Failed to parse an FTL string.");
9+
fn get_bundle(source: impl Into<String>) -> FluentBundle<FluentResource> {
10+
let res = FluentResource::try_new(source.into()).expect("Failed to parse an FTL string.");
1411

1512
let langid_en: LanguageIdentifier = "en-US".parse().expect("Parsing failed");
1613
let mut bundle = FluentBundle::new(vec![langid_en]);
@@ -30,7 +27,10 @@ mod tests {
3027
}
3128

3229
let test = Test { a: 1, b: 11 };
33-
let a = test.validate().unwrap_err().localize(&get_bundle());
30+
let a = test.validate().unwrap_err().localize(&get_bundle(
31+
["hello-world = Hello, world!", "intro = Welcome, { $name }."].join("\n"),
32+
));
33+
3434
assert_eq!(
3535
a.to_string(),
3636
json!({

0 commit comments

Comments
 (0)