state_validation/lib.rs
1//! ## State Validation
2//! `state-validation` lets you validate an input for a given state. Then, run an action using the validated output.
3//!
4//! Ex. You want to remove an admin from `UserStorage`, given a `UserID`, you want to retrieve the `User` who maps onto the `UserID` and validate they are an existing user whose privilege level is admin.
5//! The state is `UserStorage`, the input is a `UserID`, and the valid output is an `AdminUser`.
6//!
7//! Here is our input `UserID`:
8//! ```
9//! # use std::collections::{HashSet, HashMap};
10//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
11//! #[derive(Hash, PartialEq, Eq, Clone, Copy)]
12//! struct UserID(usize);
13//! ```
14//! Our `User` which holds its `UserID` and `username`:
15//! ```
16//! # use std::collections::{HashSet, HashMap};
17//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
18//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
19//! # struct UserID(usize);
20//! #[derive(Clone)]
21//! struct User {
22//! id: UserID,
23//! username: String,
24//! }
25//! ```
26//! Our state will be `UserStorage`:
27//! ```
28//! # use std::collections::{HashSet, HashMap};
29//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
30//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
31//! # struct UserID(usize);
32//! # #[derive(Clone)]
33//! # struct User {
34//! # id: UserID,
35//! # username: String,
36//! # }
37//! #[derive(Default)]
38//! struct UserStorage {
39//! maps: HashMap<UserID, User>,
40//! }
41//! ```
42//! We will create a newtype `AdminUser`, which only users with admin privilege will be wrapped in.
43//! This will also be the output of our filter which checks if a user is admin:
44//! ```
45//! # use std::collections::{HashSet, HashMap};
46//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
47//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
48//! # struct UserID(usize);
49//! # #[derive(Clone)]
50//! # struct User {
51//! # id: UserID,
52//! # username: String,
53//! # }
54//! # #[derive(Default)]
55//! # struct UserStorage {
56//! # maps: HashMap<UserID, User>,
57//! # }
58//! struct AdminUser(User);
59//! ```
60//! Our first filter will check if a `User` exists given a `UserID`:
61//! ```
62//! # use std::collections::{HashSet, HashMap};
63//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
64//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
65//! # struct UserID(usize);
66//! # #[derive(Clone)]
67//! # struct User {
68//! # id: UserID,
69//! # username: String,
70//! # }
71//! # #[derive(Default)]
72//! # struct UserStorage {
73//! # maps: HashMap<UserID, User>,
74//! # }
75//! # struct AdminUser(User);
76//! struct UserExists;
77//! # #[derive(Debug)]
78//! # struct UserDoesNotExistError;
79//! # impl std::error::Error for UserDoesNotExistError {}
80//! # impl std::fmt::Display for UserDoesNotExistError {
81//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82//! # write!(f, "user does not exist")
83//! # }
84//! # }
85//! impl StateFilter<UserStorage, UserID> for UserExists {
86//! type ValidOutput = User;
87//! type Error = UserDoesNotExistError;
88//! fn filter(state: &UserStorage, user_id: UserID) -> Result<Self::ValidOutput, Self::Error> {
89//! if let Some(user) = state.maps.get(&user_id) {
90//! Ok(user.clone())
91//! } else {
92//! Err(UserDoesNotExistError)
93//! }
94//! }
95//! }
96//! ```
97//! Our second filter will check if a `User` is admin:
98//! ```
99//! # use std::collections::{HashSet, HashMap};
100//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
101//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
102//! # struct UserID(usize);
103//! # #[derive(Clone)]
104//! # struct User {
105//! # id: UserID,
106//! # username: String,
107//! # }
108//! # #[derive(Default)]
109//! # struct UserStorage {
110//! # maps: HashMap<UserID, User>,
111//! # }
112//! # struct AdminUser(User);
113//! #
114//! struct UserIsAdmin;
115//! # #[derive(Debug)]
116//! # struct UserIsNotAdminError;
117//! # impl std::error::Error for UserIsNotAdminError {}
118//! # impl std::fmt::Display for UserIsNotAdminError {
119//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
120//! # write!(f, "user is not an admin")
121//! # }
122//! # }
123//! impl<State> StateFilter<State, User> for UserIsAdmin {
124//! type ValidOutput = AdminUser;
125//! type Error = UserIsNotAdminError;
126//! fn filter(state: &State, user: User) -> Result<Self::ValidOutput, Self::Error> {
127//! if user.username == "ADMIN" {
128//! Ok(AdminUser(user))
129//! } else {
130//! Err(UserIsNotAdminError)
131//! }
132//! }
133//! }
134//! ```
135//! Note: in the above code, we don't care about the `state` so it is a generic.
136//!
137//! Now, we can finally implement an action that removes the admin from user storage:
138//! ```
139//! # use std::collections::{HashSet, HashMap};
140//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
141//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
142//! # struct UserID(usize);
143//! # #[derive(Clone)]
144//! # struct User {
145//! # id: UserID,
146//! # username: String,
147//! # }
148//! # #[derive(Default)]
149//! # struct UserStorage {
150//! # maps: HashMap<UserID, User>,
151//! # }
152//! # struct AdminUser(User);
153//! # struct UserExists;
154//! # #[derive(Debug)]
155//! # struct UserDoesNotExistError;
156//! # impl std::error::Error for UserDoesNotExistError {}
157//! # impl std::fmt::Display for UserDoesNotExistError {
158//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
159//! # write!(f, "user does not exist")
160//! # }
161//! # }
162//! # impl StateFilter<UserStorage, UserID> for UserExists {
163//! # type ValidOutput = User;
164//! # type Error = UserDoesNotExistError;
165//! # fn filter(state: &UserStorage, user_id: UserID) -> Result<Self::ValidOutput, Self::Error> {
166//! # if let Some(user) = state.maps.get(&user_id) {
167//! # Ok(user.clone())
168//! # } else {
169//! # Err(UserDoesNotExistError)
170//! # }
171//! # }
172//! # }
173//! # struct UserIsAdmin;
174//! # #[derive(Debug)]
175//! # struct UserIsNotAdminError;
176//! # impl std::error::Error for UserIsNotAdminError {}
177//! # impl std::fmt::Display for UserIsNotAdminError {
178//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
179//! # write!(f, "user is not an admin")
180//! # }
181//! # }
182//! # impl<State> StateFilter<State, User> for UserIsAdmin {
183//! # type ValidOutput = AdminUser;
184//! # type Error = UserIsNotAdminError;
185//! # fn filter(state: &State, user: User) -> Result<Self::ValidOutput, Self::Error> {
186//! # if user.username == "ADMIN" {
187//! # Ok(AdminUser(user))
188//! # } else {
189//! # Err(UserIsNotAdminError)
190//! # }
191//! # }
192//! # }
193//! #
194//! struct RemoveAdmin;
195//! impl ValidAction<UserStorage, UserID> for RemoveAdmin {
196//! // To chain filters, use `Condition`.
197//! type Filter = (
198//! // <Input, Filter>
199//! Condition<UserID, UserExists>,
200//! // Previous filter outputs a `User`,
201//! // so next filter can take `User` as input.
202//! Condition<User, UserIsAdmin>,
203//! );
204//! type Output = UserStorage;
205//! fn with_valid_input(
206//! self,
207//! mut state: UserStorage,
208//! // The final output from the filters is an `AdminUser`.
209//! admin_user: <Self::Filter as StateFilter<UserStorage, UserID>>::ValidOutput,
210//! ) -> Self::Output {
211//! let _ = state.maps.remove(&admin_user.0.id).unwrap();
212//! state
213//! }
214//! }
215//! ```
216//! Now, let's put it all together. We create the state `UserStorage`,
217//! and then use [`Validator::try_new`] to run our filters. An error is returned if any of the filters fail,
218//! otherwise we get a validator that we can run an action on:
219//! ```
220//! # use std::collections::{HashSet, HashMap};
221//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
222//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
223//! # struct UserID(usize);
224//! # #[derive(Clone)]
225//! # struct User {
226//! # id: UserID,
227//! # username: String,
228//! # }
229//! # #[derive(Default)]
230//! # struct UserStorage {
231//! # maps: HashMap<UserID, User>,
232//! # }
233//! # struct AdminUser(User);
234//! # struct UserExists;
235//! # #[derive(Debug)]
236//! # struct UserDoesNotExistError;
237//! # impl std::error::Error for UserDoesNotExistError {}
238//! # impl std::fmt::Display for UserDoesNotExistError {
239//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
240//! # write!(f, "user does not exist")
241//! # }
242//! # }
243//! # impl StateFilter<UserStorage, UserID> for UserExists {
244//! # type ValidOutput = User;
245//! # type Error = UserDoesNotExistError;
246//! # fn filter(state: &UserStorage, user_id: UserID) -> Result<Self::ValidOutput, Self::Error> {
247//! # if let Some(user) = state.maps.get(&user_id) {
248//! # Ok(user.clone())
249//! # } else {
250//! # Err(UserDoesNotExistError)
251//! # }
252//! # }
253//! # }
254//! # struct UserIsAdmin;
255//! # #[derive(Debug)]
256//! # struct UserIsNotAdminError;
257//! # impl std::error::Error for UserIsNotAdminError {}
258//! # impl std::fmt::Display for UserIsNotAdminError {
259//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
260//! # write!(f, "user is not an admin")
261//! # }
262//! # }
263//! # impl<State> StateFilter<State, User> for UserIsAdmin {
264//! # type ValidOutput = AdminUser;
265//! # type Error = UserIsNotAdminError;
266//! # fn filter(state: &State, user: User) -> Result<Self::ValidOutput, Self::Error> {
267//! # if user.username == "ADMIN" {
268//! # Ok(AdminUser(user))
269//! # } else {
270//! # Err(UserIsNotAdminError)
271//! # }
272//! # }
273//! # }
274//! #
275//! # struct RemoveAdmin;
276//! # impl ValidAction<UserStorage, UserID> for RemoveAdmin {
277//! # type Filter = (
278//! # Condition<UserID, UserExists>,
279//! # Condition<User, UserIsAdmin>,
280//! # );
281//! # type Output = UserStorage;
282//! # fn with_valid_input(
283//! # self,
284//! # mut state: UserStorage,
285//! # admin_user: <Self::Filter as StateFilter<UserStorage, UserID>>::ValidOutput,
286//! # ) -> Self::Output {
287//! # let _ = state.maps.remove(&admin_user.0.id).unwrap();
288//! # state
289//! # }
290//! # }
291//! #
292//! // State setup
293//! let mut user_storage = UserStorage::default();
294//! user_storage.maps.insert(UserID(0), User {
295//! id: UserID(0),
296//! username: "ADMIN".to_string(),
297//! });
298//!
299//! // Create validator which will validate the input.
300//! // No error is returned if validation succeeds.
301//! let validator = Validator::try_new(user_storage, UserID(0)).expect("admin user did not exist");
302//!
303//! // Execute an action which requires the state and input above.
304//! let user_storage = validator.execute(RemoveAdmin);
305//!
306//! assert!(user_storage.maps.is_empty());
307//! ```
308//! ---
309//! Another example using `UserExists` filter, and `UserIsAdmin` filter in the body of the [`ValidAction`]:
310//! ```
311//! # use std::collections::{HashSet, HashMap};
312//! # use state_validation::{Validator, ValidAction, StateFilter, Condition};
313//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
314//! # struct UserID(usize);
315//! #
316//! # #[derive(Default)]
317//! # struct UserStorage {
318//! # maps: HashMap<UserID, User>,
319//! # }
320//! # #[derive(Clone)]
321//! # struct User {
322//! # id: UserID,
323//! # username: String,
324//! # }
325//! #
326//! # struct AdminUser(User);
327//! #
328//! # struct UserExists;
329//! # #[derive(Debug)]
330//! # struct UserDoesNotExistError;
331//! # impl std::error::Error for UserDoesNotExistError {}
332//! # impl std::fmt::Display for UserDoesNotExistError {
333//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
334//! # write!(f, "user does not exist")
335//! # }
336//! # }
337//! # impl StateFilter<UserStorage, UserID> for UserExists {
338//! # type ValidOutput = User;
339//! # type Error = UserDoesNotExistError;
340//! # fn filter(state: &UserStorage, user_id: UserID) -> Result<Self::ValidOutput, Self::Error> {
341//! # if let Some(user) = state.maps.get(&user_id) {
342//! # Ok(user.clone())
343//! # } else {
344//! # Err(UserDoesNotExistError)
345//! # }
346//! # }
347//! # }
348//! #
349//! # struct UserIsAdmin;
350//! # #[derive(Debug)]
351//! # struct UserIsNotAdminError;
352//! # impl std::error::Error for UserIsNotAdminError {}
353//! # impl std::fmt::Display for UserIsNotAdminError {
354//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
355//! # write!(f, "user is not an admin")
356//! # }
357//! # }
358//! # impl StateFilter<UserStorage, User> for UserIsAdmin {
359//! # type ValidOutput = AdminUser;
360//! # type Error = UserIsNotAdminError;
361//! # fn filter(state: &UserStorage, user: User) -> Result<Self::ValidOutput, Self::Error> {
362//! # if user.username == "ADMIN" {
363//! # Ok(AdminUser(user))
364//! # } else {
365//! # Err(UserIsNotAdminError)
366//! # }
367//! # }
368//! # }
369//! #
370//! enum Privilege {
371//! None,
372//! Admin,
373//! }
374//!
375//! struct UpdateUserPrivilege(Privilege);
376//! impl ValidAction<UserStorage, UserID> for UpdateUserPrivilege {
377//! type Filter = UserExists;
378//! type Output = UserStorage;
379//! fn with_valid_input(
380//! self,
381//! mut state: UserStorage,
382//! mut user: <Self::Filter as StateFilter<UserStorage, UserID>>::ValidOutput,
383//! ) -> Self::Output {
384//! match self.0 {
385//! Privilege::None => {
386//! if let Ok(AdminUser(mut user)) = UserIsAdmin::filter(&state, user) {
387//! user.username = "NOT_ADMIN".to_string();
388//! let _ = state.maps.insert(user.id, user);
389//! }
390//! }
391//! Privilege::Admin => {
392//! user.username = "ADMIN".to_string();
393//! let _ = state.maps.insert(user.id, user);
394//! }
395//! }
396//! state
397//! }
398//! }
399//!
400//! # let mut user_storage = UserStorage::default();
401//! # user_storage.maps.insert(UserID(0), User {
402//! # id: UserID(0),
403//! # username: "ADMIN".to_string(),
404//! # });
405//! # assert_eq!(user_storage.maps.len(), 1);
406//! # let user = user_storage.maps.get(&UserID(0));
407//! # assert!(user.is_some());
408//! # assert_eq!(user.unwrap().username, "ADMIN");
409//!
410//! // This `Validator` has its generic `Filter` parameter implicitly changed
411//! // based on what action we call. On a type level, this is a different type
412//! // than the validator in the previous example even though we write the same code
413//! // due to its `Filter` generic parameter being different.
414//! let validator = Validator::try_new(user_storage, UserID(0)).expect("user did not exist");
415//!
416//! let user_storage = validator.execute(UpdateUserPrivilege(Privilege::None));
417//!
418//! # assert_eq!(user_storage.maps.len(), 1);
419//! let user = user_storage.maps.get(&UserID(0));
420//! # assert!(user.is_some());
421//! assert_eq!(user.unwrap().username, "NOT_ADMIN");
422//! ```
423//!
424//! ## [`StateFilterInputConversion`] & [`StateFilterInputCombination`]
425//! Automatic implementations of [`StateFilterInputConversion`] and [`StateFilterInputCombination`]
426//! are generated with the [`StateFilterConversion`] derive macro.
427//! Example:
428//! ```
429//! # use state_validation::StateFilterConversion;
430//! # struct UserID(usize);
431//! # struct User;
432//! #[derive(StateFilterConversion)]
433//! struct UserWithUserName {
434//! #[conversion(User)]
435//! user_id: UserID,
436//! username: String,
437//! }
438//! ```
439//! Now, `UserWithUsername` can be broken down into `User`, `UserID`, and `String`.
440//! Take advantage of the newtype pattern to breakdown the input further.
441//! For example, instead of having username as a `String`, use:
442//! ```
443//! struct Username(String);
444//! ```
445//! This way, the compiler can differentiate between a `String` and a `Username`.
446//!
447//!
448//! The [`StateFilterInputConversion`] and [`StateFilterInputCombination`] traits work together
449//! to allow splitting the input down into its parts and then back together.
450//!
451//! The usefulness of this is, if a [`StateFilter`] only requires a part of the input,
452//! [`StateFilterInputConversion`] can split it down to just that part, and leave the rest in [`StateFilterInputConversion::Remainder`]
453//! which will be combined with the output of the filter. And, each consecutive filter can split
454//! whatever input they desire and combine their output with the remainder they did not touch.
455//!
456//! Here is an example with manual implementations: Assume we wanted to change the username of a user,
457//! if its `UserID` and current username matched that of the input.
458//! First, let's create its input:
459//! ```
460//! # struct UserID(usize);
461//! struct UserWithUsername {
462//! user_id: UserID,
463//! username: String,
464//! }
465//! ```
466//!
467//! We want to check if a user with `UserID` exists, and their current username is `username`, and then update their username.
468//! We already have a filter that will check if a user exists. It is called: `UserExists`.
469//! But, `UserExists` does not take `UserWithUsername` as an input. It only takes `UserID` as input.
470//! `UserWithUsername` does contain a `UserID`. So, we should be able to retrieve the `UserID`,
471//! pass it into `UserExists`, then reconstruct the output of `UserExists` with the leftover `username`.
472//! The first part of solving this issue is input conversion into `UserID`, so we implement [`StateFilterInputConversion`]:
473//! ```
474//! # use state_validation::StateFilterInputConversion;
475//! # struct UserID(usize);
476//! # struct UserWithUsername {
477//! # user_id: UserID,
478//! # username: String,
479//! # }
480//! struct UsernameForUserID(String);
481//! impl StateFilterInputConversion<UserID> for UserWithUsername {
482//! type Remainder = UsernameForUserID;
483//! fn split_take(self) -> (UserID, Self::Remainder) {
484//! (self.user_id, UsernameForUserID(self.username))
485//! }
486//! }
487//! ```
488//! Notice in the above code, within `split_take`, the first element of the tuple is the input our `UserExists` filter cares about.
489//! However, for the `Remainder`, we have a newtype which stores the leftover to combine later with the output of `UserExists`.
490//! ```
491//! # use state_validation::StateFilterInputCombination;
492//! # struct User;
493//! # struct UserID(usize);
494//! # struct UsernameForUserID(String);
495//! struct UsernameForUser {
496//! user: User,
497//! username: String,
498//! }
499//! impl StateFilterInputCombination<User> for UsernameForUserID {
500//! type Combined = UsernameForUser;
501//! fn combine(self, user: User) -> Self::Combined {
502//! UsernameForUser {
503//! user,
504//! username: self.0,
505//! }
506//! }
507//! }
508//! ```
509//! Now, the combination of `UserExists`'s output (which is a `User`) and the leftover `username`,
510//! results in a new struct called `UsernameForUser`.
511//!
512//! Now, we need a new filter that checks if the username of the `User` is equal to `username`.
513//! ```
514//! # use state_validation::StateFilter;
515//! # struct User {
516//! # username: String,
517//! # }
518//! # struct UsernameForUser {
519//! # user: User,
520//! # username: String,
521//! # }
522//! struct UsernameEquals;
523//! # #[derive(Debug)]
524//! # struct UsernameIsNotEqual;
525//! # impl std::error::Error for UsernameIsNotEqual {}
526//! # impl std::fmt::Display for UsernameIsNotEqual {
527//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
528//! # write!(f, "username is not equal")
529//! # }
530//! # }
531//! impl<State> StateFilter<State, UsernameForUser> for UsernameEquals {
532//! type ValidOutput = User;
533//! type Error = UsernameIsNotEqual;
534//! fn filter(state: &State, value: UsernameForUser) -> Result<Self::ValidOutput, Self::Error> {
535//! if value.user.username == value.username {
536//! Ok(value.user)
537//! } else {
538//! Err(UsernameIsNotEqual)
539//! }
540//! }
541//! }
542//! ```
543//!
544//! Finally, we can run our action to update the user's username:
545//! ```
546//! # use std::collections::HashMap;
547//! # use state_validation::{ValidAction, Condition, StateFilter, StateFilterInputConversion, StateFilterInputCombination};
548//! # #[derive(Hash, PartialEq, Eq, Clone, Copy)]
549//! # struct UserID(usize);
550//! # #[derive(Clone)]
551//! # struct User {
552//! # id: UserID,
553//! # username: String,
554//! # }
555//! # #[derive(Default)]
556//! # struct UserStorage {
557//! # maps: HashMap<UserID, User>,
558//! # }
559//! # struct UserExists;
560//! # #[derive(Debug)]
561//! # struct UserDoesNotExistError;
562//! # impl std::error::Error for UserDoesNotExistError {}
563//! # impl std::fmt::Display for UserDoesNotExistError {
564//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
565//! # write!(f, "user does not exist")
566//! # }
567//! # }
568//! # impl StateFilter<UserStorage, UserID> for UserExists {
569//! # type ValidOutput = User;
570//! # type Error = UserDoesNotExistError;
571//! # fn filter(state: &UserStorage, user_id: UserID) -> Result<Self::ValidOutput, Self::Error> {
572//! # if let Some(user) = state.maps.get(&user_id) {
573//! # Ok(user.clone())
574//! # } else {
575//! # Err(UserDoesNotExistError)
576//! # }
577//! # }
578//! # }
579//! # struct UserWithUsername {
580//! # user_id: UserID,
581//! # username: String,
582//! # }
583//! # struct UsernameForUserID(String);
584//! # impl StateFilterInputConversion<UserID> for UserWithUsername {
585//! # type Remainder = UsernameForUserID;
586//! # fn split_take(self) -> (UserID, Self::Remainder) {
587//! # (self.user_id, UsernameForUserID(self.username))
588//! # }
589//! # }
590//! # struct UsernameForUser {
591//! # user: User,
592//! # username: String,
593//! # }
594//! # impl StateFilterInputCombination<User> for UsernameForUserID {
595//! # type Combined = UsernameForUser;
596//! # fn combine(self, user: User) -> Self::Combined {
597//! # UsernameForUser {
598//! # user,
599//! # username: self.0,
600//! # }
601//! # }
602//! # }
603//! # struct UsernameEquals;
604//! # #[derive(Debug)]
605//! # struct UsernameIsNotEqual;
606//! # impl std::error::Error for UsernameIsNotEqual {}
607//! # impl std::fmt::Display for UsernameIsNotEqual {
608//! # fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
609//! # write!(f, "username is not equal")
610//! # }
611//! # }
612//! # impl<State> StateFilter<State, UsernameForUser> for UsernameEquals {
613//! # type ValidOutput = User;
614//! # type Error = UsernameIsNotEqual;
615//! # fn filter(state: &State, value: UsernameForUser) -> Result<Self::ValidOutput, Self::Error> {
616//! # if value.user.username == value.username {
617//! # Ok(value.user)
618//! # } else {
619//! # Err(UsernameIsNotEqual)
620//! # }
621//! # }
622//! # }
623//! struct UpdateUsername {
624//! new_username: String,
625//! }
626//! impl ValidAction<UserStorage, UserWithUsername> for UpdateUsername {
627//! type Filter = (
628//! Condition<UserID, UserExists>,
629//! Condition<UsernameForUser, UsernameEquals>,
630//! );
631//! type Output = UserStorage;
632//! fn with_valid_input(
633//! self,
634//! mut state: UserStorage,
635//! mut user: <Self::Filter as StateFilter<UserStorage, UserWithUsername>>::ValidOutput,
636//! ) -> Self::Output {
637//! user.username = self.new_username;
638//! let _ = state.maps.insert(user.id, user);
639//! state
640//! }
641//! }
642//! ```
643//!
644//! ## Soundness Rules
645//! [`Validator::try_new`] takes ownership of the `state` to disallow consecutive
646//! [`Validator::execute`] calls because an action is assumed to mutate the `state`.
647//! Since an action is assumed to mutate the `state`, any validators using the same `state`
648//! cannot be created.
649//!
650//! It is up to you to make sure the filters properly validate what they promise.
651//!
652//! ## Limitations
653//! Currently, the amount of filters that can be chained is eight.
654//! The reason for this is because of variadics not being supported as of Rust 2024.
655//! Having no more than eight implementations is arbitrary because having more than eight filters is unlikely.
656//! There is no reason not to implement more in the future, if more than eight filters are required.
657
658mod action;
659mod condition;
660#[cfg(feature = "input_collector")]
661mod input_collector;
662mod state_filter;
663pub use action::*;
664pub use condition::*;
665#[cfg(feature = "input_collector")]
666pub use input_collector::*;
667pub use state_filter::*;
668#[cfg(feature = "derive")]
669pub use state_validation_derive::*;
670
671pub struct Validator<State, Input, Filter: StateFilter<State, Input>> {
672 state: State,
673 value: Filter::ValidOutput,
674 _p: std::marker::PhantomData<(Input, Filter)>,
675}
676
677impl<State, Input, Filter: StateFilter<State, Input>> Validator<State, Input, Filter> {
678 pub fn try_new(state: State, input: Input) -> Result<Self, Filter::Error> {
679 let value = Filter::filter(&state, input)?;
680 Ok(Validator {
681 state,
682 value,
683 _p: std::marker::PhantomData::default(),
684 })
685 }
686 pub fn execute<Action: ValidAction<State, Input, Filter = Filter>>(
687 self,
688 valid_action: Action,
689 ) -> Action::Output {
690 valid_action.with_valid_input(self.state, self.value)
691 }
692}