{"id":34252030,"url":"https://github.com/biblius/validify","last_synced_at":"2025-12-16T10:58:32.865Z","repository":{"id":65241262,"uuid":"567939159","full_name":"biblius/validify","owner":"biblius","description":"Procedural macros for data validation/modification","archived":false,"fork":false,"pushed_at":"2025-11-21T01:05:12.000Z","size":375,"stargazers_count":72,"open_issues_count":8,"forks_count":6,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-29T01:07:19.773Z","etag":null,"topics":["rust","validation"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/biblius.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":"2022-11-19T00:21:35.000Z","updated_at":"2025-10-08T12:25:35.000Z","dependencies_parsed_at":"2023-02-10T09:00:39.844Z","dependency_job_id":"17d1b240-3fb8-4561-ad04-242d6dd1347d","html_url":"https://github.com/biblius/validify","commit_stats":{"total_commits":59,"total_committers":4,"mean_commits":14.75,"dds":0.06779661016949157,"last_synced_commit":"cb0491edd84acf31d3dee533215c5097a946a733"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/biblius/validify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biblius%2Fvalidify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biblius%2Fvalidify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biblius%2Fvalidify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biblius%2Fvalidify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/biblius","download_url":"https://codeload.github.com/biblius/validify/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biblius%2Fvalidify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27763333,"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","status":"online","status_checked_at":"2025-12-16T02:00:10.477Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["rust","validation"],"created_at":"2025-12-16T10:58:32.246Z","updated_at":"2025-12-16T10:58:32.858Z","avatar_url":"https://github.com/biblius.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Validify\n\n[![Build](https://img.shields.io/github/actions/workflow/status/biblius/validify/check.yml?logo=github\u0026style=plastic)](https://github.com/biblius/validify)\n[![test](https://img.shields.io/github/actions/workflow/status/biblius/validify/test.yml?label=test\u0026logo=github\u0026style=plastic)](https://github.com/biblius/validify)\n[![docs](https://img.shields.io/docsrs/validify?logo=rust\u0026style=plastic)](https://docs.rs/validify/latest/validify/)\n[![version](https://img.shields.io/crates/v/validify?logo=rust\u0026style=plastic)](https://crates.io/crates/validify)\n\nProcedural macros that provide attributes for data validation and modification.\n\n## Attributes\n\n### **Modifiers**\n\n| Modifier   | Type                                 | Description                                                                                                                                          |\n| ---------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| trim       | `String / Vec\u003cString\u003e`               | Removes surrounding whitespace / in each string in the iterator                                                                                      |\n| uppercase  | `String / Vec\u003cString\u003e`               | Calls `.to_uppercase()` / in each string in the iterator                                                                                             |\n| lowercase  | `String / Vec\u003cString\u003e`               | Calls `.to_lowercase()` / in each string in the iterator                                                                                             |\n| capitalize | `String / Vec\u003cString\u003e`               | Makes the first char of the string uppercase / in each string in the iterator                                                                        |\n| custom     | `T`                                  | Takes a function whose argument is `\u0026mut \u003cType\u003e`                                                                                                     |\n| validify   | `impl Validify / Vec\u003cimpl Validify\u003e` | Can only be used on fields that are structs (or vecs of) implementing the `Validify` trait. Runs all the child's struct's modifiers and validations. |\n\n### **Validators**\n\nThe syntax is either\n\n- `#[validate(\u003cValidator\u003e)]` if `Parameters` is `--`,\n\n- `#[validate(\u003cValidator\u003e( [param = val1 ]+ )` if `Parameters` is specified.\n\nAll validators also take in a `code` and `message` as parameters and their values are must be string literals if specified.\n\nAll validators are valid on their respective `Option` types. Fields only get validated if they are `Some`.\n\n| Validator        | Field type         | Parameters         | Parameter type | Description                                                                                                                     |\n| ---------------- | ------------------ | ------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------- |\n| validate         | `impl Validate`    | --                 | --             | Calls the child struct's `validate` implementation.                                                                             |\n| email            | `String`           | --                 | --             | Checks emails based on [this spec](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address).                     |\n| credit_card      | `String`           | --                 | --             | Checks if the field's value is a valid credit card number.                                                                      |\n| phone            | `String`           | --                 | --             | Checks if the field's value is a valid phone number.                                                                            |\n| required         | `Option\\\u003cT\u003e`       | --                 | --             | Checks whether the field's value is `Some`.                                                                                     |\n| url              | `String`           | --                 | --             | Checks if the string is a URL.                                                                                                  |\n| non_control_char | `String`           | --                 | --             | Checks if the field contains control characters.                                                                                |\n| ip               | `String`           | format             | Ident (v4/v6)  | Checks if the string is an IP address.                                                                                          |\n| length           | `impl Length`      | min, max, equal    | LitInt         | Checks if the collection length is within the specified params. Works via the `Length` trait.                                   |\n| range            | `Int/Float`        | min, max           | LitFloat       | Checks if the value is in the specified range.                                                                                  |\n| contains         | `impl Contains`    | value              | Lit/Path       | Checks if the collection contains the specified value. Works via the `Contains` trait.                                          |\n| contains_not     | `impl Contains`    | value              | Lit/Path       | Checks if the collection doesn't contain the specified value. Works via the `Contains` trait.                                   |\n| custom           | `T`                | function           | Path           | Executes custom validation on the field by calling the provided function.                                                       |\n| regex            | `String`           | path               | Path           | Matches the provided regex against the field. Intended to be used with lazy_static by providing a path to an initialised regex. |\n| is_in            | `impl Contains`    | collection         | Path           | Checks whether the field's value is in the specified collection.                                                                |\n| not_in           | `impl Contains`    | collection         | Path           | Checks whether the field's value is not in the specified collection.                                                            |\n| iter             | `impl Iterator`    | List of validators | Validator      | Runs the provided validators on each element of the iterable.                                                                   |\n| time             | `NaiveDate\\[Time]` | See below          | See below      | Performs a check based on the specified op.                                                                                     |\n\n### **Time operators**\n\nAll time operators may take in `inclusive = bool`.\nAll time operator must take in `time = bool` when validating datetimes, by default time validators will attempt to validate dates.\n\n`in_period` and the `*_from_now` operators are inclusive by default.\n\nThe `target` param must be a string literal date or a path to an argless function that returns a date\\[time].\n\nIf the target is a string literal, it must contain a `format` param, as per [this](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).\n\nAccepted interval parameters are `seconds`, `minutes`, `hours`, `days`, `weeks`.\n\nThe `_from_now` operators should not use negative duration due to how they validate the inputs,\nnegative duration for `in_period` works fine.\n\n| Op              | Parameters       | Description                                                                      |\n| --------------- | ---------------- | -------------------------------------------------------------------------------- |\n| before          | target           | Check whether a date\\[time] is before the target one                             |\n| after           | target           | Check whether a date\\[time] is after the target one                              |\n| before_now      | --               | Check whether a date\\[time] is before today\\[now]                                |\n| after_now       | --               | Check whether a date\\[time] is after today\\[now]                                 |\n| before_from_now | interval         | Check whether a date\\[time] is before the specified interval from today\\[now]    |\n| after_from_now  | interval         | Check whether a date\\[time] is after the specified interval from the today\\[now] |\n| in_period       | target, interval | Check whether a date\\[time] falls within a certain period                        |\n\n## Derive\n\nAnnotate the struct or enum you want to modify and/or validate with the `Validify` attribute\n(if you do not need modification, derive only `Validate`) and call its `validate/validify` function:\n\n```rust\nuse validify::Validify;\n\n#[derive(Debug, Clone, serde::Deserialize, Validify)]\nstruct Testor {\n    #[modify(lowercase, trim)]\n    #[validate(length(equal = 8))]\n    pub a: String,\n    #[modify(trim, uppercase)]\n    pub b: Option\u003cString\u003e,\n    #[modify(custom(do_something))]\n    pub c: String,\n    #[modify(custom(do_something))]\n    pub d: Option\u003cString\u003e,\n    #[validify]\n    pub nested: Nestor,\n}\n\n#[derive(Debug, Clone, serde::Deserialize, Validify)]\nstruct Nestor {\n    #[modify(trim, uppercase)]\n    #[validate(length(equal = 12))]\n    a: String,\n    #[modify(capitalize)]\n    #[validate(length(equal = 14))]\n    b: String,\n}\n\nfn do_something(input: \u0026mut String) {\n    *input = String::from(\"modified\");\n}\n\nlet mut test = Testor {\n  a: \"   LOWER ME     \".to_string(),\n  b: Some(\"  makemeshout   \".to_string()),\n  c: \"I'll never be the same\".to_string(),\n  d: Some(\"Me neither\".to_string()),\n  nested: Nestor {\n    a: \"   notsotinynow   \".to_string(),\n      b: \"capitalize me.\".to_string(),\n  },\n};\n\n// The magic line\nlet res = test.validify();\n\nassert!(matches!(res, Ok(_)));\n\n// Parent\nassert_eq!(test.a, \"lower me\");\nassert_eq!(test.b, Some(\"MAKEMESHOUT\".to_string()));\nassert_eq!(test.c, \"modified\");\nassert_eq!(test.d, Some(\"modified\".to_string()));\n// Nested\nassert_eq!(test.nested.a, \"NOTSOTINYNOW\");\nassert_eq!(test.nested.b, \"Capitalize me.\");\n```\n\n## Traits\n\nValidify is built around 3 traits:\n\n- Validate\n- Modify\n- Validify (Validate + Modify)\n\nThese traits should theoretically never have to be implemented manually.\n\nAs their names suggest, the first two traits perform validation and modification, while the third combines those 2 actions into a single one - `validify`.\n\nThe traits contain a single function which is constructed based on field annotations when deriving them.\n\n## Payload\n\nStructs annotated with `#[derive(Payload)]` get an associated payload struct, e.g.\n\n```rust\n#[derive(validify::Validify, validify::Payload)]\nstruct Something {\n  a: usize,\n  b: String,\n  c: Option\u003cbool\u003e\n}\n```\n\nbehind the scenes will generate an intermediary\n\n```rust\n#[derive(Debug, Clone, serde::Deserialize, validify::Validate)]\nstruct SomethingPayload {\n  #[validate(required)]\n  a: Option\u003cusize\u003e,\n  #[validate(required)]\n  b: Option\u003cString\u003e,\n  c: Option\u003cbool\u003e,\n\n  /* From and Into impls */\n}\n```\n\nThe motivation for this is to aid in deserializing potentially missing fields. Even though the payload struct cannot help with deserializing wrong types, it can still prove useful and provide a bit more meaningful error messages when fields are missing.\n\nThe original struct gets a `ValidifyPayload` implementation with 2 associated fns: `validate_from` and `validify_from` whose whose respective arguments are the generated payload.\n\nThe `ValidifyPayload` implementations first validate the required fields of the payload. Then, if any required fields are missing, no further modification/validation is done and the errors are returned. Next, the payload is transformed to the original struct and modifications and/or validations are run on it.\n\nWhen a struct contains nested validifies (child structs annotated with `#[validify]`), all the children in the payload will also be transformed and validated as payloads first. This means that any nested structs must also derive `Payload`.\n\nThe `Payload` derive macro does not work on enums.\n\n## The payload and serde\n\nStruct level attributes, such as `rename_all` are propagated to the payload. When attributes that modify field names are present, any field names in returned errors will be represented as the original (i.e. client payload).\n\nThere are a few special serde attributes that validify treats differently; `rename`, `with` and `deserialize_with`.\nIt is **highly** advised these attributes are kept in a separate annotation from any other serde attributes, due to the way\nthey are parsed for the payload.\n\nThe `rename` attribute is used by validify to set the field name in any errors during validation. The `with` and `deserialize_with` will be transfered to the payload field and will create a special deserialization function that will call the original and wrap the result in an option. If the custom deserializer already returns an option, it will do nothing.\n\n## Schema validation\n\nSchema level validation can be performed using the following:\n\n```rust\nuse validify::{Validify, ValidationErrors, schema_validation, schema_err};\n#[derive(validify::Validify)]\n#[validate(validate_testor)]\nstruct Testor {\n    a: String,\n    b: usize,\n}\n\n#[schema_validation]\nfn validate_testor(t: \u0026Testor) -\u003e Result\u003c(), ValidationErrors\u003e {\n  if t.a.as_str() == \"yolo\" \u0026\u0026 t.b \u003c 2 {\n    schema_err!(\"Invalid Yolo\", \"Cannot yolo with b \u003c 2\");\n  }\n}\n```\n\nThe `#[schema_validation]` proc macro expands the function to:\n\n```rust, ignore\nfn validate_testor(t: \u0026Testor) -\u003e Result\u003c(), ValidationErrors\u003e {\n    let mut errors = ValidationErrors::new();\n    if t.a == \"yolo\" \u0026\u0026 t.b \u003c 2 {\n        errors.add(ValidationError::new_schema(\"Invalid Yolo\").with_message(\"Cannot yolo with b \u003c 2\".to_string()));\n    }\n    if errors.is_empty() { Ok(()) } else { Err(errors) }\n}\n```\n\nThis makes schema validations a bit more ergonomic and concise.\nLike field level validation, schema level validation is performed after modification.\n\nWhen you have annotated a function with `#[schema_validation]`, you can use the `schema_err!` macro to ergonomically\ncreate schema errors.\n\n## Errors\n\nThe main ValidationError is an enum with 2 variants, Field and Schema. Field errors are, as the name suggests, created when fields fail validation and are usually automatically generated unless using custom handlers (custom field validation functions always must return a result whose Err variant is ValidationError).\n\nIf you want to provide a message along with the error, you can directly specify it in the attribute (the same goes for the code),\nfor example:\n\n`#[validate(contains(value = \"something\", message = \"Does not contain something\", code = \"MUST_CONTAIN\"))]`\n\nKeep in mind, when specifying validations this way, all attribute parameters MUST be specified as [NameValue](https://docs.rs/syn/latest/syn/struct.MetaNameValue.html) pairs. This means that if you write\n\n`#[validate(contains(\"something\", message = \"Bla\"))]`,\n\nyou will get an error because the parser expects either a single value or multiple name value pairs.\n\nThe [field_err!](https://docs.rs/validify/latest/validify/macro.field_err.html) macro provides a more ergonomic way to create field errors.\n\n### Location\n\nLocations are tracked for each error in a similar manner to [JSON pointers](https://opis.io/json-schema/2.x/pointers.html).\nWhen using custom validation, whatever field name you specify in the returned error will be used in the location for that field.\nKeep in mind locations are not reliable when dealing with hashed map/set collections as the item ordering for those is not guaranteed.\n\nError location display will depend on the original client payload, i.e.\nthey will be displayed in the original case the payload was received (e.g. when using serde's `rename_all` or `rename` attributes).\nAny overriden field names will be displayed as such.\n\n### Schema\n\nSchema errors are usually created by the user in schema validation.\nThe `schema_err!` macro alongside `#[schema_validation]` provides an ergonomic way to create schema errors.\nAll errors are composed to a `ValidationErrors` struct which contains a vec of all the validation errors.\nAll schema errors generated by `validify` will have their location set to '/'.\n\n### Params\n\nWhen sensible, validify automatically appends failing parameters and the target values they were validated against to the errors created to provide more clarity to the client and to save some manual work.\n\nOne parameter that is often appended is the `actual` field which represents the value of the violating field's target property during the validation. Some validators append additional data to the errors representing the expected values for the field.\n\n## **Examples**\n\n### **Date\\[times]s**\n\n```rust, ignore\nuse chrono::{NaiveDate, NaiveDateTime};\n\n#[derive(Debug, validify::Validate)]\nstruct DateTimeExamples {\n    #[validate(time(op = before, target = \"2500-04-20\", format = \"%Y-%m-%d\", inclusive = true))]\n    before: NaiveDate,\n    #[validate(time(op = before, target = \"2500-04-20T12:00:00.000\", format = \"%Y-%m-%-dT%H:%M:%S%.3f\"))]\n    before_dt: NaiveDateTime,\n    #[validate(time(op = after, target = \"2022-04-20\", format = \"%Y-%m-%d\"))]\n    after: NaiveDate,\n    #[validate(time(op = after, target = \"2022-04-20T12:00:00.000\", format = \"%Y-%m-%-dT%H:%M:%S%.3f\"))]\n    after_dt: NaiveDateTime,\n    #[validate(time(op = in_period, target = \"2022-04-20\", format = \"%Y-%m-%d\", weeks = -2))]\n    period: NaiveDate,\n}\n```\n\n### **With route handler**\n\n```rust\n    use validify::{Validify, Payload, ValidifyPayload};\n\n    #[derive(Debug, Validify, Payload)]\n    struct JsonTest {\n        #[modify(lowercase)]\n        a: String,\n        #[modify(trim, uppercase)]\n        #[validate(length(equal = 11))]\n        b: String,\n    }\n\n    // This would normally come from a framework\n    struct Json\u003cT\u003e(T);\n\n    fn test() {\n      let jt = JsonTest {\n          a: \"MODIFIED\".to_string(),\n          b: \"    makemeshout    \".to_string(),\n      };\n      let json = Json(JsonTestPayload::from(jt));\n      mock_handler(json)\n    }\n\n    fn mock_handler(data: Json\u003cJsonTestPayload\u003e) {\n      let data = data.0;\n      let data = JsonTest::validify_from(data.into()).unwrap();\n      mock_service(data);\n    }\n\n    fn mock_service(data: JsonTest) {\n      assert_eq!(data.a, \"modified\".to_string());\n      assert_eq!(data.b, \"MAKEMESHOUT\".to_string())\n    }\n```\n\nSee more examples in [the test directory](./derive_tests/tests).\n\n### Contributing\n\nIf you have any ideas on how to improve Validify, such as common validations you find useful or better error messages, do not hesitate to open an issue or PR. All ideas are welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbiblius%2Fvalidify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbiblius%2Fvalidify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbiblius%2Fvalidify/lists"}