An open API service indexing awesome lists of open source software.

https://github.com/vic1707/nnn


https://github.com/vic1707/nnn

Last synced: 3 months ago
JSON representation

Awesome Lists containing this project

README

          

# nnn

[github](https://github.com/vic1707/nnn)
[crates.io](https://crates.io/crates/nnn)
[docs.rs](https://docs.rs/nnn)
[lines](https://github.com/vic1707/nnn)
[maintenance](https://github.com/vic1707/nnn)

### nnn Crate Documentation

The `nnn` crate provides a procedural macro to help create [`newtype`](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)s with validation and sanitization based on a specified set of rules. Its design focuses on being as slim and non-intrusive as possible.

#### Philosophy

The primary goal of `nnn` is to provide tools, not guardrails, the only errors returned by `nnn` are parsing/syntax errors and footguns.
(e.g., `nnn` allows using a `finite` validator on a `String`—though this will not compile because `String` lacks `.is_finite()`. The same applies to the `each` validator and sanitizer, which are only available on inner with `.iter()`.)

By design, `nnn` doesn’t “hold hands” or attempt to protect users from all possible mistakes. Instead, it prioritizes flexibility and assumes user expertise.

---

#### Inspirations

The `nnn` crate draws heavy inspiration from the excellent [`nutype`](https://docs.rs/nutype/), borrowing much of its syntax and approach. While `nutype` offers robust features, its internal complexity motivated this project.

This crate was developed as a fun, 3-week challenge to explore whether `nutype`'s functionality could be reimagined in a simpler form. The result is `nnn`, which aims to provide a more streamlined experience without sacrificing power.

---

#### Complete example

```rs
use nnn::nnn;

#[nnn(
derive(Debug, PartialEq, Eq, PartialOrd, Ord),
nnn_derive(TryFrom),
consts(
ZERO = 0.0_f64,
pub ONE = 1.0_f64,
),
default = 5.0_f64,
validators(finite, positive),
sanitizers(custom = |v: f64| v.abs()),
// Serialize & Deserialize are only available in test env.
cfg(test, derive(Serialize, Deserialize)),
attrs(
repr(transparent),
),
)]
struct PositiveFiniteFloat(f64);
```

I encourage you to see what code is generated using [`cargo-expand`](https://github.com/dtolnay/cargo-expand).

#### Usage

Every argument in a `nnn` declaration must be provided in a single macro invocation, so

```rs
#[nnn(derive(Debug))]
#[nnn(default = 5.0_f64)]
struct Dummy(f64);
```

is invalid.

Note that `derive` and other attributes must be passed via `nnn` to make it clear that `nnn` manages them.

##### Arguments

Below is a complete list of supported arguments for the `nnn` macro:

- **`cfg(, )`**: Adds conditional compilation to the provided arguments.

- **`consts(pub EIGHT = 8, pub(in crate) NINE = 9, ...)`**: Defines associated constants for the newtype.
_Note:_ `nnn` will generate unit tests ensuring these values are correct.

- **`derive(, ...)`**: Specifies standard Rust derives for the newtype.

- **`nnn_derive(, ...)`**: Declares specific derives re-implemented by `nnn` that ensure validation and sanitization are applied where appropriate.

- **`default` or `default = ...`**: Defines a default value for the newtype. Can be either:

- `#[nnn(default)]`: Uses the inner type's default value.
- `#[nnn(default = ...)]`: Specifies a custom default value.

_Note:_ `nnn` will generate a unit test ensuring the default value is correct.

- **`new_unchecked`**: Enables the generation of the unsafe method `const fn new_unchecked(v: ) -> Self` that bypasses validation and sanitization.

- **`sanitizers()`**: Declares a list of sanitizers to apply to the input.
_Note:_ Sanitization is executed **before** validation.

- **`validators()`**: Declares a list of validators to ensure the input meets the desired conditions.

- **`attrs()`**: Specifies additional attributes for the newtype, such as `#[repr(C)]` or `#[non_exhaustive]`.

##### Derive Handling

While most derives are passed through transparently, there are exceptions:

1. **`Eq` & `Ord`**

Automatically implemented except when the `finite` or `not_nan` validator is provided, in which case a manual implementation is generated.

2. **`Deserialize`**

Passed transparently, with `nnn` injecting `#[serde(try_from = "")]` to ensure validation and sanitization during deserialization.

**Note:** Some derives are disallowed as they bypass validation. For these cases, `nnn` provides a custom `nnn_derive` to replace standard derives while ensuring validation and sanitization are preserved.

##### Custom derives

`nnn` provides custom implementations of some common derives to implement `nnn`'s guarantees.

1. **`Into`/`From`/`Borrow`/`AsRef`/`Deref`**

These derives their respective traits to convert from a new-type to its inner type.
These derives can take generic inputs as parameters, `#[nnn_derive(Into)]` will generate derives for `Into`/`Into`/`Into` for the new_type. `#[nnn_derive(Into)]` still defaults to deriving `Into`.

_Note:_ `_`as a generic parameter will be translated to``, e.g:

```rs
#[nnn_derive(From<_>)]
struct A(i8);
```

will implement `From` for the new-type.

2. **`TryFrom`**

Implements `TryFrom` and calls the `try_new` methods.
`TryFrom` can take generic parameters as parameters, `#[nnn_derive(TryFrom)]` will generate derives for `TryFrom`/`TryFrom`/`TryFrom`. `#[nnn_derive(TryFrom)]` still defaults to deriving `TryFrom`.

_Note:_ `_` as a generic parameter will be translated to ``, e.g:

```rs
#[nnn_derive(TryFrom<_>)]
struct A(i8);
```

will implement `TryFrom` for the new-type.

3. **`FromStr`**

Generates an implementation using the inner `FromStr` implementation and passing parsed value through sanitization and validation.
It generates the following error enum implementing `Debug`, `Clone`, `PartialEq`, `Eq` and `Display`.

```rs
enum /* new_type's name */ParseError {
InnerParse(* inner_type */ as ::core::str::FromStr>::Err),
Validation(/* new_type's name */Error),
}
```

4. `IntoIterator`

Implements `IntoIterator` for the new-type and a reference to it, the iterator yields items from the inner type's iterator implementation.

---

#### Sanitizers

_To see examples, each sanitizer is tested in the [test folder](./tests/sanitizers)._

The `sanitizers` argument accepts the following options:

- **Iterables**:

- `each(...)`: Applies sanitizers to each element in a collection.
- `sort`: Sorts the elements in the collection.
- `dedup`: Removes duplicate elements from the collection using rust's `dedup` which only removes consecutive duplicates.

- **Strings**:

- `trim`: Removes leading and trailing whitespace.
- `lowercase`: Converts the string to lowercase.
- `uppercase`: Converts the string to uppercase.

- **Common**:
- `custom = ...`: Specifies a custom sanitization function.

---

#### Validators

_To see examples, each validator is tested in the [test folder](./tests/validators)._

Each validator generates a specific variant for the corresponding error type which has the same visibility as the `new_type`.
This error enum implements the traits: `Debug`, `Error`, `Display`, `Clone`, `PartialEq`, `Eq`.

```rs
enum /* new_type's name */Error {
Positive,
Finite,
/// idx of the problematic element, inner error
Each(usize, Box),
/* ... */
}
```

The `validators` argument accepts the following options:

- **Iterables**:

- `not_empty`: Ensures an iterable is not empty.
- `each(...)`: Applies validators to each element in an iterable.
- `min_length = ...`: Ensures the iterable has a minimum length.
- `min_length_or_eq = ...`: Ensures the iterable has a minimum length or is equal to the specified length.
- `length = ...`: Ensures the iterable has an exact length.
- `max_length = ...`: Ensures the iterable has a maximum length.
- `max_length_or_eq = ...`: Ensures the iterable has a maximum length or is equal to the specified length.

- **Numerics**:

- `min = ...`: Ensures the value is greater than this value.
- `min_or_eq = ...`: Ensures the value is greater than or equal to this value.
- `max = ...`: Ensures the value is less than this value.
- `max_or_eq = ...`: Ensures the value is less than or equal to this value.
- `positive`: Ensures the value is positive (excluding 0/-0/NAN).
- `negative`: Ensures the value is negative (excluding 0/-0/NAN).

- **Float specifics**:

- `finite`: Ensures the value is finite.
- `not_infinite`: Ensures the value is not infinite.
- `not_nan`: Ensures the value is not NaN.

- **String specifics**:

- `regex = ...`: Ensures the string matches a regular expression. You can pass a raw string or a variable.
_Note:_ A test is generated to ensure the pattern is valid.
_Note2:_ raw string regex can be checked at compile time with an optional feature (see: [Optional Features](#Optional-Features)).

- **Common**:

- `exactly = ...`: Ensures the value is equal to the specified value.
- `custom(with = ..., error = ...)`: Validates using a custom function, specifying an error path.
- `predicate(with = ..., error_name = ...)`: Uses a predicate function with an optional custom error variant name (defaults to `Predicate`).

##### _Note:_ The `with =` argument to the `custom` and `predicate` validator/sanitizer can be of 3 forms:

- **inlined closure:** `with = |str: &String| f64::from_str(str)`
- **function path:** `with = f64::from_str`
- **inlined block:**
These must use the variable `mut value: `.
- _For validators:_ `with = { f64::from_str(&value) }`
- _For sanitizers:_ `with = { value = value.to_uppercase(); }`

---

#### Optional Features

- **Compile-Time Regex**: When `regex_validation` is enabled, raw literal regex patterns are validated at compile time, so you don't have to run the generated test every time.

---

#### Why that name

For those who wonder, the name `nnn` reflects a 3-week, carefree adventure with no expectations — it's simply 'n' for 'newtype', tapped a random number of times.

![](https://i.makeagif.com/media/9-14-2024/8wcpfp.gif)

Ladies and Gentelmens, welcome to n-n-n-newtypes.

#### License

This project is licensed under the **[WTFPL](./LICENSE)**.