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
#![cfg_attr(docsrs, feature(doc_cfg))]
//! # What is `prae`?
//!
//! This crate aims to provide a better way to define types that require
//! validation. `prae` **is not** a validation library, but a library that
//! **helps developers** to define validation-requiring types with **very little
//! effort**.
//!
//! # How it works?
//!
//! The main way to use `prae` is through [`define!`](crate::define) macro.
//!
//! For example, suppose you want to create a `Username` type. You want this
//! type to be a string, and you don't want it to be empty. Traditionally, would
//! create a wrapper struct with getter and setter functions, like this
//! simplified example:
//! ```
//! #[derive(Debug)]
//! pub struct Username(String);
//!
//! impl Username {
//! pub fn new(username: &str) -> Result<Self, &'static str> {
//! let username = username.trim().to_owned();
//! if username.is_empty() {
//! Err("value is invalid")
//! } else {
//! Ok(Self(username))
//! }
//! }
//!
//! pub fn get(&self) -> &str {
//! &self.0
//! }
//!
//! pub fn set(&mut self, username: &str) -> Result<(), &'static str> {
//! let username = username.trim().to_owned();
//! if username.is_empty() {
//! Err("value is invalid")
//! } else {
//! self.0 = username;
//! Ok(())
//! }
//! }
//! }
//!
//! let username = Username::new(" my username ").unwrap();
//! assert_eq!(username.get(), "my username");
//!
//! let err = Username::new(" ").unwrap_err();
//! assert_eq!(err, "value is invalid");
//! ```
//!
//! Using `prae`, you will do it like this:
//! ```
//! use prae::define;
//!
//! define! {
//! pub Username: String
//! adjust |username| *username = username.trim().to_owned()
//! ensure |username| !username.is_empty()
//! }
//!
//! let username = Username::new(" my username ").unwrap();
//! assert_eq!(username.get(), "my username");
//!
//! let err = Username::new(" ").unwrap_err();
//! assert_eq!(err.inner, "value is invalid");
//! assert_eq!(err.value, "");
//! ```
//!
//! Futhermore, `prae` allows you to use custom errors and extend your types.
//! See docs for [`define!`](crate::define) for more information and examples.
//!
//! # Additional features
//!
//! ## `*_unprocessed` functions
//!
//! By default, all methods of the wrappers generated by
//! [`define!`](crate::define) (which are just aliases of the
//! [`Bounded`](crate::Bounded) type) will run the adjustment/validation (if
//! present) routines on every construction and mutation.
//!
//! If you find yourself in a situation where you know for sure that some
//! construction/mutation is valid, you can opt out of this using
//! `*_unprocessed` functions (e.g. `foo.set_unprocessed(value)` instead of
//! `foo.set(value)`) and save a bit of computations.
//!
//! To be able to use these functions, just enable the `unprocessed`
//! feature of the crate.
//!
//! ## Serde integration
//!
//! You can enable serde integration with the `serde` feature. It will implement
//! [`Serialize`](serde::Serialize) and [`Deserialize`](serde::Deserialize)
//! traits for the wrappers if their inner type implements them. The
//! deserialization will automatically fail if it contains invalid value. Here
//! is an example:
//!
//! ```
//! use serde::{Deserialize, Serialize};
//! use prae::define;
//!
//! define! {
//! Username: String
//! adjust |username| *username = username.trim().to_string()
//! validate(&'static str) |username| {
//! if username.is_empty() {
//! Err("username is empty")
//! } else {
//! Ok(())
//! }
//! }
//! }
//!
//! #[derive(Debug, Deserialize, Serialize)]
//! struct User {
//! username: Username,
//! }
//!
//! // Serialization works as expected.
//! let u = User {
//! username: Username::new(" john doe ").unwrap(),
//! };
//! let j = serde_json::to_string(&u).unwrap();
//! assert_eq!(j, r#"{"username":"john doe"}"#);
//!
//! // Deserialization with invalid data fails.
//! let e = serde_json::from_str::<User>(r#"{ "username": " " }"#).unwrap_err();
//! assert_eq!(e.to_string(), "username is empty at line 1 column 20");
//!
//! // And here we get a nice adjusted value.
//! let u = serde_json::from_str::<User>(r#"{ "username": " john doe " }"#).unwrap();
//! assert_eq!(u.username.get(), "john doe");
//! ```
//!
//! # Drawbacks
//!
//! Although proc macros are very powerful, they aren't free. In this case, you
//! have to pull up additional dependencies such as [`syn`](https://docs.rs/syn) and
//! [`quote`](https://docs.rs/quote), and expect a slightly slower compile times.
mod core;
pub use crate::core::*;
/// A macro that makes defining new types easy.
///
/// This macro uses custom syntax described below and introduces two new types
/// to the scope: the [bounded](crate::Bounded) type (that you will use in your
/// code, so check it's documentation of available methods) and it's associated
/// [bound](crate::Bound) (which will hidden from your docs).
///
/// # Macro structure
///
/// ## Type signature
///
/// This part describes the visibility of the type, it's name and it's inner
/// type:
/// ```
/// use prae::define;
///
/// define! {
/// pub UserId: u64
/// // ...
/// }
///
/// define! {
/// UserName: String
/// // ...
/// }
/// ```
///
/// ## Type extension
///
/// In case you want to extend the validation/adjustment logic of some other
/// type, you should just specify the other type's [`Bound`](crate::Bound):
/// ```
/// # use prae::define;
/// # define! {
/// # pub UserId: u64
/// # // ...
/// # }
/// define! {
/// pub AdminUserID: u64
/// extend UserIdBound
/// // ...
/// }
/// ```
/// When you do this, your type will execute adjustment and validation (see
/// below) of the extended type first, then run the adjustment and validation of
/// the current type.
///
/// Note that such extension is only possible when the types have the same inner
/// type.
///
/// ## Adjustment closure
///
/// This part describes how a value of your type should be mutated before
/// every construction or mutation.
/// ```
/// # use prae::define;
/// define! {
/// pub UserName: String
/// // ..
/// adjust |name: &mut String| *name = name.trim().to_owned()
/// // ...
/// }
/// ```
///
/// ## Validation closure
///
/// This part describes how a value of your type should be validated before
/// every construction or mutation. There are two ways you can do this: using
/// `ensure` closure or using `validate` closure.
///
/// ### Using `ensure`
///
/// This is the simplest method. In this case, your closure returns `true` if
/// the value is valid and `false` otherwise. The downside of this method is
/// that you can't use custom error types. If the value is invalid, the error
/// will be just a string `"value is invalid"`.
/// ```
/// # use prae::define;
/// define! {
/// pub UserName: String
/// // ...
/// ensure |name: &String| !name.is_empty()
/// }
/// ```
///
/// ### Using `validate`
///
/// This method allows you to use custom errors.
/// ```
/// # use prae::define;
/// #[derive(Debug)]
/// pub enum UserNameError {
/// Empty,
/// }
///
/// define! {
/// pub UserName: String
/// // ...
/// validate(UserNameError) |name: &String| {
/// if name.is_empty() {
/// Err(UserNameError::Empty)
/// } else {
/// Ok(())
/// }
/// }
/// }
/// ```
///
/// # Examples
///
/// ## Basic usage
///
/// Suppose we want to implement some types for a typography app. Let's start
/// from a `Text` type, which represents a string with some symbols
/// (non-empty!).
/// ```
/// # use prae::define;
/// /// A string with some symbols.
/// define! {
/// pub Text: String
/// ensure |text| !text.is_empty()
/// }
///
/// // Let's try it.
/// let text1 = Text::new("some text").unwrap();
/// assert_eq!(text1.get(), "some text");
///
/// // Now with an invalid value.
/// let err = Text::new("").unwrap_err();
/// assert_eq!(err.inner, "value is invalid");
/// assert_eq!(err.value, "");
///
/// // But what if...
/// assert!(Text::new(" ").is_ok());
/// ```
///
/// The last line doesn't look right. Although the string `" "` contains
/// symbols, it's all whitespace. How can we solve that? Well, we could
/// use `!text.trim().is_empty()` inside the `ensure` closure. But what if we
/// give give it a string with some symbols and trailing whitespace, like `"
/// some text\n"`? This string will pass validation and will be saved in it's
/// original form. We don't want that in this particular case. Let's fix it with
/// `adjust` closure!
///
/// ```
/// # use prae::define;
/// /// A non-empty string without trailing whitespace.
/// define! {
/// pub Text: String
/// adjust |text| *text = text.trim().to_owned()
/// ensure |text| !text.is_empty()
/// }
///
/// // Now we can't use a string with only whitespace.
/// let err = Text::new(" ").unwrap_err();
/// assert_eq!(err.inner, "value is invalid");
/// assert_eq!(err.value, "");
///
/// // And the trailing whitespace will be truncated.
/// let text = Text::new(" some text\n").unwrap();
/// assert_eq!(text.get(), "some text");
/// ```
///
/// ## Custom errors
///
/// Okay, so far so good. The only problem is: right now our `Text` type will
/// return string error `"value is invalid"` if it's value is not valid. If we
/// want to make our error more descriptive, we should create a custom error!
/// ```
/// # use prae::define;
/// /// Error that can happen during construction/mutation of `Text`.
/// #[derive(Debug)]
/// pub enum TextError {
/// Empty,
/// }
///
/// define! {
/// pub Text: String
/// adjust |text| *text = text.trim().to_owned()
/// validate(TextError) |text| {
/// if text.is_empty() {
/// Err(TextError::Empty)
/// } else {
/// Ok(())
/// }
/// }
/// }
///
/// // Let's try it!
/// let err = Text::new(" ").unwrap_err();
/// assert!(matches!(err.inner, TextError::Empty));
/// assert_eq!(err.value, "");
/// ```
///
/// ## Type extension
///
/// It's a perfect time to introduce a new type: `CapitalizedText`. This type is
/// very similar to `Text`, but requires the first letter to be capitalized.
/// Sinces this type inherits invariants of `Text`, we will just extend `Text`
/// type to make the code less verbose.
/// ```
/// # use prae::define;
/// /// Error that can happen during construction/mutation of `Text`.
/// #[derive(Debug)]
/// pub enum TextError {
/// Empty,
/// }
///
/// define! {
/// pub Text: String
/// adjust |text| *text = text.trim().to_owned()
/// validate(TextError) |text| {
/// if text.is_empty() {
/// Err(TextError::Empty)
/// } else {
/// Ok(())
/// }
/// }
/// }
///
/// /// Error that can happen during construction/mutation of `CapitalizedText`.
/// #[derive(Debug)]
/// pub enum CapitalizedTextError {
/// Empty,
/// NotCapitalized,
/// }
///
/// impl From<TextError> for CapitalizedTextError {
/// fn from(err: TextError) -> Self {
/// match err {
/// TextError::Empty => Self::Empty,
/// }
/// }
/// }
///
/// define! {
/// pub CapitalizedText: String
/// extend TextBound
/// validate(CapitalizedTextError) |text| {
/// if text.chars().next().unwrap().is_lowercase() {
/// Err(CapitalizedTextError::NotCapitalized)
/// } else {
/// Ok(())
/// }
/// }
/// }
///
/// // Let's try it.
/// let text = CapitalizedText::new(" Some text ").unwrap();
/// assert_eq!(text.get(), "Some text");
///
/// let err = CapitalizedText::new(" some other text").unwrap_err();
/// assert!(matches!(err.inner, CapitalizedTextError::NotCapitalized));
/// assert_eq!(err.value, "some other text");
///
/// let err = CapitalizedText::new(" ").unwrap_err();
/// assert!(matches!(err.inner, CapitalizedTextError::Empty));
/// assert_eq!(err.value, "");
/// ```
pub use prae_macro::define;
#[cfg(test)]
mod tests {}