# `biome_deserialize`

`biome_deserialize` consists of data structures that know how to deserialize themselves
along with data formats that know how to deserialize data.
It provides a framework by which these two groups interact with each other,
allowing any supported data structure to be deserialized using any supported data format.

This crate inspired by [serde](https://serde.rs/).
0ne of the main difference is the fault-tolerant behavior of `biome_deserialize`.
_Serde_ uses a fast-fail strategy, while `biome_deserialize` deserialize as much as possible
and report several diagnostics (errors, warning, deprecation messages, ...).
Also, `biome_deserialize` is intended to deserialize textual data formats.

`biome_deserialize` assumes that every supported data formats supports the following types:

- null-like values;
- boolean;
- number -- integers and floats;
- string;
- array;
- maps of key-value pairs (covers objects).

It currently supports the JSON data format.

## Design overview

The two most important traits are `Deserializable`, `DeserializableValue`.

- A type that implements `Deserializable` is a data structure that can be
  deserialized from any supported data format;
- A type that implements `DeserializableValue` is a data format that can
  deserialize any supported data structure.

Simple implementations of `Deserializable` can reuse other deserializable data structures.
For instance, an enumeration that corresponds to a string among A, B, and C, can first deserialize a string and then check that the string is one of its values.

Data structures that cannot directly use another deserializable data structures, use a visitor.
A visitor is generally a zero-sized data structure that implements the `DeserializationVisitor` trait.
A [visitor](https://en.wikipedia.org/wiki/Visitor_pattern) is a well-known design pattern.
It allows selecting an implementation based on the deserialized type without bothering of data format details.

## Usage examples

### Deserializing common types

`biome_deserialize` implements `Deserializable` for common Rust data structure.

In the following example, we deserialize a boolean, an array of integers, and an unordered map of string-integer pairs.

```rust
use biome_deserialize::json::deserialize_from_json_str;
use biome_deserialize::Deserialized;
use biome_json_parser::JsonParserOptions;

let json = "false";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<bool>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(false));
assert!(diagnostics.is_empty());

let json = "[0, 1]";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Vec<u8>>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(vec![0, 1]));
assert!(diagnostics.is_empty());

use std::collections::HashMap;
let json = r#"{ "a": 0, "b": 1 }"#;
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<HashMap<String, u8>>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(HashMap::from([("a".to_string(), 0), ("b".to_string(), 1)])));
assert!(diagnostics.is_empty());
```

### Deserializable derive macro

For more complex types, such as structs and enums, `biome_deserialize_macros` offers a derive macro
which will generate an implementation of the `Deserializable` trait for you.

For example:

```rs
#[derive(Clone, Debug, Default, Deserializable)]
pub struct MyRuleOptions {
    behavior: Behavior,
    threshold: u8,
    behavior_exceptions: Vec<String>
}
```

Extensive documentation for the macro is also available if you hover over it in your IDE.

Note that the macro has two main limitations:

- The current implementation only supports enums where none of the variants have custom fields,
  structs with named fields, and newtypes.
- Every field must also implement `Deserializable`.

If you want to implement `Deserializable` for a new primitive type, or a complex type that falls
outside the limitations above, you'll need to implement it manually. See below for the section about
[Implementing a custom deserializer](#implementing-a-custom-deserializer).

## Deserializing map and array collections

`biome_deserialize` requires that every data format supports arrays and maps.
A map and an array can be deserialized into several types in Rust.

`biome_deserialize` is able to deserialize an array into a `Vec`, a `HashSet`, or a `IndexSet`.

- `Vec` preserves the insertion order and allows the repetition of values;
- `HashSet` **doesn't preserve the insertion order** and disallows the repetition of values;
- `IndexSet` preserves the insertion order and disallows the repetition of values.

`biome_deserialize` is able to deserialize a map into a `HashMap`, a `BTreeMap`, or a `IndexMap`.

- `HashMap` and `BTreeMap` **don't preserve the insertion order**;
- `IndexMap` preserves the insertion order.

If you hesitate between a collection that preserves the insertion order and one that doesn't,
chooses the collection that preserves the insertion order.
This often outputs less surprising behavior.

## Implementing a custom deserializer

For most enums and structs, an implementation can be generated by the `Deserializable` macro.
However, for primitives, and certain complex types, you may need to implement
`biome_deserialize::Deserializable` yourself.

> [!NOTE]
> If you are curious about the code generated by the macro, *Rust Analyzer* offers a great feature
> from the command palette called "Expand macro recursively at caret". Just put your cursor on the
> word `Deserializable` in the `#[derive(...)]` statement and invoke the command. A panel should
> open with the expanded macro code.

We provide a few examples for custom implementations below.

### Custom integer range

Sometimes you want to deserialize an integer and ensure that it is between two given integers.

For instance, let's assume we want to deserialize a day represented by an integer between 1 and 365.
We can use the [new-type idiom](https://doc.rust-lang.org/rust-by-example/generics/new_types.html) in Rust:

```rust
use std::str::FromStr;
use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, TextNumber};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Day(u16);

impl Day {
    pub const MIN: Day = Day(1);
    pub const MAX: Day = Day(365);
    pub fn get(&self) -> u16 {
        self.0
    }
}

impl Default for Day {
    fn default() -> Self {
        Self::MIN
    }
}

impl TryFrom<u16> for Day {
    type Error = &'static str;
    fn try_from(value: u16) -> Result<Self, Self::Error> {
        if (Self::MIN.get()..=Self::MAX.get()).contains(&value) {
            Ok(Self(value))
        } else {
            Err("A day must be between 1 and 365")
        }
    }
}

impl FromStr for Day {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse::<u16>()
            .map_err(|_error| "A day must be an integer between 1 and 365")
            .and_then(|value| Day::try_from(value))
    }
}

impl Deserializable for Day {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        // We deserialize the value into a number represented as a string.
        let value_text = TextNumber::deserialize(value, name, diagnostics)?;
        // We attempt to convert the string into a `Day`.
        value_text.parse::<Day>().map_err(|error| {
            // If the conversion failed, then we report the error.
            diagnostics.push(DeserializationDiagnostic::new(error).with_range(value.range()));
        }).ok()
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_deserialize::Deserialized;
use biome_json_parser::JsonParserOptions;

let json = "42";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Day>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(Day(42)));
assert!(diagnostics.is_empty());

let json = "999";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Day>(&source, JsonParserOptions::default());
assert_eq!(deserialized, None);
assert_eq!(diagnostics..len(), 1);
```

### Deserializing a union

Sometimes we want to allow several types for a single value.
For instance let's assume that we cant to accept a JSON value that is either a string or a boolean.

In this case, we'll need to inspect the type of the `DeserializableValue` to know which deserializer
to use:

```rust
use biome_deserialize::{DeserializationDiagnostic, Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
use biome_rowan::TextRange;

#[derive(Debug, Eq, PartialEq)]
enum Union {
    Bool(bool),
    Str(String),
}

impl Deserializable for Union {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        if value.is_type(VisitableType::BOOL) {
            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
                .map(Self::Bool)
        } else {
            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
                .map(Self::Str)
        }
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_json_parser::JsonParserOptions;

let source = r#" "string" "#;
let deserialized = deserialize_from_json_str::<Union>(&source, JsonParserOptions::default());
assert!(!deserialized.has_errors());
assert_eq!(deserialized.into_deserialized(), Some(Union::Str("string".to_string())));

let source = "true";
let deserialized = deserialize_from_json_str::<Union>(&source, JsonParserOptions::default());
assert!(!deserialized.has_errors());
assert_eq!(deserialized.into_deserialized(), Some(Union::Bool(true)));
```

Alternatively, we could implement the above using a custom visitor. Note that this approach is much
more involved and should only be used as a last resort. Here, we will reimplement `Deserializable`
for our `Union` type with a visitor for demonstration purposes.

To create your own visitor, start with a struct named after your type with a `Visitor` suffix. We'll
call ours `UnionVisitor`.

The visitor struct doesn't need any fields, but we do need to implement `DeserializationVisitor` on
it. A `DeserializationVisitor` provides several `visit_` methods and you must implement the `visit_`
methods of the type(s) you expect. Here we expect either a boolean or a string, so we'll implement
`visit_bool()` and `visit_str()`.

We also have to set the associated type `Output` to be a union of the types we expect:
`VisitableType::BOOL.union(VisitableType::STR)`.

The full example:

```rust
impl Deserializable for Union {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        // Delegate deserialization to `UnionVisitor`
        value.deserialize(UnionVisitor, name, diagnostics)
    }
}

struct UnionVisitor;
impl DeserializationVisitor for UnionVisitor {
    type Output = Union;

    // We expect a `bool` or a `str` as data type.
    const EXPECTED_TYPE: VisitableType = VisitableType::BOOL.union(VisitableType::STR);

    // Because we expect a `bool` or a `str`, we have to implement the associated method `visit_bool`.
    fn visit_bool(
        self,
        value: bool,
        range: TextRange,
        _name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self::Output> {
        Some(Union::Bool(value))
    }

    // Because we expect a `bool` or a `str`, we have to implement the associated method `visit_str`.
    fn visit_str(
        self,
        value: Text,
        range: TextRange,
        _name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self::Output> {
        Some(Union::Str(value.text().to_string()))
    }
}
```

### Implementing `Deserializable` for an enumeration of values

Let's assume that we want to deserialize a JSON string that is either `A`, or `B`.
We represent this string by a Rust's `enum`:

To implement `Deserializable` for `Variant`, we can reuse the `String`,
because `biome_deserialize` implements `Deserializable` for the `String` type.

Our implementation attempts to deserialize a string and creates the corresponding variant.
If the variant is not known, we report a diagnostic.

```rust
use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Variant { A, B }

impl Deserializable for Variant {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        match String::deserialize(value, name, diagnostics)? {
            "A" => Some(Variant::A),
            "B" => Some(Variant::B),
            unknown_variant => {
                const ALLOWED_VARIANTS: &[&str] = &["A", "B"];
                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
                    unknown_variant,
                    value.range(),
                    ALLOWED_VARIANTS,
                ));
                None
            }
        }
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_deserialize::Deserialized;
use biome_json_parser::JsonParserOptions;

let json = "\"A\"";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Day>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(Variant::A));
assert!(diagnostics.is_empty());
```

We can improve our implementation by avoiding the heap-allocation of a string.
To do this, we use `Text` instead of `String`.
Internally `Text` borrows a slice of the source.

```rust
use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, Text};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Variant { A, B }

impl Deserializable for Variant {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        match Text::deserialize(value, name, diagnostics)?.text() {
            "A" => Some(Variant::A),
            "B" => Some(Variant::B),
            unknown_variant => {
                const ALLOWED_VARIANTS: &[&str] = &["A", "B"];
                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
                    unknown_variant,
                    value.range(),
                    ALLOWED_VARIANTS,
                ));
                None
            }
        }
    }
}
```

### Implementing `Deserializable` for a struct

Let's assume we want to deserialize a _JSON_ map (object) into an instance of a Rust `struct`.

Because a `struct` has custom fields and types, we cannot reuse an existing deserializable type.
We have to delegate the deserialization to a visitor.

To do that, we create a zero-sized struct `PersonViistor` that implements `DeserializationVisitor`.
A `DeserializationVisitor` provides several `visit_` methods.
You must implement the `visit_` methods of the type you expect.
Here we expect a map of key-value pairs.
Thus, we implement `visit_map` and set the associated constant `EXPECTED_TYPE` to `VisitableType::MAP`.
We also set the associated type `Output` to the type that we want to produce: `Person`.

The implementation of `Deserialziable` for `Person` simply delegates the deserialization of the visitor.
Internally, the deserialization of `value` calls the `visit_` method that corresponds to its type.
If the value is a map of key-value pairs, then `visit_map` is called.
Otherwise, another `visit_` method is called.
The default implementation of a `visit_` method reports an incorrect type diagnostic.

Our implementation of `visit_map` traverses the map of key-value pairs.
It attempts to deserialize every key as a string and deserialize the value according the key's content.
If the key is `name`, then we deserialize a `String`.
If the key is `age`, then we deserialize a `u8`.
`String` and `u8` implements `Deserializable`.
Thus, we can reuse `String::deserialize` and `u8::deserialize`.

Note that if you use _Serde_ in tandem with `biome_deserialize`, you have to disambiguate the call to `deserialize`.
Thus, instead of using `String::deserialize` and `u8::deserialize`, you should use `Deserialize::deserialize`.

```rust
use biome_deserialize::{DeserializationDiagnostic, Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
use biome_rowan::TextRange;

#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct Person { name: String, age: u8 }

impl Deserializable for Person {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        // Delegate the deserialization to `PersonVisitor`.
        // `value` will call the `PersonVisitor::viist_` method that corresponds to its type.
        value.deserialize(PersonVisitor, name, diagnostics)
    }
}

struct PersonVisitor;
impl DeserializationVisitor for PersonVisitor {
    // The visitor deserialize a [Person].
    type Output = Person;

    // We expect a `map` as data type.
    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;

    // Because we expect a `map`, we have to implement the associated method `visit_map`.
    fn visit_map(
        self,
        // Iterator of key-value pairs.
        members: impl Iterator<Item = Option<(impl DeserializableValue, impl DeserializableValue)>>,
        // range of the map in the source text.
        range: TextRange,
        _name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self::Output> {
        let mut result = Person::default();
        for (key, value) in members.flatten() {
            // Try to deserialize the key as a string.
            // We use `Text` to avoid an heap-allocation.
            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
                // If this failed, then pass to the next key-value pair.
                continue;
            };
            match key_text.text() {
                "name" => {
                    if let Some(name) = String::deserialize(&value, &key_text, diagnostics) {
                        result.name = name;
                    }
                },
                "age" => {
                    if let Some(age) = u8::deserialize(&value, &key_text, diagnostics) {
                        result.age = age;
                    }
                },
                unknown_key => {
                    const ALLOWED_KEYS: &[&str] = &["name"];
                    diagnostics.push(DeserializationDiagnostic::new_unknown_key(
                        unknown_key,
                        key.range(),
                        ALLOWED_KEYS,
                    ));
                }
            }
        }
        Some(result)
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_json_parser::JsonParserOptions;

let source = r#"{ "name": "Isaac Asimov" }"#;
let deserialized = deserialize_from_json_str::<Person>(&source, JsonParserOptions::default());
assert!(!deserialized.has_errors());
assert_eq!(deserialized.into_deserialized(), Some(Person { name: "Isaac Asimov".to_string() }));
```
