<!--
SPDX-FileCopyrightText: 2025 Slatian

SPDX-License-Identifier: LGPL-3.0-only
-->

# Criterium Serde Integration

Having the ability to dynamically assemble all sorts of comparisons results in the almost natural desire to use that mechanism for cinfiguration.

If the `serde` feature flag is enabled the following types get implementations for the `Serialize` and `Deserialize` traits:
* [CriteriumChain](https://docs.rs/criterium/latest/criterium/chain/enum.CriteriumChain.html)
* [CriteriumChainList](https://docs.rs/criterium/latest/criterium/chain/struct.CriteriumChainList.html)
* [BooleanCriterium](https://docs.rs/criterium/latest/criterium/boolean/enum.BooleanCriterium.html)
* [NumberCriterium](https://docs.rs/criterium/latest/criterium/number/enum.NumberCriterium.html)
* [StringCriterium](https://docs.rs/criterium/latest/criterium/string/enum.StringCriterium.html)

<small>The above links lead to docs.rs, **not your local documentation**.</small>

It is currently **not** implemented for any full-text search related structures in this crate.

The serde implementations are configured to work well for human readable, self describing formats. There is no guarantee that they work with formats that are not self describing (those are usually lightweight binary formats).

## Conventions

Criterium chains and Criteria are mostly represented as enums in rust. This usually translates to some form of map or dictionary with one key indentifying the variant being set to the one value that variant has as child. Variants that don't take any arguments are simply represented by their name as a string (i.e. `"is_none"`, `"always"`, `"never"`)

Variant names should always be in lower `snake_case`. For your own types add `#[serde(rename_all="snake_case")]`.

Vriant names that take a single argument are written in a way that it reads `if <data> to compare <varant_name> <argument>` (i.e. `if number to compare is_less_than_or_equal 42`).

`and` and `or` connections for chains take lists as values. If the list is empty it always falls back to never match, however this **should** be avoided.

Empty lists falling back to a `false` implementation are different from the rust version where the fallback is configurable. The serializer never outputs an empty list, empty lists serialize to a list containing only their fallback value.

The criterium that should be a chain member is untagged, meaning that one doesn't have to explicitly specify switching from chain specifying to criterium specifying.

## Reference

The following lists how the prebuilt types are (de)serialized.

### BooleanCriterium

* `equals`(bool) - Example: `{ "equals": true }`
* `is_none` - Example: `"is_none"`

### NumberCriterium

* `equals`(number) - Example `{ "equals": 0 }`
* `less_than`(number)
* `less_than_or_equal`(number)
* `greater_than`(number)
* `greater_than_or_equal`(number)
* `is_none`

### StringCriterium

* `equals`(string) - Example: `{ "equals": "test" }`
* `has_prefix`(string)
* `has_suffix`(string)
* `contains`(String)
* `is_none`

### CriteriumChain

* `and`[CriteriumChain,…]
* `or`[CriteriumChain,…]
* `not`(CriteriumChain)
* `always` - always matches 
* `never` - never matches
* `not_match`(Criterium) - Inverts a criterium without creating an extra chain element.
* `<other>` - Any other variants are interpreted as described by the criterium

## Examples

Example of a string criterium chain that matches anything beginning with `foo`, but not `foo` itself in json:
```json
{ "and": [
	{ "not": { "equals": "foo" } },
	{ "has_prefix": "foo" }
] }
```

in toml:
```toml
and = [
	{ not.equals = "foo" },
	{ has_prefix = "foo" },
]
```

---

Example of how a an empty `or` criterium with a fallback of true would be serialized:

```json
{ "or": [ "always" ]}
```
---

A theoretical configuration with nested criteria for an Url matcher that us implemented as an enum for matching on different parts of an url could look like this in json:
```json
{ "url": { "path": { "has_prefix": "/some/path/" } } }
```

or in toml:
```toml
url.path.has_prefix = "/some/path/"
```

or if we wanted to test if it had no fragment in toml:
```toml
url.fragment = "is_none"
```

Note that the url examples are not only valid Criteria, but also valid CriteriumChains, they just happen to be composed of one criterium each without any inversion.
