{"id":24016020,"url":"https://github.com/axionbuster/validus","last_synced_at":"2025-06-29T21:03:18.011Z","repository":{"id":270579670,"uuid":"652483006","full_name":"axionbuster/validus","owner":"axionbuster","description":"Validated string slices","archived":false,"fork":false,"pushed_at":"2023-07-08T16:49:17.000Z","size":101,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-10T20:03:07.084Z","etag":null,"topics":["rust","rust-patterns","validation"],"latest_commit_sha":null,"homepage":"https://docs.rs/validus","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/axionbuster.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2023-06-12T06:59:36.000Z","updated_at":"2024-12-25T19:21:41.000Z","dependencies_parsed_at":"2025-01-01T15:16:22.180Z","dependency_job_id":"015f10f5-fa41-4ab9-a1cb-06a99994ac08","html_url":"https://github.com/axionbuster/validus","commit_stats":null,"previous_names":["axionbuster/validus"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/axionbuster/validus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axionbuster%2Fvalidus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axionbuster%2Fvalidus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axionbuster%2Fvalidus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axionbuster%2Fvalidus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/axionbuster","download_url":"https://codeload.github.com/axionbuster/validus/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axionbuster%2Fvalidus/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261220636,"owners_count":23126785,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["rust","rust-patterns","validation"],"created_at":"2025-01-08T08:39:04.035Z","updated_at":"2025-06-22T01:34:32.316Z","avatar_url":"https://github.com/axionbuster.png","language":"Rust","readme":"# `validus` --- validated string slices\n\n```rust\n// A three-step process.\n\n// 1. Define your validation. No need to re-implement your validation logic!\n// (though, we have a macro that makes it easy if you do have control.)\n// Validus offers multiple tradeoff points between flexibility and convenience.\n\n// 2. Replace `str` with `vstr\u003cRule\u003e` where you need validation.\n// `\u0026vstr` coerces to `\u0026str`, so you can use it deep inside your codebase,\n// or even a foreign codebase. (Limitations exist.)\n\n// 3. Use `vstr\u003cRule\u003e` as though it were `str`.\n\n# use std::sync::OnceLock;\n# \nuse validus::prelude::*; // vstr, fast_rule, \u003c\u0026str\u003e.validate(), etc.\nuse validus::fast_rule;\n\n# \n# use regex::Regex;\n#\n// Let's define a string validation rule that uses a Regex.\nconst USERNAME_RE_S: \u0026str = r#\"^[a-zA-Z0-9_]{1,16}$\"#;\nstatic USERNAME_RE: OnceLock\u003cRegex\u003e = OnceLock::new();\n\n// 1. Defining the rule type and the error type.\n// - I used fast_rule! because I have control over the error type.\n// - If you are using existing error types and/or function,\n// you can use easy_rule! instead. There's also a macro-free way;\n// see the `vstr` module docs for more info.\nstruct BadUsernameError;\nstruct UsernameRule;\nfast_rule!(\n    UsernameRule,\n    err = BadUsernameError,\n    msg = \"bad username\",\n    // The closure below could very well be a function.\n    // So if the function were called `username_rule`,\n    // I could do fast_rule!(..., ..., ..., username_rule)\n    |s: \u0026str| {\n        let re = USERNAME_RE.get_or_init(|| Regex::new(USERNAME_RE_S).unwrap());\n        re.is_match(s)\n    }\n);\n// (There's also easy_rule that allows you to bring your own error type.)\n// (That's helpful when you have existing error types.)\n// (... or, walk the macro-free path and implement ValidateString manually.)\n// (consult the `vstr` module docs for more info.)\n\n// 2. Restrict your string slice with the rule.\ntype Username = vstr\u003cUsernameRule\u003e;\n\n// 3. Use your `vstr\u003cRule\u003e` as though it were `str`.\nlet input = \"hello\";\nlet username: Result\u003c\u0026Username, BadUsernameError\u003e = input.validate();\nassert!(username.is_ok());\nassert_eq!(username.unwrap(), \"hello\");\n\nlet input = \"haha 😊\";\nlet username: Result\u003c\u0026Username, _\u003e = input.validate();\nassert!(username.is_err());\n\n// It's that easy! Let's do a recap.\n\n// - Error and the rule:\n// struct MyError;\n// struct MyRule;\n// fn my_rule_priv(s: \u0026str) -\u003e Result\u003c(), MyError\u003e { ... }\n// fast_rule!(MyRule, err = MyError, msg = \"my error\", my_rule_priv);\n\n// - Use `vstr` as though it were `str`:\n// type MyStr = vstr\u003cMyRule\u003e;\n// let a: \u0026vstr\u003cMyRule\u003e = \"hello\".validate().unwrap();\n\n// Plus, this library has serde support with validation, and more.\n```\n\n## Synopsis\n\n1. **Bring your own validation**, and use [`vstr\u003c_\u003e`](crate::vstr::vstr)\nas though it were `str` (where immutable `str` references are expected).\n2. **Mix it into your code.** If you have existing validation modules,\ndon't touch it. Validus has multiple easy ways to wrap your validation\ninto a `vstr\u003c_\u003e`-compatible rule type.\n3. **Take a shortcut when starting out.** If you don't have existing validation\nmodules, you can use the [`fast_rule!`](crate::fast_rule) macro to quickly define a rule type\nwith a predicate (closure or external function, which may or may not\nbelong to your crate). Your error type will be a proper `std::error::Error`\ntype; you can use an ad-hoc `\u0026'static str` error message, even.\n3. **Work with `serde` with no extra code.**\nSimply add `#[derive(Serialize, Deserialize)]` to your struct as usual.\nNow, no need to worry about invalid strings getting deserialized.\n4. **Opt into lazy validation if you want.**\nWith a special rule called [`Later\u003c_\u003e`](crate::vstr::Later), you can also choose to either eagerly\nvalidate or defer validation until when you decide you need it.\n(Pretty useful for `serde` deserialization, for example.)\n5. **Combine rules on the spot.** Three logical connectives (and, or, not) are provided\nas an extension enabled by default; those are in your prelude, so you can\nuse them right away.\n\nThe Validus library provides a newtype over regular string slices\ncalled `vstr`. This `vstr` is parameterized by your own validation\nrule type, like `vstr\u003cEmailRule\u003e`. Any zero-sized type can be used\nas a rule if it implements the trait [`ValidateString`](crate::vstr::ValidateString).\n\n`vstr` is meant to be a replacement of `str` in certain contexts.\nSo,\n- `vstr` implements `Eq` with `vstr` of other rules as well as `str`,\n- ... and `Ord` and `Hash` the same as any other `str` reference.\n- `vstr` is aware of `String`, `Cow` and smart pointer types\nsuch as `Box`, `Rc` and `Arc`.\n\nTo show you how `vstr\u003c_\u003e` compares and hashes the same as `str` references,\nI will give you an example of directly using `vstr` as keys in `HashMap`s and `HashSet`s.\n\n```rust\n// Illustration: using vstr\u003c_\u003e as a key in a HashMap.\n# \n# use std::collections::HashMap;\n# \n# use validus::prelude::*;\n# use validus::fast_rule;\n\nstruct BadUsernameError;\nstruct UsernameRule;\nfast_rule!(\n    UsernameRule,\n    err = BadUsernameError,\n    msg = \"bad username\",\n    |s: \u0026str| s.len() \u003c= 16\n);\n\ntype Username = vstr\u003cUsernameRule\u003e;\n\nlet mut map = HashMap::\u003c\u0026Username, i32\u003e::new();\nmap.insert(\"hello\".validate().unwrap(), 1);\nmap.insert(\"world\".validate().unwrap(), 2);\n\n// assume_valid bypasses validation, incurring no computational cost,\n// so it's useful in this case.\nassert_eq!(map.get(\"hello\".assume_valid()), Some(\u00261));\nassert_eq!(map.get(\"world\".assume_valid()), Some(\u00262));\n\n// So every time you need a `\u0026str` but with validation,\n// you know that Validus and `vstr\u003c_\u003e` have got you covered,\n// anywhere in your codebase, or a foreign codebase.\n// (Limitations exist.)\n```\n\n## `serde` with validation\n\nWith the optional `serde` feature, this crate also\nsupports serialization and deserialization with validation.\nThis means that you can use `vstr\u003c_\u003e` as a field in a\n`serde`-powered struct, and if the input fails the validation,\nit will be rejected and an error according to the validation\nrule's associated `Error` type will be returned.\n\n- The `serde` feature is enabled by default. Disable it using\n`default-features = false` in your `Cargo.toml`.\n\n```rust\n// Illustration: a struct with a validated email field.\n\n#[cfg(feature = \"serde\")] {\n\nuse validus::prelude::*;\nuse validus::fast_rule;\nuse serde::Deserialize;\n\n// This rule is very generous. It accepts any string that\n// contains an at-symbol.\n// (When the error type is not specified, it is inferred to\n// be \u0026'static str.)\nstruct EmailRule;\nfast_rule!(EmailRule, msg = \"no at-symbol\", |s: \u0026str| s.contains('@'));\n\n#[derive(Deserialize)]\npub struct User {\n    pub email: Box\u003cvstr\u003cEmailRule\u003e\u003e,\n}\n\nlet input = r#\"{\"email\": \"notgood\"}\"#;\nlet result = serde_json::from_str::\u003cUser\u003e(input);\nassert!(result.is_err());\n\nlet input = r#\"{\"email\": \"hi@example.com\"}\"#;\nlet result = serde_json::from_str::\u003cUser\u003e(input);\nassert!(result.is_ok());\nassert!(result.unwrap().email.as_str() == \"hi@example.com\");\n\n}\n```\n\n## Deferred validation with [`Later`](crate::vstr::Later)\n\nSometimes, you want to validate a string slice only when it is actually used.\n\nFor this need, there is a rule called `Later`\nthat bypasses all validation, but specifies what rule it is supposed to be\nvalidated with. When the validation is actually needed, you can call\n`make_strict` to validate the string slice\nand convert it to a `vstr` with the specified rule.\n\nHere, I copy the example code from the `Later` type documentation.\n\n- [`Later`](crate::vstr::Later)\n- [`make_strict`](crate::vstr::VStr::make_strict)\n- [`assume_valid`](crate::vstr::VStr::assume_valid)\n\n```rust\nuse validus::prelude::*;\nuse validus::fast_rule;\n\nstruct EmailError;\nstruct Email;\nfast_rule!(Email,\n    err = EmailError,\n    msg = \"no @ symbol\",\n    |s: \u0026str| s.contains('@')\n);\n\n// Here, we start with an email with deferred (postponed) validation.\n// Validation of `Later\u003c_\u003e` is infallible.\nlet v1: \u0026vstr\u003cLater\u003cEmail\u003e\u003e = \"hi@example.com\".validate().unwrap();\n// Now, we truly validate it.\nlet v1: Result\u003c\u0026vstr\u003cEmail\u003e, _\u003e = v1.make_strict();\nassert!(v1.is_ok());\n\n// So, again, this is going to succeed.\nlet v2 = \"notgood\".validate::\u003cLater\u003cEmail\u003e\u003e().unwrap();\n// But, when we check it, it will fail, since it is not a good email address\n// (according to the rule we defined).\nlet v2 = v2.make_strict();\nassert!(v2.is_err());\n\n// With the extension `StrExt`, we can also call `.assume_valid()`\n// to skip validation, since we know that `Later\u003c_\u003e` doesn't validate.\n\nlet relaxed = \"hi@example.com\".assume_valid::\u003cLater\u003cEmail\u003e\u003e();\nassert!(relaxed.check().is_ok()); // This is infallible because `Later\u003c_\u003e` is infallible.\nassert!(relaxed.make_strict().is_ok()); // Later\u003cEmail\u003e -\u003e Email.\n\nlet relaxed = \"nonono\".assume_valid::\u003cLater\u003cEmail\u003e\u003e();\nassert!(relaxed.check().is_ok()); // Yup, it is still infallible.\nlet strict = relaxed.make_strict(); // Now, we made it strict.\nassert!(strict.is_err()); // It didn't make it (it was a bad email address.)\n```\n\n## Overriding a rule with `assume_valid` and checking with `check`\n\nYou are also given the power to override the underlying\nmechanism using `assume_valid`. This is useful when you\nhave a `vstr\u003c_\u003e` that you know is valid, but that is difficult to\ndecide at a given moment; or, when, for some reason, you don't need\nto validate a `vstr\u003c_\u003e` (for example, when you are using it as a\nlook-up key in a `HashMap`).\nThe crate provides the `check()` method that can be used to\nestablish the validity of a `vstr\u003c_\u003e`.\n\n- [`assume_valid`](crate::vstr::VStr::assume_valid)\n- [`check`](crate::vstr::VStr::check)\n\n```rust\n// Illustration: overriding the validation mechanism.\n\nuse validus::prelude::*;\nuse validus::easy_rule;\n\n// easy_rule is a different macro that helps you define rules.\n// The difference with fast_rule! is that leaves the error type\n// untouched, and you need to return a Result\u003c(), YourError\u003e\n// instead of a bool in the closure.\nstruct No;\neasy_rule!(No, err = \u0026'static str, |s: \u0026str| Err(\"i won't accept anything\"));\n\nlet s = \"hello\";\nlet v: \u0026vstr\u003cNo\u003e = vstr::assume_valid(s);\n\n// Yup, it works. We overrode the validation mechanism.\nassert_eq!(v, \"hello\");\n\n// But it's not valid. Let's test that.\nassert!(v.check().is_err());\n```\n\n(`assume_valid` is NOT `unsafe`: `vstr` makes no further\nguarantees about the validity of the string slice beyond what\n`str` provides. \\[it also doesn't make any fewer\\].\nThus, **`assume_valid` may not be blamed for\ncausing undefined behavior.**)\n\n## Defining implication among rules\n\nFurthermore, since some pairs of rules can be converted\nautomatically (there is an IMPLIES relation between them),\nyou can use the `change_rules` associated method to\nconvert a reference to `vstr\u003cRule1\u003e` to a reference to\n`vstr\u003cRule2\u003e`. This requires `Rule` to implement `Into\u003cRule2\u003e`.\n(Otherwise, the regular `try_change_rules` can be used\nbetween any two rules.)\n\n- [`change_rules`](crate::vstr::VStr::change_rules)\n- [`try_change_rules`](crate::vstr::VStr::try_change_rules)\n- [`ValidateString`](crate::vstr::ValidateString)\n- [`Into`] and [`From`]\n\n```rust\n// Illustration: declaring implication.\n// Implication means: \"Whenever [rule] A says good, so does B.\"\n\nuse validus::prelude::*;\nuse validus::fast_rule;\n\n// Less generous\nstruct A;\nfast_rule!(A, msg = \"no wow\", |s: \u0026str| s.contains(\"wow\"));\n\n// More generous: includes all strings that A accepts and\n// perhaps more.\nstruct B;\nfast_rule!(B, msg = \"neither wow nor bad found\", |s: \u0026str| {\n    s.contains(\"wow\") || s.contains(\"bad\")\n});\n\n// Assert that A implies B.\n// In English: \"whatever string A accepts, B accepts, too.\"\nimpl From\u003cA\u003e for B {\n    // This particular formulation is idiomatic\n    // to the `validus` crate because all rules are supposed\n    // to be freely constructed Zero-Sized Types (ZSTs).\n    fn from(_: A) -\u003e Self {\n        // And, this value never gets used, anyway.\n        // All methods of `ValidateString` (trait that\n        // defines rules) have static methods, not instance\n        // methods.\n        B\n    }\n}\n\n// The declaration of implication unlocks the `change_rules`\n// method that converts a reference to `vstr\u003cA\u003e` to a reference\n// to `vstr\u003cB\u003e` infallibly.\n\nlet good = \"wow bad\";\nlet a: \u0026vstr\u003cA\u003e = vstr::assume_valid(good); // we know it works, so.\nlet _: \u0026vstr\u003cB\u003e = a.change_rules(); // infallible. see, no Result or unwrap().\n```\n\n### The special rule `ValidateAll`\n\nOh, one more. There are two special rules which validate\nall strings and no strings, respectively. They are called\n`ValidateAll` and `()`. Though you can't use `change_rules`\nto convert your rule to `ValidateAll`, you can still use\na dedicated method called `erase_rules` just for that.\nFrom `ValidateAll`, you can use `try_change_rules` to\nconvert to any other rule.\n\n- [`try_change_rules`](crate::vstr::VStr::try_change_rules)\n- [`change_rules`](crate::vstr::VStr::change_rules)\n- [`erase_rules`](crate::vstr::VStr::erase_rules)\n- [`ValidateAll`](crate::vstr::ValidateAll)\n\n## Batteries included ... (but I need your help!)\n\nCheck out some of the prepared validation rules in the module [`vstrext`][crate::vstrext].\nThe module should already have been imported in the prelude module\n(it's feature-gated by `ext`, which is enabled by default.)\n\nCurrently, three *logical connectives* and a few rules are implemented in the extension module:\n- [`Conj`](crate::vstrext::Conj), which requires two rules to be satisfied.\n- [`Disj`](crate::vstrext::Disj), which requires at least one of two rules to be satisfied.\n- [`Neg`](crate::vstrext::Neg), which requires a rule to be **un**-satisfied.\n- [`StringSizeRule`](crate::vstrext::StringSizeRule) (and its variants), which check the size of a string.\n- [`StringAsciiRule`](crate::vstrext::StringAsciiRule), our only rule that checks the content of a string so far.\n\nI would really appreciate your help in adding more rules to the extension module.\n\n## Experimental features\n\n### Contingent validation\n\nThe experimental `cow` feature introduces a new type, `VCow` that represents\na `Cow\u003c'_, vstr\u003c_\u003e\u003e` that is either valid or invalid. The validity is tracked\nat runtime.\n\n**NOTE**: `vstr` already supports being in a Cow like this: `Cow\u003c'_, vstr\u003c_\u003e\u003e`\neven without the `cow` feature. The `cow` feature adds an experimental\nwrapper type that tracks the validity of the `vstr` that may change at runtime.\n\n- [`VCow`](crate::vstr::VCow)\n\n## Features\n\n- (default) `serde`: enables `serde` support. Pulls in `serde` as a dependency.\n- (default) `ext`: enables built-in extensions. Pulls in `thiserror` as a dependency.\n- (default) `std`: enables `std` support. Necessary to implement the `Error` trait. Pulls in `std` as a dependency.\n- (default) `alloc`: integrates with `alloc` crate., and enables smart pointers.\n- `cow`: enables the experimental `VCow` type. Pulls in `alloc` as a dependency.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxionbuster%2Fvalidus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faxionbuster%2Fvalidus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxionbuster%2Fvalidus/lists"}