かつて serde_valid なるものを開発していた

# かつて serde_valid なるものを開発してた

結局、 Rust を書かなくなったので止めちゃったけど、
OpenAPI の スキーマ から Rust の型を自動生成するツールを作った過程で、
`serde_valid` という crate も昔作ってた。

---

こんな感じ

```rust
use serde_valid::Validate;

#[derive(Validate)]
struct TestStruct {
    #[validate(length(min_length = 4, max_length = 4))]
    val: String,
}

let s = TestStruct {
    val: String::from("test"),
};

assert!(s.validate().is_ok());

```

---

気づいた人がいるかもしれないが、 [validator](https://github.com/Keats/validator) がOpenAPI の仕様に
上手くマッチしなかったから作ったライブラリだ。

設計はかなり参考にしたが、下記点が異なる

- OpenAPI（がもとにした JsonSchema）のバリデーションに合わせて再編成
- 構造体だけでなく、列挙体にも対応（OpenAPIのバリデーション対応に必要）
- serdeのデシリアライズと同時にバリデーションを実行可能

---

## 基本的な機能
### OpenAPI 準拠のバリデーション
テストケースのコードを見てみると、下記のような定義ができるようだ
（もう覚えていない）。

```rust
#[derive(Debug, Validate)]
struct TestStruct<'a> {
    // Generic validator
    #[validate(enumerate(5, 10, 15))]
    // Numeric validator
    #[validate(multiple_of = 5)]
    #[validate(range(minimum = 5, maximum = 5))]
    #[validate(range(exclusive_minimum = 4, exclusive_maximum = 6))]
    int_value: i32,

    // Generic validator
    #[validate(enumerate(5.0, 10.0, 15.0))]
    // Numeric validator
    #[validate(multiple_of = 5.0)]
    #[validate(range(minimum = 5.0, maximum = 5.0))]
    #[validate(range(exclusive_minimum = 4.0, exclusive_maximum = 6.0))]
    float_value: f32,

    // Generic validator
    #[validate(enumerate("12345", "67890"))]
    // String validator
    #[validate(length(min_length = 5, max_length = 5))]
    #[validate(pattern = r"^\d{5}$")]
    string_value: String,

    // Generic validator
    #[validate(enumerate("12345", "67890"))]
    // String validator
    #[validate(length(min_length = 5, max_length = 5))]
    #[validate(pattern = r"^\d{5}$")]
    str_value: &'a str,

    // Generic validator
    #[validate(enumerate(5, 10, 15))]
    // Numeric validator
    #[validate(multiple_of = 5)]
    #[validate(range(minimum = 5, maximum = 5))]
    optional_value: Option<i32>,

    // Generic validator
    #[validate(enumerate(5, 10, 15))]
    // Array validator
    #[validate(unique_items)]
    #[validate(items(min_items = 3, max_items = 3))]
    // Numeric validator
    #[validate(multiple_of = 5)]
    #[validate(range(minimum = 5, maximum = 15))]
    vec_value: Vec<i32>,

    // Nested validator
    #[validate]
    nested_struct: TestInnerStruct<'a>,

    // Nested vec validator
    #[validate]
    // Array validator
    #[validate(items(min_items = 1, max_items = 1))]
    nested_vec_struct: Vec<TestInnerStruct<'a>>,
}
```

---

余談）下記のテストケースはお気に入り。

```rust
#[test]
fn test_validate_emoji_string_length() {
    assert!(validate_string_length(
        "😍👺🙋🏽👨‍🎤👨‍👩‍👧‍👦",
        Some(5),
        Some(5)
    ));
}
```

ただし、世間一般の文字数の数え方は実装依存なので
`serde_valid` が正しすぎて [良くないこと](https://qiita.com/Takayuki_Nakano/items/9c0cd70912d90cdf8e1a) が起こるかもしれない。

---

### 使い方
基本的には [validator](https://github.com/Keats/validator) と同様だ。
構造体に Derive でバリデーションを定義し、[serde](https://docs.serde.rs/serde/index.html) を用いて変換する。

しかし、 [私の個人的な好み](https://qiita.com/yan7010/items/c02b9c3fd6e92f34c28e) から、データ変換時にバリデーションを実行する
インターフェースが公開されている。

```rust
#[derive(Debug, Validate, Deserialize)]
struct TestStruct{
    ...
}

// 参考にした validator と同じバリデーション方法
let s = serde::from_value<TestStruct>(json!({"val": 1234})).unwrap();
assert!(s.validate().is_ok());

// 新たに追加したバリデーション方法 serde_valid::form_value
let s = serde_valid::from_value::<TestStruct, _>(json!({ "val": 1234 })).unwrap();
```

`serde_valid` だけを含め、 `serde` をプロジェクトから外すことで、
バリデーションの実行を忘れずに開発することが可能だ。
（ `serde::from_value` を呼ぶ方法がなくなり、 `serde_valid::from_value` を強制できるので）  

バリデーションで保証されたデータであることを確信したうえで、プロジェクトの開発ができる。

---

### エラーハンドリング
エラー発生時は Web での使用を想定して、 JSON に変換できる構造体を返すようにしている。


```rust
#[derive(Debug, Validate, Deserialize)]
struct TestStruct {
    #[validate(range(minimum = 0, maximum = 1000))]
    val: i32,
}

// エラーの型に応じた構造体を返す。
let err = serde_valid::from_value::<TestStruct, _>(json!({ "val": 1234 })).unwrap_err();

// エラーは文字列に変換すると JSON フォーマットになるので、 API サーバの戻り値として使える。
assert_eq!(
    serde_json::from_str::<serde_json::Value>(&err.to_string()).unwrap(),
    json!({"val": ["`1234` must be in `0 <= value <= 1000`, but not."]})
);
```

---

少しだけ賢いのは、NewType や構造体・列挙体などの種類に応じて
適切な JSON メッセージを返す点だ。

```rust
#[derive(Validate)]
struct TestStruct {
    #[validate]
    named_fields_struct: StructNamedFields,
    #[validate]
    unnamed_fields_struct: StructUnnamedFields,
    #[validate]
    single_unnamed_fields_struct: StructSingleUnnamedFields,
    #[validate]
    named_fields_enum: EnumNamedFields,
    #[validate]
    unnamed_fields_enum: EnumUnnamedFields,
    #[validate]
    single_unnamed_fields_enum: EnumSingleUnnamedFields,
}

#[derive(Validate)]
struct StructNamedFields {
    #[validate(range(maximum = 0))]
    val: i32,
}

#[derive(Validate)]
struct StructSingleUnnamedFields(#[validate(range(maximum = 0))] i32);

#[derive(Validate)]
struct StructUnnamedFields(
    #[validate(range(maximum = 0))] i32,
    #[validate(range(maximum = 0))] i32,
);

#[derive(Validate)]
enum EnumNamedFields {
    Value {
        #[validate(range(maximum = 0))]
        val: i32,
    },
}

#[derive(Validate)]
enum EnumSingleUnnamedFields {
    Value(#[validate(range(maximum = 0))] i32),
}

#[derive(Validate)]
enum EnumUnnamedFields {
    Value(
        #[validate(range(maximum = 0))] i32,
        #[validate(range(maximum = 0))] i32,
    ),
}

let s = TestStruct {
    named_fields_struct: StructNamedFields { val: 5 },
    unnamed_fields_struct: StructUnnamedFields(5, 5),
    single_unnamed_fields_struct: StructSingleUnnamedFields(5),
    named_fields_enum: EnumNamedFields::Value { val: 5 },
    single_unnamed_fields_enum: EnumSingleUnnamedFields::Value(5),
    unnamed_fields_enum: EnumUnnamedFields::Value(5, 5),
};

assert_eq!(
    serde_json::to_value(&s.validate().unwrap_err()).unwrap(),
    json!({
        "named_fields_struct": [{
            "val": [
                "`5` must be in `value <= 0`, but not."
            ]
        }],
        "unnamed_fields_struct": [{
            "0": [
                "`5` must be in `value <= 0`, but not."
            ],
            "1": [
                "`5` must be in `value <= 0`, but not."
            ]
        }],
        "single_unnamed_fields_struct": ["`5` must be in `value <= 0`, but not."],
        "named_fields_enum": [{
            "val": [
                "`5` must be in `value <= 0`, but not."
            ]
        }],
        "unnamed_fields_enum": [{
            "0": [
                "`5` must be in `value <= 0`, but not."
            ],
            "1": [
                "`5` must be in `value <= 0`, but not."
            ]
        }],
        "single_unnamed_fields_enum": ["`5` must be in `value <= 0`, but not."],
    })
);

```

---

## `serde_valid` の気に入っていないところ
既に十分、公開可能な水準に達している。しかし、いくつかの理由で公開していない。

---

### エラー型をオプションで選べるようになっている
コンパイルオプションで エラー型を `serde_valid` が独自に定義した型ではなく、
`serde::de::Error` を返すことができる。

そうした理由は、既存の大規模システムへの移植を容易にするためだ。

結局、 `serde::de::Error` は [独自のエラーを返すとき文字列として返す](https://docs.serde.rs/serde/de/trait.Error.html#tymethod.custom) ので、
`serde` のエラーメッセージが JSON でデシリアライズできるのなら、 `serde_valid` のエラーと見なせる...
という判断だ。

```rust
#[cfg(not(feature = "serde_error"))]
fn deserialize_with_validation_from_value(self) -> Result<T, crate::Error<Self::Error>> {
    let model: T = serde_json::from_value(self)?;
    model
        .validate()
        .map_err(|err| crate::Error::ValidationError(err))?;
    Ok(model)
}
```

```rust
#[cfg(feature = "serde_error")]
fn deserialize_with_validation_from_value(self) -> Result<T, Self::Error> {
    let model: T = serde_json::from_value(self)?;
    model
        .validate()
        .map_err(|err| serde::de::Error::custom(err))?;
    Ok(model)
}
```

---

...このオプションは、無駄ではないだろうか？  :thinking: 
本当に、JSON形式で返すのは `serde_valid` だけだといえるであろうか？

結局、 `serde_valid` を導入したいなら、エラー部分を書き直してもらう方が正しい気がする。
特にエラー型をコンパイルオプションで切り替えるのは良くない判断だった
（エラーハンドリングの方法すらコンパイル方法によって変わってしまう）。

このオプションは消さなければならない（消すのは簡単）。でもまだやってない。

---

### カスタムエラーで複数のフィールドを参照する方法の改善
参考にした `validator` と同じく、 `serde_valid` も独自のエラーが定義でき、
構造体/列挙体の複数のフィールドの要素を利用して定義できる。

しかし、今はこんな形になっている。

```rust
fn user_validation(
    val1: &Vec<i32>,
    val2: &f32,
    literl: f32,
) -> Result<(), serde_valid::validation::Error> {
    Ok(())
}

#[derive(Validate)]
struct TestStruct {
    // 構造体/列挙体の引数と、リテラル型を指定できる。
    #[validate(custom(user_validation(val2, 1.234)))]
    val1: Vec<i32>,
    val2: f32,
}

let s = TestStruct {
    val1: vec![1, 2, 3, 4],
    val2: 1.234,
};

assert!(s.validate().is_ok());
```

自明なものとして、 `validate` が定義されたフィールドを第1引数として取り、
残りをユーザに指定させている。

しかし、複数のフィールドにまたがるエラーを、なぜフィールドで定義するのだろうか？


---

こうあるべきだ。

```rust
fn user_validation(
    val1: &Vec<i32>,
    val2: &f32,
    literl: f32,
) -> Result<(), serde_valid::validation::Error> {
    Ok(())
}

#[derive(Validate)]
// 構造体/列挙体の引数と、リテラル型を指定できる。
#[validate(custom(user_validation(val1, val2, 1.234)))]
struct TestStruct {
    val1: Vec<i32>,
    val2: f32,
}

let s = TestStruct {
    val1: vec![1, 2, 3, 4],
    val2: 1.234,
};

assert!(s.validate().is_ok());
```

既存のインターフェースの使い方を削除し（簡単）、
構造体のスコープにインターフェースを追加しなければならない（少し大変）。

---

## おまけ

`serde_valid` の計画を考えたころ、 `serde` の制御を奪ってしまおうとした。

しかし、調査の結果、制御を奪うのは大変だと思ったのでラップする方向に転換した。

`serde` は非公式の標準ライブラリになっている感があるが、以下の点が不満だ。
（他の人が不満に思っているのはどこだろう？）

- validation 機能をデフォルトで持っていない
- visitor パターンによる独自型の `Deserialize` `Serialize` の実装が面倒
- ファイルから読み取るとき、エラーと解釈された場所（N行目M文字）を取得できない

`serde` の代わりになるツールを作っても良かったが、
ほとんどのライブラリ設計者は私のトレイトを継承してくれないであろう...

---

## 最後に

結局、担当の製品上、会社で Rust 書かないから Rust 使わなくて、
Rust を使わないから公開する理由がなくて、
公開する理由がないから開発していない。

しかし、誰か欲しい人はいるんじゃあないかなぁ...
と思ったので興味ある人は私に元気をください :bow: