Introduction
プログラムを書くときに必ずといっていいほど実装する
処理の1つにバリデーションがあります。
バリデーションは入力データが特定の条件を満たしているかどうか
チェックするプロセスです。
この処理はソフトウェアの安全性を保つために非常に重要です。
本稿ではRustのValidationライブラリである
garde(axumとも統合されてるヤツ)を使ってValidationを実装してみます。
Environment
- Rust : 1.70.0
Setup
CargoでRustプロジェクトを作成し、
gardeクレートをインストールします。
% cargo new garde_example && cd garde_example
% cargo add garde
Try
gardeでは構造体やEnumにattributeを付与することで
Validationルールを定義します。
基本的な使い方
garde::Validateを使用して、構造体にValidateルールを追加します。
#[derive(Validate)]
struct User<'a> {
#[garde(required)]
id: Option<String>,
#[garde(ascii, length(min=3, max=25))]
name: &'a str,
#[garde(length(min=20))]
pass: &'a str,
#[garde(email)]
mail: &'a str,
#[garde(skip)]
memo:&'a str,
}
User型変数を使ってみます。
validate関数を実行することでValidation処理が行われます。
let user = User {
id:Some("ID0001".to_string()),
name: "a",
pass: "mypassword",
mail:"hoge",
foo:"hello"
};
if let Err(e) = user.validate(&()) {
println!("invalid user:\n{e}");
}
mailやlengthのチェックがちゃんとされてます。
% cargo run
invalid user:
value.mail: not a valid email: value is missing `@`
value.name: length is lower than 3
value.pass: length is lower than 20
なお、構造体だけでなくEnumを対象にすると下記のようになります。
#[derive(Validate)]
enum Data {
Struct {
#[garde(range(min=-1, max=10))]
field: i32,
},
Tuple(
#[garde(ascii)]
String
),
}
・
・
・
let data = Data::Struct { field: 100 };
if let Err(e) = data.validate(&()) {
println!("invalid data: {e}");
}
なお、Validateionルールにrequieredが付与していた場合、
それ以外のルールはSome型だった場合に検証されます。
gardeが標準で用意している検証ルールは下記になります。
name | format | validation | feature flag |
---|---|---|---|
required | #[garde(required)] | is value set | - |
ascii | #[garde(ascii)] | only contains ASCII | - |
alphanumeric | #[garde(alphanumeric)] | only letters and digits | - |
#[garde(email)] | an email according to the HTML5 spec1 | ||
url | #[garde(url)] | a URL | url |
ip | #[garde(ip)] | an IP address (either IPv4 or IPv6) | - |
ipv4 | #[garde(ipv4)] | an IPv4 address | - |
ipv6 | #[garde(ipv6)] | an IPv6 address | - |
credit card | #[garde(credit_card)] | a credit card number | credit-card |
phone number | #[garde(phone_number)] | a phone number | phone-number |
length | #[garde(length(min=<usize>, max=<usize>))] | a container with length in min..=max | - |
byte_length | #[garde(byte_length(min=<usize>, max=<usize>))] | a byte sequence with length in min..=max | - |
range | #[garde(range(min=<expr>, max=<expr>))] | a number in the range min..=max | - |
contains | #[garde(contains(<string>))] | a string-like value containing a substring | - |
prefix | #[garde(prefix(<string>))] | a string-like value prefixed by some string | - |
suffix | #[garde(suffix(<string>))] | a string-like value suffixed by some string | - |
pattern | #[garde(pattern("<regex>"))] | a string-like value matching some regex | regex |
pattern | #[garde(pattern(<matcher>))] | a string-like value matched by some Matcher | - |
dive | #[garde(dive)] | nested validation, calls validate on the value | - |
skip | #[garde(skip)] | skip validation | - |
custom | #[garde(custom(<function or closure>))] | a custom validator | - |
カスタムバリデーション
バリデーションは自由にカスタマイズすることができます。
指定したフィールドも文字列が任意のVec<String>に含まれるか
チェックするValidationを定義してみます。
#[derive(garde::Validate)]
#[garde(context(MapContext))]
struct MyLang {
#[garde(custom(is_langs))]
lang: String,
}
struct MapContext {
langs: Vec<String>,
}
fn is_langs(value: &str, context: &MapContext) -> garde::Result {
if context.langs.contains(&value.to_string()) {
println!("The value is present in the Vec!");
} else {
println!("The value is not present in the Vec.");
return Err(garde::Error::new(format!("{} {}", value, "is not present in the Vec.")));
}
Ok(())
}
validateにContextを渡し、自作のValidateion関数(is_langs)でチェックします。
let ctx = MapContext {
langs: vec![
"rust".to_string(),
"java".to_string(),
"javascript".to_string(),
],
};
let mylang = MyLang {
lang : "java".to_string()
};
if let Err(e) = mylang.validate(&ctx){
println!("invalid data: {e}");
}
Implementing Validate
ネストされたバリデーションをサポートしたいコンテナ型がある場合、
#[garde(dive)]を使ってValidationを実装できます。
#[repr(transparent)]
#[derive(Debug)]
struct MyVec<T>(Vec<T>);
impl<T: garde::Validate + std::fmt::Debug> garde::Validate for MyVec<T> {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), garde::Errors> {
garde::Errors::list(|errors| {
for item in self.0.iter() {
errors.push(item.validate(ctx));
}
})
.finish()
}
}
#[derive(Debug,garde::Validate)]
struct Foo {
#[garde(dive)]
field: MyVec<Bar>,
}
#[derive(Debug,garde::Validate)]
struct Bar {
#[garde(range(min = 1, max = 10))]
value: u32,
}
Summary
今回はRustのValidateionライブラリgardeを使ってみました。
シンプルでカスタマイズも簡単なのですぐに使えると思います。