{"id":39584732,"url":"https://github.com/vic1707/nnn","last_synced_at":"2026-01-26T15:01:15.013Z","repository":{"id":270214200,"uuid":"898710579","full_name":"vic1707/nnn","owner":"vic1707","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-21T19:47:31.000Z","size":194,"stargazers_count":17,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-23T08:12:18.509Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vic1707.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-12-04T22:20:11.000Z","updated_at":"2025-12-21T19:47:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"0f088f58-506d-4b43-9c5a-0850cf4bf128","html_url":"https://github.com/vic1707/nnn","commit_stats":null,"previous_names":["vic1707/nnn"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/vic1707/nnn","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic1707%2Fnnn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic1707%2Fnnn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic1707%2Fnnn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic1707%2Fnnn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vic1707","download_url":"https://codeload.github.com/vic1707/nnn/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic1707%2Fnnn/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28781308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T13:55:28.044Z","status":"ssl_error","status_checked_at":"2026-01-26T13:55:26.068Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-18T07:35:27.099Z","updated_at":"2026-01-26T15:01:15.006Z","avatar_url":"https://github.com/vic1707.png","language":"Rust","readme":"# nnn\n\n[\u003cimg alt=\"github\" src=\"https://img.shields.io/badge/github-vic1707/nnn-8da0cb?style=for-the-badge\u0026labelColor=555555\u0026logo=github\" height=\"20\"\u003e](https://github.com/vic1707/nnn)\n[\u003cimg alt=\"crates.io\" src=\"https://img.shields.io/crates/v/nnn.svg?style=for-the-badge\u0026color=fc8d62\u0026logo=rust\" height=\"20\"\u003e](https://crates.io/crates/nnn)\n[\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/badge/docs.rs-nnn-66c2a5?style=for-the-badge\u0026labelColor=555555\u0026logo=docs.rs\" height=\"20\"\u003e](https://docs.rs/nnn)\n[\u003cimg alt=\"lines\" src=\"https://www.aschey.tech/tokei/github.com/vic1707/nnn?label=\u0026style=for-the-badge\u0026logo=https://simpleicons.org/icons/rust.svg?logoAsLabel%3D1?category%3Dcode\" height=\"20\"\u003e](https://github.com/vic1707/nnn)\n[\u003cimg alt=\"maintenance\" src=\"https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg?style=for-the-badge\" height=\"20\"\u003e](https://github.com/vic1707/nnn)\n\n### nnn Crate Documentation\n\nThe `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.\n\n#### Philosophy\n\nThe primary goal of `nnn` is to provide tools, not guardrails, the only errors returned by `nnn` are parsing/syntax errors and footguns.\n(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()`.)\n\nBy design, `nnn` doesn’t “hold hands” or attempt to protect users from all possible mistakes. Instead, it prioritizes flexibility and assumes user expertise.\n\n---\n\n#### Inspirations\n\nThe `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.\n\nThis 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.\n\n---\n\n#### Complete example\n\n```rs\nuse nnn::nnn;\n\n#[nnn(\n    derive(Debug, PartialEq, Eq, PartialOrd, Ord),\n    nnn_derive(TryFrom),\n    consts(\n            ZERO = 0.0_f64,\n        pub ONE = 1.0_f64,\n    ),\n    default = 5.0_f64,\n    validators(finite, positive),\n    sanitizers(custom = |v: f64| v.abs()),\n    // Serialize \u0026 Deserialize are only available in test env.\n    cfg(test, derive(Serialize, Deserialize)),\n    attrs(\n        repr(transparent),\n    ),\n)]\nstruct PositiveFiniteFloat(f64);\n```\n\nI encourage you to see what code is generated using [`cargo-expand`](https://github.com/dtolnay/cargo-expand).\n\n#### Usage\n\nEvery argument in a `nnn` declaration must be provided in a single macro invocation, so\n\n```rs\n#[nnn(derive(Debug))]\n#[nnn(default = 5.0_f64)]\nstruct Dummy(f64);\n```\n\nis invalid.\n\nNote that `derive` and other attributes must be passed via `nnn` to make it clear that `nnn` manages them.\n\n##### Arguments\n\nBelow is a complete list of supported arguments for the `nnn` macro:\n\n- **`cfg(\u003ccondition\u003e, \u003cnnn_arguments\u003e)`**: Adds conditional compilation to the provided arguments.\n\n- **`consts(pub EIGHT = 8, pub(in crate) NINE = 9, ...)`**: Defines associated constants for the newtype.\n  _Note:_ `nnn` will generate unit tests ensuring these values are correct.\n\n- **`derive(\u003ctraits\u003e, ...)`**: Specifies standard Rust derives for the newtype.\n\n- **`nnn_derive(\u003ctraits\u003e, ...)`**: Declares specific derives re-implemented by `nnn` that ensure validation and sanitization are applied where appropriate.\n\n- **`default` or `default = ...`**: Defines a default value for the newtype. Can be either:\n\n  - `#[nnn(default)]`: Uses the inner type's default value.\n  - `#[nnn(default = ...)]`: Specifies a custom default value.\n\n  _Note:_ `nnn` will generate a unit test ensuring the default value is correct.\n\n- **`new_unchecked`**: Enables the generation of the unsafe method `const fn new_unchecked(v: \u003cinner\u003e) -\u003e Self` that bypasses validation and sanitization.\n\n- **`sanitizers(\u003clist of sanitizers\u003e)`**: Declares a list of sanitizers to apply to the input.\n  _Note:_ Sanitization is executed **before** validation.\n\n- **`validators(\u003clist of validators\u003e)`**: Declares a list of validators to ensure the input meets the desired conditions.\n\n- **`attrs(\u003cattributes to pass down to the newtype\u003e)`**: Specifies additional attributes for the newtype, such as `#[repr(C)]` or `#[non_exhaustive]`.\n\n##### Derive Handling\n\nWhile most derives are passed through transparently, there are exceptions:\n\n1. **`Eq` \u0026 `Ord`**\n\nAutomatically implemented except when the `finite` or `not_nan` validator is provided, in which case a manual implementation is generated.\n\n2. **`Deserialize`**\n\nPassed transparently, with `nnn` injecting `#[serde(try_from = \"\u003cinner\u003e\")]` to ensure validation and sanitization during deserialization.\n\n**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.\n\n##### Custom derives\n\n`nnn` provides custom implementations of some common derives to implement `nnn`'s guarantees.\n\n1. **`Into`/`From`/`Borrow`/`AsRef`/`Deref`**\n\nThese derives their respective traits to convert from a new-type to its inner type.\nThese derives can take generic inputs as parameters, `#[nnn_derive(Into\u003ci8, i16, 132\u003e)]` will generate derives for `Into\u003ci8\u003e`/`Into\u003ci16\u003e`/`Into\u003ci32\u003e` for the new_type. `#[nnn_derive(Into)]` still defaults to deriving `Into\u003cinner_type\u003e`.\n\n_Note:_ `_`as a generic parameter will be translated to`\u003cinner_type\u003e`, e.g:\n\n```rs\n#[nnn_derive(From\u003c_\u003e)]\nstruct A(i8);\n```\n\nwill implement `From\u003ci8\u003e` for the new-type.\n\n2. **`TryFrom`**\n\nImplements `TryFrom` and calls the `try_new` methods.\n`TryFrom` can take generic parameters as parameters, `#[nnn_derive(TryFrom\u003ci8, i16, 132\u003e)]` will generate derives for `TryFrom\u003ci8\u003e`/`TryFrom\u003ci16\u003e`/`TryFrom\u003ci32\u003e`. `#[nnn_derive(TryFrom)]` still defaults to deriving `TryFrom\u003cinner_type\u003e`.\n\n_Note:_ `_` as a generic parameter will be translated to `\u003cinner_type\u003e`, e.g:\n\n```rs\n#[nnn_derive(TryFrom\u003c_\u003e)]\nstruct A(i8);\n```\n\nwill implement `TryFrom\u003ci8\u003e` for the new-type.\n\n3. **`FromStr`**\n\nGenerates an implementation using the inner `FromStr` implementation and passing parsed value through sanitization and validation.\nIt generates the following error enum implementing `Debug`, `Clone`, `PartialEq`, `Eq` and `Display`.\n\n```rs\nenum /* new_type's name */ParseError {\n    InnerParse(\u003c/* inner_type */ as ::core::str::FromStr\u003e::Err),\n    Validation(/* new_type's name */Error),\n}\n```\n\n4. `IntoIterator`\n\nImplements `IntoIterator` for the new-type and a reference to it, the iterator yields items from the inner type's iterator implementation.\n\n---\n\n#### Sanitizers\n\n_To see examples, each sanitizer is tested in the [test folder](./tests/sanitizers)._\n\nThe `sanitizers` argument accepts the following options:\n\n- **Iterables**:\n\n  - `each(...)`: Applies sanitizers to each element in a collection.\n  - `sort`: Sorts the elements in the collection.\n  - `dedup`: Removes duplicate elements from the collection using rust's `dedup` which only removes consecutive duplicates.\n\n- **Strings**:\n\n  - `trim`: Removes leading and trailing whitespace.\n  - `lowercase`: Converts the string to lowercase.\n  - `uppercase`: Converts the string to uppercase.\n\n- **Common**:\n  - `custom = ...`: Specifies a custom sanitization function.\n\n---\n\n#### Validators\n\n_To see examples, each validator is tested in the [test folder](./tests/validators)._\n\nEach validator generates a specific variant for the corresponding error type which has the same visibility as the `new_type`.\nThis error enum implements the traits: `Debug`, `Error`, `Display`, `Clone`, `PartialEq`, `Eq`.\n\n```rs\nenum /* new_type's name */Error {\n    Positive,\n    Finite,\n    /// idx of the problematic element, inner error\n    Each(usize, Box\u003cSelf\u003e),\n    /* ... */\n}\n```\n\nThe `validators` argument accepts the following options:\n\n- **Iterables**:\n\n  - `not_empty`: Ensures an iterable is not empty.\n  - `each(...)`: Applies validators to each element in an iterable.\n  - `min_length = ...`: Ensures the iterable has a minimum length.\n  - `min_length_or_eq = ...`: Ensures the iterable has a minimum length or is equal to the specified length.\n  - `length = ...`: Ensures the iterable has an exact length.\n  - `max_length = ...`: Ensures the iterable has a maximum length.\n  - `max_length_or_eq = ...`: Ensures the iterable has a maximum length or is equal to the specified length.\n\n- **Numerics**:\n\n  - `min = ...`: Ensures the value is greater than this value.\n  - `min_or_eq = ...`: Ensures the value is greater than or equal to this value.\n  - `max = ...`: Ensures the value is less than this value.\n  - `max_or_eq = ...`: Ensures the value is less than or equal to this value.\n  - `positive`: Ensures the value is positive (excluding 0/-0/NAN).\n  - `negative`: Ensures the value is negative (excluding 0/-0/NAN).\n\n- **Float specifics**:\n\n  - `finite`: Ensures the value is finite.\n  - `not_infinite`: Ensures the value is not infinite.\n  - `not_nan`: Ensures the value is not NaN.\n\n- **String specifics**:\n\n  - `regex = ...`: Ensures the string matches a regular expression. You can pass a raw string or a variable.\n    _Note:_ A test is generated to ensure the pattern is valid.\n    _Note2:_ raw string regex can be checked at compile time with an optional feature (see: [Optional Features](#Optional-Features)).\n\n- **Common**:\n\n  - `exactly = ...`: Ensures the value is equal to the specified value.\n  - `custom(with = ..., error = ...)`: Validates using a custom function, specifying an error path.\n  - `predicate(with = ..., error_name = ...)`: Uses a predicate function with an optional custom error variant name (defaults to `Predicate`).\n\n##### _Note:_ The `with =` argument to the `custom` and `predicate` validator/sanitizer can be of 3 forms:\n\n- **inlined closure:** `with = |str: \u0026String| f64::from_str(str)`\n- **function path:** `with = f64::from_str`\n- **inlined block:**\n  These must use the variable `mut value: \u003cinner\u003e`.\n  - _For validators:_ `with = { f64::from_str(\u0026value) }`\n  - _For sanitizers:_ `with = { value = value.to_uppercase(); }`\n\n---\n\n#### Optional Features\n\n- **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.\n\n---\n\n#### Why that name\n\nFor 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.\n\n![](https://i.makeagif.com/media/9-14-2024/8wcpfp.gif)\n\nLadies and Gentelmens, welcome to n-n-n-newtypes.\n\n#### License\n\nThis project is licensed under the **[WTFPL](./LICENSE)**.\n","funding_links":[],"categories":["\u003ca name=\"Rust\"\u003e\u003c/a\u003eRust"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic1707%2Fnnn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvic1707%2Fnnn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic1707%2Fnnn/lists"}