/*! This library provides traits and an error type to be used in Bottlerocket models. It is intended to be combined with the `scalar-derive` crate so that we can use the `Scalar` macro on simple types. */ use serde::Serialize; use std::error::Error; use std::fmt::{Display, Formatter}; /// Because the `Scalar` trait has the same name as the `Scalar` derive macro, we place it and the /// `Validate` traits into a module named `traits`. pub mod traits { use super::*; /// The `Scalar` trait is used to wrap some inner, validated, value. It is intended for use in /// the Bottlerocket model for values like strings or numbers that need some additional /// validation. The `Inner` value is the string or number that has been validated. Validation /// of the `Inner` value takes place during the `new` function. Thus a constructed `Scalar` /// object can be expected to hold a valid value. /// /// # Example /// /// ``` /// use scalar::traits::Scalar; /// use scalar::ValidationError; /// /// pub struct Pizza { /// inner: String, /// } /// /// impl Scalar for Pizza { /// type Inner = String; /// /// fn new>(input: T) -> Result where Self: Sized { /// let input: String = input.into(); /// if input == "pineapple" { /// Err(ValidationError::new("pineapple is gross on pizza")) /// } else { /// Ok(Self{ inner: input }) /// } /// } /// /// fn inner(&self) -> &Self::Inner { /// &self.inner /// } /// /// fn unwrap(self) -> Self::Inner { /// self.inner /// }} /// ``` /// pub trait Scalar { /// The inner type who's value is being wrapped by the `Scalar` object. For example, /// `String`, `u32`, etc. /// /// The traits bounds on the `Inner` type exist because the `Scalar` derive macro expects /// them to be there. Placing the trait bounds here makes error messages easier to interpret /// when using the `Scalar` derive macro. The macro also requires `Deserialize<'de>`, but it /// is unhelpful to add it to the trait bounds here because of the lifetime parameter. type Inner: PartialEq + Display + Serialize; /// Creates a new instance of the `Scalar` object. Validates `input` and returns a /// `ValidationError` if the `input` is invalid. fn new>(input: T) -> Result where Self: Sized; /// Return a reference to the inner value. fn inner(&self) -> &Self::Inner; /// Destroys the `Scalar` object and gives ownership of its inner value. fn unwrap(self) -> Self::Inner; } /// `Validate` provides a function that you can implement to construct your `Scalar` type from /// its `Inner` type. The `Scalar` derive macro expects your `Scalar` type to implement /// `Validate`. /// /// # Example /// /// If we rewrite the `Scalar` example to incorporate the `Validate` trait, it looks like this. /// Notice that the validation has moved to the `Validate` trait and that the `Scalar::new` /// function calls it. This is what the `new` function looks like when generated by the `Scalar` /// derive macro. /// /// ``` /// use scalar::traits::{Scalar, Validate}; /// use scalar::ValidationError; /// /// pub struct Pizza { /// inner: String, /// } /// /// impl Validate for Pizza { /// fn validate(input: T) -> Result /// where T: Into<::Inner> { /// let input: String = input.into(); /// if input == "pineapple" { /// Err(ValidationError::new("pineapple is gross on pizza")) /// } else { /// Ok(Self{ inner: input }) /// } /// } /// /// } /// /// impl Scalar for Pizza { /// type Inner = String; /// /// fn new>(input: T) -> Result /// where Self: Sized { /// ::validate(input) /// } /// /// fn inner(&self) -> &Self::Inner { /// &self.inner /// } /// /// fn unwrap(self) -> Self::Inner { /// self.inner /// }} /// ``` /// pub trait Validate where Self: Scalar + Sized, { /// Constructs a validated `Wrapper` object. fn validate(input: T) -> Result where T: Into<::Inner>; } } /// The error type that [`Validate::validate`] returns. #[derive(Debug)] pub struct ValidationError { /// A message about what occurred during validation. message: String, /// The underlying error that occurred (if any). source: Option>, } impl ValidationError { /// Creates a new [`ValidationError`] with a message. Use this when there is no underlying error /// to include. pub fn new(message: S) -> Self where S: AsRef, { Self { message: message.as_ref().into(), source: None, } } /// Creates a new [`ValidationError`] with a message and an error. Use this when you have an /// underlying error that you want to preserve as the `source`. pub fn new_with_cause(message: S, source: E) -> Self where E: Into>, S: AsRef, { Self { message: message.as_ref().into(), source: Some(source.into()), } } } impl Display for ValidationError { /// Display the message if there is no underlying error, or display both if there is one. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if let Some(e) = self.source.as_ref() { write!(f, "{}: {}", self.message, e) } else { write!(f, "{}", self.message) } } } impl Error for ValidationError { /// Return the underlying error if there is one. fn source(&self) -> Option<&(dyn Error + 'static)> { self.source.as_ref().map(|e| e.as_ref() as &(dyn Error)) } } impl From for ValidationError { fn from(e: serde_plain::Error) -> Self { ValidationError::new_with_cause("Unable to convert from string", e) } }