diff --git a/src/errors.rs b/src/errors.rs index 5f2bb6b..3c9555c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -34,6 +34,10 @@ pub enum CaptchaError { #[display(fmt = "difficulty factor must be greater than zero")] DifficultyFactorZero, + /// captcha cooldown duration must be greater than 0 + #[display(fmt = "difficulty factor must be greater than zero")] + CaptchaDurationZero, + /// Difficulty factor must be set #[display(fmt = "Set difficulty factor")] SetDifficultyFactor, @@ -63,6 +67,10 @@ pub enum CaptchaError { /// isn't in cache #[display(fmt = "String now found")] StringNotFound, + + /// Used in builder structs when a value is not set + #[display(fmt = "Please set value: {}", _0)] + PleaseSetValue(#[error(not(source))] String), } /// [Result] datatype for m_captcha diff --git a/src/mcaptcha.rs b/src/mcaptcha.rs index 1e5ade2..cf7aefe 100644 --- a/src/mcaptcha.rs +++ b/src/mcaptcha.rs @@ -75,20 +75,72 @@ use std::time::Duration; use actix::dev::*; -use derive_builder::Builder; +use serde::{Deserialize, Serialize}; -use crate::defense::Defense; +use crate::{ + defense::Defense, + errors::{CaptchaError, CaptchaResult}, +}; + +/// Builder for [MCaptcha] +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct MCaptchaBuilder { + visitor_threshold: u32, + defense: Option, + duration: Option, +} + +impl Default for MCaptchaBuilder { + fn default() -> Self { + MCaptchaBuilder { + visitor_threshold: 0, + defense: None, + duration: None, + } + } +} /// This struct represents the mCaptcha state and is used /// to configure leaky-bucket lifetime and manage defense -#[derive(Clone, Debug, Builder)] +#[derive(Clone, Serialize, Deserialize, Debug)] pub struct MCaptcha { - #[builder(default = "0", setter(skip))] visitor_threshold: u32, defense: Defense, duration: u64, } +impl MCaptchaBuilder { + /// set defense + pub fn defense(&mut self, d: Defense) -> &mut Self { + self.defense = Some(d); + self + } + + /// set duration + pub fn duration(&mut self, d: u64) -> &mut Self { + self.duration = Some(d); + self + } + + /// Builds new [MCaptcha] + pub fn build(&mut self) -> CaptchaResult { + if self.duration.is_none() { + Err(CaptchaError::PleaseSetValue("duration".into())) + } else if self.defense.is_none() { + Err(CaptchaError::PleaseSetValue("defense".into())) + } else if self.duration <= Some(0) { + Err(CaptchaError::CaptchaDurationZero) + } else { + let m = MCaptcha { + duration: self.duration.unwrap(), + defense: self.defense.clone().unwrap(), + visitor_threshold: self.visitor_threshold, + }; + Ok(m) + } + } +} + impl MCaptcha { /// increments the visitor count by one pub fn add_visitor(&mut self) { @@ -256,4 +308,27 @@ pub mod tests { mcaptcha = addr.send(Visitor).await.unwrap(); assert_eq!(mcaptcha.difficulty_factor, LEVEL_1.1); } + + #[test] + fn test_mcatcptha_builder() { + let defense = get_defense(); + let m = MCaptchaBuilder::default() + .duration(0) + .defense(defense.clone()) + .build(); + + assert_eq!(m.err(), Some(CaptchaError::CaptchaDurationZero)); + + let m = MCaptchaBuilder::default().duration(30).build(); + assert_eq!( + m.err(), + Some(CaptchaError::PleaseSetValue("defense".into())) + ); + + let m = MCaptchaBuilder::default().defense(defense.clone()).build(); + assert_eq!( + m.err(), + Some(CaptchaError::PleaseSetValue("duration".into())) + ); + } }