validy 1.1.16

A powerful and flexible Rust library based on procedural macros for validation, modification, and DTO (Data Transfer Object) handling. Designed to integrate seamlessly with Axum. Inspired by Validator, Validify and Garde.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
# Validy

*More than just validation.*

[![Crates.io](https://img.shields.io/crates/v/validy.svg)](https://crates.io/crates/validy)
[![Status](https://github.com/L-Marcel/validy/actions/workflows/ci.yml/badge.svg)](https://github.com/L-Marcel/validy/actions/workflows/ci.yml)

A powerful and flexible Rust library based on procedural macros for `validation`, `modification`, and DTO (Data Transfer Object) handling. Designed to integrate seamlessly with `Axum`. Inspired by `Validator`, `Validify`, and `Garde`.

- [📝 Installation]#-installation
- [🚀 Quick Start]#-quick-start
- [📓 Glossary]#-glossary
- [🔎 About Implementations]#-about-implementations
- [🔌 Axum Integration]#-axum-integration
  - [Customizing the failure `status code`]#customizing-the-failure-status-code
  - [Multipart support]#multipart-support
- [🧩 Manual Usage]#-manual-usage
  - [Available traits]#available-traits
- [🚩 Feature Flags]#-feature-flags
- [🚧 Validation Rules]#-validation-rules
  - [For `required` fields]#for-required-fields
  - [For `string` fields]#for-string-fields
  - [For `collection` or `single` fields]#for-collection-or-single-fields
  - [For `numbers` fields]#for-numbers-fields
  - [For `date` or `time` fields]#for-date-or-time-fields
  - [For multipart `field data` fields]#for-multipart-field-data-fields
  - [Custom rules]#custom-rules
- [🔨 Modification Rules]#-modification-rules
  - [For `string` fields]#for-string-fields-1
  - [Custom rules]#custom-rules-1
- [🔧 Parsing Rules]#-parsing-rules
  - [For `uuid` fields]#for-uuid-fields
  - [For `date` or `time` fields]#for-date-or-time-fields-1
  - [Custom rules]#custom-rules-2
- [🔮 Special Rules]#-special-rules
- [📨 Wrappers]#-wrappers
- [📐 Useful Macros]#-useful-macros
  - [For `error` handling]#for-error-handling
  - [For `test` assertions]#for-test-assertions
- [💝 Complete Example]#-complete-example
- [📁 More Examples]#-more-examples
- [🎯 Work In Progress]#-work-in-progress
- [🎁 For Developers]#-for-developers

## 📝 Installation

Add with Cargo:

```bash
cargo add validy --features axum,email
```

## 🚀 Quick Start

The main entry point is the `#[derive(Validate)]` macro. It allows you to configure validations, modifications and parses behaviors directly on your struct. You also can use `#[wrapper_derive(...)]` to apply derive macros on [📨 Wrappers](#-wrappers).

```rust
use validy::core::{Validate, ValidateAndParse, ValidationError};
use validy::{assert_errors, assert_parsed};

#[derive(Debug, Validate, PartialEq)]
#[validate(payload, axum)]
#[wrapper_derive(Clone)]
pub struct CreateUserDTO {
	#[modificate(trim)]
	#[validate(length(3..=120, "name must be between 3 and 120 characters"))]
	#[validate(required("name is required"))]
	pub name: String,

	#[modificate(trim)]
	#[validate(email("invalid email format", "bad_format"))]
	#[validate(length(0..=254, "email must not be more than 254 characters"))]
	pub email: String,

	#[validate(length(3..=12, code = "size", message = "password must be between 3 and 12 characters"))]
	pub password: String,

	#[special(from_type(String))]
	#[modificate(lowercase)]
	#[parse(inline(|x: String| x.parse::<u16>().unwrap_or(0)))]
	#[validate(range(3..=12))]
	pub dependent_id: u16,

	#[modificate(trim)]
	#[validate(length(0..=254, "code must not be more than 254 characters"))]
	#[modificate(snake_case)]
	#[modificate(custom(modificate_code))]
	pub code: Option<String>,
}

fn modificate_code(code: &mut String, _field_name: &str) -> Result<(), ValidationError> {
	*code = code.clone() + "_code";
	Ok(())
}

#[test]
pub fn should_validate() {
	let mut wrapper = CreateUserDTOWrapper {
		name: None,
		email: Some("test@gmail.com".to_string()),
		password: None,
		dependent_id: Some("3".to_string()),
		code: None,
	};

	let result = CreateUserDTO::validate_and_parse(wrapper.clone());

	assert_errors!(result, wrapper.clone(), {
	  "name" => ("required", "name is required"),
	  "password" => ("required", "is required")
	});

	wrapper.name = Some("test".to_string());
	wrapper.password = Some("test".to_string());
	wrapper.code = Some("code test".to_string());

	let result = CreateUserDTO::validate_and_parse(wrapper.clone());

	assert_parsed!(
		result,
		wrapper,
		CreateUserDTO {
			name: "test".to_string(),
			email: "test@gmail.com".to_string(),
			password: "test".to_string(),
			dependent_id: 3,
			code: Some("code_test_code".to_string()),
		}
	);
}
```

If that example isn't enough, check [📁 More Examples](#-more-examples).

## 📓 Glossary

I've made some naming that might escape the standard, so I think this might be helpful:

```rust
use validy::core::Validate;

#[derive(Validate)]
//vvvvvvvv Struct attribute
#[validate(asynchronous, context = bool, payload)]
//---------^^^^^^^^^^^^ Configuration attribute
pub struct CreateUserExampleDTO {
  //vvvvvvvvvv Field attribute
  #[modificate(trim, lowercase)]
  //-----------^^^^ Rule
  #[validate(length(3..=120, "name must be between 3 and 120 characters"))]
  //----------------^^^^^^^ Rule arg 'range' value
  pub name: String,
  //-------------------------------vvvvvv Rule arg 'code' value
  #[validate(length(3..=12, code = "size", message = "password must be between 3 and 12 characters"))]
  //------------------------^^^^ Rule arg 'code' declaration
  pub password: String,
}
```

## 🔎 About Implementations

Almost all `rules` are executed from left to right and top to bottom, according to their field attribute and definition order.

I decided to avoid unnecessary `.clone()` calls for performance. Practically all rules only use references, the exceptions are the `allowlist` and `blocklist` rules, which need to clone the items (the field to be validated does not need to be cloned). Additionally, the `regex` rule and some rules with patterns needs to clone the `Arc` pointer from the cache.

It was also inevitable that the `parse` field attribute returns new values.

## 🔌 Axum Integration

When you enable the `axum` feature, the library automatically generates the `FromRequest` implementation for your `struct` if it has the `axum` configuration attribute enabled. The automated flow is as follows:

- *Extract:* receives the body.
- *Deserialize:* deserializes the body.
  - When the `payload` configuration attribute is enabled, the body is deserialized into [📨 Wrapper]#-wrappers.
- *Execute:* executes all `rules`.
- *Convert:* if successful, passes the final struct to the `handler`.
- *Error Handling:* if any step fails, returns `Bad Request` by default with the errors.

See an example:

```rust
use axum::{Json, extract::State, http::StatusCode, response::{Response, IntoResponse}};
use validy::core::{Validate, ValidateAndParse, ValidationError};
use std::{sync::Arc, fmt::Debug};

#[derive(Debug, Validate)]
#[validate(asynchronous, context = Arc<dyn UserService>, payload, axum)]
pub struct CreateUserDTO {
	#[modificate(trim)]
	#[validate(length(3..=120, "name must be between 3 and 120 characters"))]
	pub name: String,

	#[modificate(trim)]
	#[validate(length(0..=254, "email must not be more than 254 characters"))]
	#[validate(email("invalid email format"))]
	#[validate(async_custom_with_context(validate_unique_email))]
	pub email: String,

	#[validate(length(3..=12, code = "size", message = "password must be between 3 and 12 characters"))]
	pub password: String,
}

pub async fn create_user(
	State(service): State<Arc<dyn UserService>>,
	CreateUserDTO { name, email, password }: CreateUserDTO,
) -> Result<Response, Response> {
	service.create(name, email, password).await?;
	Ok(StatusCode::CREATED.into_response())
}

async fn validate_unique_email(
	email: &str,
	_field_name: &str,
	service: &Arc<dyn UserService>,
) -> Result<(), ValidationError> {
	let result = service.email_exists(email).await;

	match result {
		Ok(false) => Ok(()),
		Ok(true) => Err(ValidationError::builder()
			.with_field("email")
			.as_simple("unique")
			.with_message("email already in use")
			.build()
			.into()),
		Err(_) => {
			Err(ValidationError::builder()
				.with_field("email")
				.as_simple("internal error")
				.with_message("It wasn't possible to verify if the email is unique")
				.build()
				.into())
		}
	}
}

#[async_trait::async_trait]
pub trait UserService: Send + Sync + Debug {
	async fn create(&self, name: String, email: String, password: String) -> Result<(), Response>;
	async fn email_exists(&self, email: &str) -> Result<bool, Response>;
	//...
}
```

Yes, it's beautiful.

### Customizing the failure `status code`

You can change the HTTP status code returned on validation failure:

```rust
use validy::settings::ValidationSettings;
use axum::http::StatusCode;

ValidationSettings::set_failure_status_code(StatusCode::UNPROCESSABLE_ENTITY);
assert_eq!(ValidationSettings::get_failure_status_code(), StatusCode::UNPROCESSABLE_ENTITY);
```

This method is `thread-safe`. The default status code is `BAD_REQUEST`.

### Multipart support

When you enable the `axum_multipart` feature, the library automatically generates the `FromRequest` implementation for your `struct` with `axum_typed_multipart` if it has the `multipart` configuration attribute enabled. But you should still use `TryFromMultipart`.

```rust
use axum::{Json, extract::State, http::StatusCode, response::{Response, IntoResponse}};
use validy::core::{Validate, ValidateAndParse, ValidationError};
use std::{sync::Arc, fmt::Debug};
use tempfile::NamedTempFile;
use axum_typed_multipart::{FieldData, TryFromMultipart};

#[derive(Debug, Validate)]
#[validate(asynchronous, context = Arc<dyn UserService>, payload, axum, multipart)]
#[wrapper_derive(TryFromMultipart)]
pub struct CreateUserDTO {
  #[form_data(limit = "10MB")]
  #[validate(field_content_type(r"^(image/.*)$"))] //requires `axum_multipart_field_data` feature yet
  pub avatar: FieldData<NamedTempFile>,
  
  #[form_data(field_name = "user_name")]
	#[modificate(trim)]
	#[validate(length(3..=120, "name must be between 3 and 120 characters"))]
	pub name: String,

	#[modificate(trim)]
	#[validate(length(0..=254, "email must not be more than 254 characters"))]
	#[validate(email("invalid email format"))]
	#[validate(async_custom_with_context(validate_unique_email))]
	pub email: String,

	#[validate(length(3..=12, code = "size", message = "password must be between 3 and 12 characters"))]
	pub password: String,
}

pub async fn create_user(
	State(service): State<Arc<dyn UserService>>,
	CreateUserDTO { avatar, name, email, password }: CreateUserDTO,
) -> Result<Response, Response> {
	service.create(avatar, name, email, password).await?;
	Ok(StatusCode::CREATED.into_response())
}

async fn validate_unique_email(
	email: &str,
	_field_name: &str,
	service: &Arc<dyn UserService>,
) -> Result<(), ValidationError> {
	let result = service.email_exists(email).await;

	match result {
		Ok(false) => Ok(()),
		Ok(true) => Err(ValidationError::builder()
			.with_field("email")
			.as_simple("unique")
			.with_message("email already in use")
			.build()
			.into()),
		Err(_) => {
			Err(ValidationError::builder()
				.with_field("email")
				.as_simple("internal error")
				.with_message("It wasn't possible to verify if the email is unique")
				.build()
				.into())
		}
	}
}

#[async_trait::async_trait]
pub trait UserService: Send + Sync + Debug {
	async fn create(&self, avatar: FieldData<NamedTempFile>, name: String, email: String, password: String) -> Result<(), Response>;
	async fn email_exists(&self, email: &str) -> Result<bool, Response>;
	//...
}
```

Yes, it's beautiful too.

## 🧩 Manual Usage

The derive macros implement specific traits for your structs. To call methods like `.validate()`, `.async_validate()`, or `::validate_and_parse(...)`, you must import the corresponding traits into your scope.

```rust
use validy::core::{Validate, AsyncValidate, ValidateAndParse};

// Or just import the prelude
use validy::core::*;
```

### Available traits

| **Category** | **Traits** |
| :-------- | :------- |
| Validation | `Validate`, `AsyncValidate`, `ValidateWithContext<C>`, `SpecificValidateWithContext`, `AsyncValidateWithContext<C>`, and `SpecificAsyncValidateWithContext`. |
| Modification | `ValidateAndModificate`, `AsyncValidateAndModificate`, `ValidateAndModificateWithContext<C>`, `SpecificValidateAndModificateWithContext`, `AsyncValidateAndModificateWithContext<C>`, and `SpecificAsyncValidateAndModificateWithContext`. |
| Parsing | `ValidateAndParse<W>`, `SpecificValidateAndParse`, `AsyncValidateAndParse<W>`, `SpecificAsyncValidateAndParse`, `ValidateAndParseWithContext<W, C>`, `SpecificValidateAndParseWithContext`, `AsyncValidateAndParseWithContext<W, C>`, and `SpecificAsyncValidateAndParseWithContext`. |
| Error | `IntoValidationError` |

## 🚩 Feature Flags

The crate's behavior can be adjusted in your `Cargo.toml`.

| **Feature** | **Description** | **Dependencies** |
| :-------- | :------- | :------- |
| `default` | `derive`, `validation`, `modification`, `parsing` | | 
| `all` | Enables all features. | |
| `derive` | Enables macro functionality. | `dep:serde`, `dep:validation_derive` |
| `validation` | Enables validation functions. Needed by almost all not custom or inline validation rules. | |
| `modification` | Enables modification functions. Needed by almost all not custom or inline modification rules. | `dep:heck` |
| `parsing` | Enables parsing functions. Needed by all not custom or inline parsing rules. | |
| `uuid` | Enables `uuid` rules. | `dep:uuid` |
| `email` | Enables email rule. | `dep:email_address` |
| `pattern` | Enables `pattern` and `url` rules. Uses `moka` to cache compiled `regex` patterns. The cache can be configured by calling `ValidationSettings::set_regex_cache(...)`. | `dep:moka`, `dep:regex` | 
| `ip` | Enables ip rules. | |
| `time` | Enables time rules. | `dep:chrono` |
| `axum` | Enables Axum integration. | `dep:axum`, `derive` |
| `axum_multipart` | Enables multipart support. | `axum_typed_multipart`, `axum` |
| `axum_multipart_field_data` | Enables multipart field data rules " | `axum_multipart`, `pattern` |
| `macro_rules` | Enables macros for validation errors. | |
| `macro_rules_assertions` | Enables macros for assertions (tests). | `dep:pretty_assertions` |

## 🚧 Validation Rules

Primitive rules for the `#[validate(...)]` attribute.

> The '?' indicates that the argument is optional.

### For `required` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `required`(message = <?string>, code = <?string>) | Overrides the default message and code for a missing field. This rule requires the `payload` attribute to be enabled on the struct. |

### For `string` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `contains`(slice = \<string>, message = <?string>, code = <?string>) | Validates that the string contains the specified substring. |
| `uuid`(message = <?string>, code = <?string>) | Validates that the string is a valid UUID. This does not parse the string. |
| `email`(message = <?string>, code = <?string>) | Validates that the string follows a standard email format. |
| `url`(message = <?string>, code = <?string>) | Validates that the string is a standard URL. Finding good regex patterns for URLs is difficult and tedious, so I used the pattern `(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)` found [here]https://stackoverflow.com/a/3809435. |
| `ip`(message = <?string>, code = <?string>) | Validates that the string is a valid IP address (v4 or v6). |
| `ipv4`(message = <?string>, code = <?string>) | Validates that the string is a valid IPv4 address. |
| `ipv6`(message = <?string>, code = <?string>) | Validates that the string is a valid IPv6 address. |
| `pattern`(pattern = \<regex>, message = <?string>, code = <?string>) | Validates that the string matches the provided Regex pattern. |
| `suffix`(suffix = \<string>, message = <?string>, code = <?string>) | Validates that the string ends with the specified suffix. |
| `prefix`(prefix = \<string>, message = <?string>, code = <?string>) | Validates that the string starts with the specified prefix. |
| `length`(range = \<range>, message = <?string>, code = <?string>) | Validates that the length of a string or collection is within the specified range. |

### For `collection` or `single` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `length`(range = \<range>, message = <?string>, code = <?string>) | Validates that the length of a string or collection is within the specified range. |
| `allowlist`(mode = <"SINGLE" \| "COLLECTION">, items = \<array>, message = <?string>, code = <?string>) | Validates that the value or collection items are present in the allowlist. |
| `blocklist`(mode = <"SINGLE" \| "COLLECTION">, items = \<array>, message = <?string>, code = <?string>) | Validates that the value or collection items are NOT present in the blocklist. |

### For `numbers` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `range`(range = \<range>, message = <?string>, code = <?string>) | Validates that the number falls within the specified numeric range. |

### For `date` or `time` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `time`(format = \<string>, message = <?string>, code = <?string>) | Validates that the string matches the specified `DateTime<FixedOffset>` format. This does not parse the string. |
| `naive_time`(format = \<string>, message = <?string>, code = <?string>) | Validates that the string matches the specified `NaiveDateTime` format. This does not parse the string. |
| `naive_date`(format = \<string>, message = <?string>, code = <?string>) | Validates that the string matches the specified `NaiveDate` format. This does not parse the string. |
| `after_now`(accept_equals = <?bool>, message = <?string>, code = <?string>) | Validates that the `DateTime<FixedOffset>` is strictly after the current time. |
| `before_now`(accept_equals = <?bool>, message = <?string>, code = <?string>) | Validates that the `DateTime<FixedOffset>` is strictly before the current time. |
| `now`(ms_tolerance = <?int>, message = <?string>, code = <?string>) | Validates that the `DateTime<FixedOffset>` matches the current time within a tolerance (default: 500ms). |
| `after_today`(accept_equals = <?bool>, message = <?string>, code = <?string>) | Validates that the `NaiveDate` is strictly after the current day. |
| `before_today`(accept_equals = <?bool>, message = <?string>, code = <?string>) | Validates that the `NaiveDate` is strictly before the current day. |
| `today`(message = <?string>, code = <?string>) | Validates that the `NaiveDate` matches the current day. |

### For multipart `field data` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `field_name`(pattern = \<regex>, message = <?string>, code = <?string>) | Validates that the field name matches the provided Regex pattern. |
| `field_file_name`(pattern = \<regex>, message = <?string>, code = <?string>) | Validates that the field file name matches the provided Regex pattern. |
| `field_content_type`(pattern = \<regex>, message = <?string>, code = <?string>) | Validates that the field content type matches the provided Regex pattern. |

### Custom rules

All rules prefixed with `async_` require the `asynchronous` configuration attribute to be enabled. All rules suffixed with `_with_context` require the `context` configuration attribute to be defined.

| **Rule** | **Description** |
| :-------- | :------- |
| `inline`(closure = \<closure>, params = <?array>, message = <?string>, code = <?string>) | Validates using a simple inline closure returning a boolean. |
| `custom`(function = \<function>, params = <?array>) | Validates using a custom function. |
| `custom_with_context`(function = \<function>, params = <?array>) | Validates using a custom function with access to the context. |
| `async_custom`(function = \<function>, params = <?array>) | Validates using a custom async function. |
| `async_custom_with_context`(function = \<function>, params = <?array>) | Validates using a custom async function with access to the context. |

## 🔨 Modification Rules

Primitive rules for the `#[modificate(...)]` attribute. These all require either the `payload` or `modificate` attribute to be enabled on the struct.

> The '?' indicates that the argument is optional.

### For `string` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `trim` | Removes whitespace from both ends of the string. |
| `trim_start` | Removes whitespace from the start of the string. |
| `trim_end` | Removes whitespace from the end of the string. |
| `uppercase` | Converts all characters in the string to uppercase. |
| `lowercase` | Converts all characters in the string to lowercase. |
| `capitalize` | Capitalizes the first character of each word in the string. |
| `camel_case` | Converts the string to CamelCase (PascalCase). |
| `lower_camel_case` | Converts the string to lowerCamelCase. |
| `snake_case` | Converts the string to snake_case. |
| `shouty_snake_case` | Converts the string to SHOUTY_SNAKE_CASE. |
| `kebab_case` | Converts the string to kebab-case. |
| `shouty_kebab_case` | Converts the string to SHOUTY-KEBAB-CASE. |
| `train_case` | Converts the string to Train-Case. |

### Custom rules

All rules prefixed with `async_` require the `asynchronous` configuration attribute to be enabled. All rules suffixed with `_with_context` require the `context` configuration attribute to be defined.

| **Rule** | **Description** |
| :-------- | :------- |
| `inline`(closure = \<closure>, params = <?array>) | Modifies the value using an inline closure. |
| `custom`(function = \<function>, params = <?array>) | Modifies the value using a custom function. |
| `custom_with_context`(function = \<function>, params = <?array>) | Modifies the value using a custom function with context access. |
| `async_custom`(function = \<function>, params = <?array>) | Modifies the value using a custom async function. |
| `async_custom_with_context`(function = \<function>, params = <?array>) | Modifies the value using a custom async function with context access. |

## 🔧 Parsing Rules

Primitive rules for the `#[parse(...)]` attribute. These all require either the `payload` attribute to be enabled on the struct.

> The '?' indicates that the argument is optional.

### For `date` or `time` fields

All of these rules were created to be used with `#[special(from_type(String))]` declared before them.

| **Rule** | **Description** |
| :-------- | :------- |
| `parse_time`(format = \<string>, message = <?string>, code = <?string>) | Validates and parses a string into a `DateTime<FixedOffset>` matching the specified format. |
| `parse_naive_time`(format = \<string>, message = <?string>, code = <?string>) | Validates and parses a string into a `NaiveDateTime` matching the specified format. |
| `parse_naive_date`(format = \<string>, message = <?string>, code = <?string>) | Validates and parses a string into a `NaiveDate` matching the specified format. |

### For `uuid` fields

| **Rule** | **Description** |
| :-------- | :------- |
| `parse_uuid` | Validates and parses a string into a UUID. |

### Custom rules

All rules prefixed with `async_` require the `asynchronous` configuration attribute to be enabled. All rules suffixed with `_with_context` require the `context` configuration attribute to be defined.

| **Rule** | **Description** |
| :-------- | :------- |
| `inline`(closure = \<closure>, params = <?array>) | Parses the value using an inline closure. |
| `custom`(function = \<function>, params = <?array>) | Parses the value using a custom function. |
| `custom_with_context`(function = \<function>, params = <?array>) | Parses the value using a custom function with context access. |
| `async_custom`(function = \<function>, params = <?array>) | Parses the value using a custom async function. |
| `async_custom_with_context`(function = \<function>, params = <?array>) | Parses the value using a custom async function with context access. |

## 🔮 Special Rules

Primitive rules for the `#[special(...)]` attribute.

> The '?' indicates that the argument is optional.

| **Rule** | **Description** |
| :-------- | :------- |
| `nested`(value = <type>, wrapper = <?type>) | Validates the fields of a nested struct. Warning: cyclical references can cause compilation issues. |
| `ignore` | Ignores any validation or modification rule. |
| `for_each`(config?(from_item = <?type>, to_collection = <?type>, from_collection = <?type>), \<rule>) | Applies validation rules to every element in a collection. The `from_item` arg from the optional `config` rule defines the type of each collection item. The `to_collection` arg defines the final type of the collection, and the `from_collection` arg defines the initial type. It's like a `from_type` adapter for collections. |
| `from_type`(value = <?type>) | Defines the type of the field in the wrapper. Must be defined before all other rules on a field. |

## 📨 Wrappers

Wrappers are generated structs similar to the original struct where all fields are covered with `Option`. They all have the `Default` and `Debug` derive macros by default. And when the `multipart` configuration attribute is disabled, they also implement `Deserialize`. Ultimately, the only reason I could think of for having all optional fields was the deserialization and validation of required fields with custom errors.

The name of the wrapper struct is the name of the origional struct with the suffix 'Wrapper'. For example, `CreateUserDTO` generates a public wrapper named `CreateUserDTOWrapper`. The generated wrapper is left exposed for you to use. You also can use `#[wrapper_derive(...)]` struct attribute in the origional struct to apply derive macros on the wrapper.

The following field attributes are passed to the wrapper when its original struct has them in the fields:
- `serde` from `serde`.
- `field` from `axum_typed_multipart`.
- `form_data` from `axum_typed_multipart`.

The following field attributes are passed to the wrapper when its original struct has them in the fields:
- `try_from_multipart` from `axum_typed_multipart`.
- `serde` from `serde`

> It doesn't seem very difficult to me, so we'll probably have a way to apply any field attribute or struct attribute from other libraries soon. For now, this is a limitation.

## 📐 Useful Macros

Sometimes, you might prefer to use macros to declare errors or assertions.

### For `error` handling
  
All require the `macro_rules` feature flag to be enabled.

```rust
use validy::validation_error;
// SimpleValidationError
let error = validation_error!("field", "custom_code", "custom message");
```

```rust
use validy::validation_error;
// SimpleValidationError
let error = validation_error!("field", "custom_code");
```

```rust
use validy::validation_errors;
// ValidationErrors
let errors = validation_errors! {
  "a" => ("custom_code", "custom message"),
	"b" => ("nested", validation_errors! {
	  "c" => ("custom_code", "custom message"),
		"d" => [
		  ("custom_code", "custom message"), 
			("custom_code_2", "custom_message_2")
		]
	})
};
```

```rust
use validy::{nested_validation_error, validation_errors};
// NestedValidationError
let error = nested_validation_error!(
	"field",
	"custom_code",
	validation_errors! {
    "a" => ("custom_code", "custom message"),
	}
);
```

### For `test` assertions
  
All require the `macro_rules_assertions` feature flag to be enabled.

```rust
use validy::{
	assert_errors, assert_modification, assert_parsed, assert_validation,
	core::{Validate, ValidateAndModificate, ValidateAndParse},
};

#[derive(Debug, PartialEq, Clone, Default, Validate)]
struct Test {
	#[validate(range(1..=2, "out of range"))]
	a: u32,
}

let mut test = Test::default();
let result = Test::validate_and_parse(test.clone());

assert_errors!(result, test, {
  "a" => ("range", "out of range"),
});

test.a = 1;
let result = test.validate_and_modificate();
assert_validation!(result, test);
assert_modification!(test.a, 1, test);

let result = Test::validate_and_parse(test.clone());
assert_parsed!(result, test, Test { a: 1 });
```

## 💝 Complete Example

```rust
use axum::{Json, extract::State, http::StatusCode, response::{Response, IntoResponse}};
use validy::core::{Validate, ValidationError};
use std::{sync::Arc, fmt::Debug};

#[derive(Debug, Validate)]
#[validate(asynchronous, context = Arc<dyn UserService>, payload, axum)]
pub struct CreateUserExampleDTO {
	#[modificate(trim)]
	#[validate(length(3..=120, "name must be between 3 and 120 characters"))]
	#[validate(required("name is required"))] // Just changes the 'required' message.
	pub name: String,

	#[modificate(trim)]
	#[validate(email("invalid email format", "bad_format"))]
	#[validate(async_custom_with_context(validate_unique_email))]
	// You can pass extra arguments.
	//#[validate(async_custom_with_context(validate_unique_email, [&wrapper.name]))]
	// If 'payload' is false, you should replace 'wrapper' with 'self'.
	// Technically you can also access variables within the implementation, but I don't recommend it. 
	#[validate(inline(|_| true))] // Just an example.
	#[validate(length(0..=254, "email must not be more than 254 characters"))]
	pub email: String,
	
	// The order of a rule's arguments can be changed using the '=' operator.
	#[validate(length(3..=12, code = "size", message = "password must be between 3 and 12 characters"))]
	// However, positional argument order is still the priority.
	//#[validate(length(3..=12, "size", message = "password must be between 3 and 12 characters"))]
	// In the line above, "size" is treated as the message argument (which is then immediately overridden).
	pub password: String,

	#[special(from_type(String))] // 'dependent_id' will be deserialized as Option<String>.
	#[modificate(lowercase)] // You can modificate or validate it as a String, if it has a value.
	#[parse(inline(|x: String| x.parse::<u16>().unwrap_or(0)))] // You can then parse it to the final value type.
	#[modificate(inline(|x: &mut u16| *x = 3))] // And validate or modificate it again.
	#[validate(range(3..=12))] 
	pub dependent_id: u16,

	#[modificate(trim)]
	#[validate(length(0..=254, "tag must not be more than 254 characters"))]
	#[modificate(snake_case)]
	#[modificate(custom(modificate_tag))]
	pub tag: Option<String>, // 'tag' is truly optional.
	
	#[special(from_type(RoleWrapper))] // Required to correctly define the wrapper field type.
	#[special(nested(Role, RoleWrapper))] // Required to correctly validate nested content.
	// The wrapper type and the 'from_type' rule can be ignored when 'payload' is disabled.
	//#[special(nested(Role))]
	pub role: Option<Role>, // Can be optional or required.
	//pub role: Role,
}

// To use a struct in nested validations, it needs to derive 'Default'.
#[derive(Debug, Default, Validate)]
#[validate(payload, axum)]
pub struct Role {
	#[special(from_type(Vec<String>))]
	#[validate(length(1..=2))]
	#[special(for_each( // You can validate or modificate each item in a collection.
 	  config(from_item = String, from_collection = Vec<String>, to_collection = Vec<u32>),
    parse(inline(|x: String| x.parse::<u32>().unwrap_or(0))), // Just another parse example.
    validate(inline(|x: &u32| *x > 1)), // Just a validation example.
 	  modificate(inline(|x: &mut u32| *x += 1))
	))]
	pub permissions: Vec<u32>,
	
	#[special(from_type(Vec<String>))]
	#[special(for_each(
	  config(from_item = String, from_collection = Vec<String>, to_collection = Vec<u32>),
		parse(inline(|x: String| x.parse::<u32>().unwrap_or(0))),
	  validate(inline(|x: &u32| *x > 1)),
		modificate(inline(|x: &mut u32| *x += 1))
	))]
	pub alt_permissions: Vec<u32>,
}

// As a rule, the input for custom functions is '(&field, &field_name)'.
// All custom modification rules can also throw validation errors.
// Unfortunately, each modification has to return a new value instead of changing the existing one in-place.
// This ensures that changes are only committed at the end of the validation process.
fn modificate_tag(tag: &mut String, _field_name: &str) -> Result<(), ValidationError> {
	*tag = "new_tag".to_string();
	Ok(())
}

// Custom functions can be async instead of sync.
// With context, or not. See 'custom', 'custom_with_context', 'async_custom',
// 'async_custom_with_context', and 'inline' rules.
async fn validate_unique_email(
	email: &str,
	field_name: &str,
	service: &Arc<dyn UserService>, // Only if context is provided.
	//name: &str                    // Example with extra arguments.
) -> Result<(), ValidationError> {
	let result = service.email_exists(email).await;

	match result {
		Ok(false) => Ok(()),
		Ok(true) => Err(ValidationError::builder()
			.with_field(field_name.to_string())
			.as_simple("unique")
			.with_message("e-mail must be unique")
			.build()
			.into()),
		Err(_) => { // Simplified error handling
			Err(ValidationError::builder()
				.with_field(field_name.to_string())
				.as_simple("internal")
				.with_message("internal error")
				.build()
				.into())
		}
	}
}

pub async fn create_user(
	State(service): State<Arc<dyn UserService>>,
	body: CreateUserExampleDTO,
) -> Result<Response, Response> {
	service.create(body.name, body.email, body.password).await?;
	Ok(StatusCode::CREATED.into_response())
}

#[async_trait::async_trait]
pub trait UserService: Send + Sync + Debug {
  async fn create(&self, name: String, email: String, password: String) -> Result<(), Response>;
	async fn email_exists(&self, email: &str) -> Result<bool, Response>;
	//...
}
```

## 📁 More Examples

If the examples aren't enough, I've included a more complete and documented example [here](/docs/complete_example.md). You also can use the [tests](/tests) as a reference.

## 🎯 Work In Progress

Some of these features are available now, but are only partially finished. I will document them fully once they are complete.

- [x] More test coverage.
- [x] Custom validation status code.
- [x] Typed multipart/form-data validation support.
  - [x] File validation rules.
- [x] Validation rules for uuid.
- [x] Better documentation.
- [ ] Failure mode.
  - The current default is `FailOncePerField` (covered by the tests).
- [ ] Fully support for external crates field and structs attributes.
- [ ] Validation rules for decimal (maybe).

## 🎁 For Developers

You can run all tests with `cargo test-all`. To see the generated code from the `derive` macros, you can run the `expand.sh` script (this requires `cargo expand`). It will compile, generate, and check all tests.

> This is a personal project maintained by a graduate student. Maintenance may be limited, but I do my best to keep it in good shape.