ivo 0.4.0

The rust implementation of the ivo schema valitator
Documentation
<p align="center">
  <img src="https://raw.githubusercontent.com/kamtoeddy/ivo/main/docs/static/img/logo.png" alt="ivo logo" width="120" />
</p>

# Rust Implementation

This is the documentation of the Rust implementation of ivo.

# Installation

```bash
$ cargo add ivo
```

# How to use

ivo expects you to define your data model with structs that implement `IvoInputStruct` (required for input structs) and `IvoStruct` and this can be done via their respective derive macros as shown below.

```rs
use chrono::{DateTime, Utc};
use ivo::{IvoInputStruct, IvoStruct};

#[derive(Clone, PartailEq, IvoInputStruct)]
struct UserInput {
    email: Option<String>,
    phone_number: Option<String>,
    username: String,
}

type Timestamp = DateTime<Utc>;

#[derive(Clone, PartailEq, IvoStruct)]
struct User {
    id: String,
    created_at: Timestamp,
    email: Option<String>,
    phone_number: Option<String>,
    updated_at: Option<Timestamp>,
    username: String,
    username_last_updated_at: Option<Timestamp>,
}
```

## IvoStruct

Deriving `IvoStruct` on **User** generates a struct called **`PartialUser`** together some helper methods for **User** and **PartialUser**.

- **User** gets three helper methods with the following signatures:

  ```rs
  impl IvoStruct for User {
      fn append_updates(&mut self, updates: &Self::Partial);

      // and
      fn clone_with_updates(&self, updates: &Self::Partial) -> Self;
  }

  impl From<User> for PartialUser {
      fn from(value: User) -> PartialUser;
  }
  ```

- **PartialUser** has the signature:

  ```rs
  struct PartialUser {
    id: Option<String>,
    created_at: Option<Timestamp>,
    email: Option<String>,
    phone_number: Option<Option<String>>,
    updated_at: Option<Option<Timestamp>>,
    username: Option<String>,
    username_last_updated_at: Option<Option<Timestamp>>,
  }

  impl PartialUser {
    // the constructor
    fn new() -> Self;

    // you also get two types of builder methods for each field
    fn set_id(&mut self, value: String) -> &mut Self;
    fn with_id(mut self, value: String) -> Self;

    // ... more builder methods for the other fields

    fn set_username_last_updated_at(&mut self, value: Option<Timestamp>) -> &mut Self;
    fn with_username_last_updated_at(mut self, value: Option<Timestamp>) -> Self;

    // you also get a method to unset (or set value to None) for each field
    fn unset_id(&mut self) -> &mut Self;

    // converts PartialUser to Some(Self) if at least one field is_some, otherwise none
    fn into_option(self) -> Option<Self>;

    // returns true if every field in PartialUser is_none, otherwise false
    fn is_empty(&self) -> bool;
  }
  ```

- The `#[ivo(...)]` attribute can be used to customize PartialStructs and their fields.

  ```rs
  #[derive(Clone, PartailEq, IvoInputStruct)]
  #[ivo(derive(Serialize, Deserialize))]
  struct UserInput {
      email: Option<String>,
      #[ivo(serde(skip_serializing_if = "Option::is_none"))]
      phone_number: Option<String>,
      username: String,
  }

  #[derive(Serialize, Deserialize)] // 👈 because it was provided above
  struct PartialUserInput {
      email: Option<Option<String>>,
      #[serde(skip_serializing_if = "Option::is_none")] // 👈 because it was provided above
      phone_number: Option<Option<String>>,
      username: Option<String>,
  }
  ```

## IvoInputStruct

Deriving `IvoInputStruct` on **UserInput** automatically implements `IvoStruct` and generates two structs: **`PartialUserInput`** and **`UserInputErrors`**.

- **UserInputErrors** is used to return errors from post-validators and grouped required resolvers and has the signature:

  ```rs
  struct UserInputErrors {
    email: Option<Option<String>>,
    phone_number: Option<Option<String>>,
    username: Option<String>,
  }

  impl UserInputErrors {
    // the constructor
    fn new() -> Self;

    // you also get two types of builder methods for each field
    fn set_email(&mut self, reason: &str, metadata: Option<IvoErrorSanitizer::Metadata>) -> &mut Self;
    fn with_email(mut self, reason: &str, metadata: Option<IvoErrorSanitizer::Metadata>) -> Self;
    // ... more builder methods for the other fields

    // you also get a method to unset (or set value to None) for each field
    fn unset_email(&mut self) -> &mut Self;

    // converts UserInputErrors to Some(Self) if at least one field is_some, otherwise none
    fn into_option(self) -> Option<Self>;

    // returns true if every field in UserInputErrors is_none, otherwise false
    fn is_empty(&self) -> bool;
  }
  ```

## Docs

[Read the docs](https://ivo.kamtoeddy.com/docs/rs)