https://github.com/vic1707/nnn
https://github.com/vic1707/nnn
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/vic1707/nnn
- Owner: vic1707
- License: wtfpl
- Created: 2024-12-04T22:20:11.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-12-21T19:47:31.000Z (4 months ago)
- Last Synced: 2025-12-23T08:12:18.509Z (4 months ago)
- Language: Rust
- Size: 189 KB
- Stars: 17
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome - vic1707/nnn - (<a name="Rust"></a>Rust)
README
# nnn
[
](https://github.com/vic1707/nnn)
[
](https://crates.io/crates/nnn)
[
](https://docs.rs/nnn)
[
](https://github.com/vic1707/nnn)
[
](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.

Ladies and Gentelmens, welcome to n-n-n-newtypes.
#### License
This project is licensed under the **[WTFPL](./LICENSE)**.