diff --git a/Cargo.toml b/Cargo.toml index 63221dd..88d6e02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,7 @@ tempfile = "3.9" tokio = { version = "1.35", features = ["macros", "rt-multi-thread"] } base64 = "0.21" salvo = { version = ">= 0.65, < 0.69", default-features = false, features = ["server", "http1","http2", "affix"] } + +[[example]] +name = "simple_login" +required-features = ["cacache-storage"] diff --git a/examples/simple_login.rs b/examples/simple_login.rs index ee1f7d7..9a0fe39 100644 --- a/examples/simple_login.rs +++ b/examples/simple_login.rs @@ -3,15 +3,6 @@ // You can see a video of this example here: // // Run the example with `cargo run --example simple_login --features cacache-storage` -// -// Or set up a crate for it, with the following `Cargo.toml`: -// ```toml -// [dependencies] -// base64 = ">= 0.21" -// salvo = { version = ">= 0.65", features = ["affix"] } -// salvo-captcha = { version = ">= 0.1", features = ["cacache-storage"] } -// tokio = { version = ">= 1.35", features = ["macros", "rt-multi-thread", "time"] } -// ``` use std::{sync::Arc, time::Duration}; @@ -19,9 +10,6 @@ use base64::{engine::GeneralPurpose, Engine}; use salvo::prelude::*; use salvo_captcha::*; -// The type of the captcha -type MyCaptcha = Captcha>; - // To convert the image to base64, to show it in the browser const BASE_64_ENGINE: GeneralPurpose = GeneralPurpose::new( &base64::alphabet::STANDARD, @@ -29,7 +17,7 @@ const BASE_64_ENGINE: GeneralPurpose = GeneralPurpose::new( ); #[handler] -async fn index(_req: &mut Request, res: &mut Response, depot: &mut Depot) { +async fn index(res: &mut Response, depot: &mut Depot) { // Get the captcha from the depot let captcha_storage = depot.obtain::>().unwrap(); @@ -51,7 +39,7 @@ async fn index(_req: &mut Request, res: &mut Response, depot: &mut Depot) { #[handler] async fn auth(req: &mut Request, res: &mut Response, depot: &mut Depot) { // Get the captcha state from the depot, where we can know if the captcha is passed - let captcha_state = depot.get_captcha_state().unwrap(); + let captcha_state = depot.get_captcha_state(); // Not important, just for demo let Some(username) = req.form::("username").await else { res.status_code(StatusCode::BAD_REQUEST); @@ -76,7 +64,7 @@ async fn auth(req: &mut Request, res: &mut Response, depot: &mut Depot) { #[tokio::main] async fn main() { - let captcha_middleware = MyCaptcha::new( + let captcha_middleware = Captcha::new( CacacheStorage::new("./captcha-cache"), CaptchaFormFinder::new(), ) @@ -115,6 +103,7 @@ async fn main() { ); let acceptor = TcpListener::new(("127.0.0.1", 5800)).bind().await; + println!("Starting server on http://127.0.0.1:5800"); Server::new(acceptor).serve(router).await; captcha_cleaner.await.ok(); } diff --git a/src/captcha_gen.rs b/src/captcha_gen.rs index 7c6ba4f..e19a361 100644 --- a/src/captcha_gen.rs +++ b/src/captcha_gen.rs @@ -1,3 +1,14 @@ +// Copyright (c) 2024, Awiteb +// A captcha middleware for Salvo framework. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + use crate::{CaptchaDifficulty, CaptchaName, CaptchaStorage}; /// Captcha generator, used to generate a new captcha image. This trait are implemented for all [`CaptchaStorage`]. @@ -11,7 +22,7 @@ pub trait CaptchaGenerator: CaptchaStorage { &self, name: CaptchaName, difficulty: CaptchaDifficulty, - ) -> impl std::future::Future)>, Self::Error>> + Send + ) -> impl std::future::Future)>, Self::Error>> + Send { async { let Some((captcha_answer, captcha_image)) = @@ -20,7 +31,7 @@ pub trait CaptchaGenerator: CaptchaStorage { return Ok(None); }; - let token = self.store_answer(captcha_answer.into()).await?; + let token = self.store_answer(captcha_answer).await?; Ok(Some((token, captcha_image))) } } diff --git a/src/finder.rs b/src/finder.rs index c68b8e7..f26cd71 100644 --- a/src/finder.rs +++ b/src/finder.rs @@ -9,49 +9,37 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -use std::marker::PhantomData; - use salvo_core::http::header::HeaderName; use salvo_core::http::Request; /// Trait to find the captcha token and answer from the request. pub trait CaptchaFinder: Send + Sync { - /// The token type - type Token: TryFrom + Sync + Send; - /// The answer type - type Answer: TryFrom + Sync + Send; - - /// The token error type - type TError: std::fmt::Debug + Send; - /// The answer error type - type AError: std::fmt::Debug + Send; - /// Find the captcha token from the request. /// - /// Return [`None`] if the request does not contain a captcha token. An error is returned if the token is invalid format. + /// ### Returns + /// - None: If the token is not found + /// - Some(None): If the token is found but is invalid (e.g. not a valid string) + /// - Some(Some(token)): If the token is found fn find_token( &self, req: &mut Request, - ) -> impl std::future::Future, Self::TError>> + std::marker::Send; + ) -> impl std::future::Future>> + std::marker::Send; /// Find the captcha answer from the request. /// - /// Return [`None`] if the request does not contain a captcha answer. An error is returned if the answer is invalid format. + /// ### Returns + /// - None: If the answer is not found + /// - Some(None): If the answer is found but is invalid (e.g. not a valid string) + /// - Some(Some(answer)): If the answer is found fn find_answer( &self, req: &mut Request, - ) -> impl std::future::Future, Self::AError>> + std::marker::Send; + ) -> impl std::future::Future>> + std::marker::Send; } /// Find the captcha token and answer from the header #[derive(Debug)] -pub struct CaptchaHeaderFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, -{ - phantom: PhantomData<(T, A)>, - +pub struct CaptchaHeaderFinder { /// The header name of the captcha token /// /// Default: "x-captcha-token" @@ -65,13 +53,7 @@ where /// Find the captcha token and answer from the form #[derive(Debug)] -pub struct CaptchaFormFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, -{ - phantom: PhantomData<(T, A)>, - +pub struct CaptchaFormFinder { /// The form name of the captcha token /// /// Default: "captcha_token" @@ -83,11 +65,7 @@ where pub answer_name: String, } -impl CaptchaHeaderFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, -{ +impl CaptchaHeaderFinder { /// Create a new CaptchaHeaderFinder pub fn new() -> Self { Self::default() @@ -106,11 +84,7 @@ where } } -impl CaptchaFormFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, -{ +impl CaptchaFormFinder { /// Create a new CaptchaFormFinder pub fn new() -> Self { Self::default() @@ -129,99 +103,57 @@ where } } -impl Default for CaptchaHeaderFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, -{ +impl Default for CaptchaHeaderFinder { /// Create a default CaptchaHeaderFinder with: /// - token_header: "x-captcha-token" /// - answer_header: "x-captcha-answer" fn default() -> Self { Self { - phantom: PhantomData, token_header: HeaderName::from_static("x-captcha-token"), answer_header: HeaderName::from_static("x-captcha-answer"), } } } -impl Default for CaptchaFormFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, -{ +impl Default for CaptchaFormFinder { /// Create a default CaptchaFormFinder with: /// - token_name: "captcha_token" /// - answer_name: "captcha_answer" fn default() -> Self { Self { - phantom: PhantomData, token_name: "captcha_token".to_string(), answer_name: "captcha_answer".to_string(), } } } -impl CaptchaFinder for CaptchaHeaderFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, - >::Error: Send, - >::Error: std::fmt::Debug, - >::Error: Send, - >::Error: std::fmt::Debug, -{ - type Token = T; - type Answer = A; - - type TError = >::Error; - type AError = >::Error; - - async fn find_token(&self, req: &mut Request) -> Result, Self::TError> { +impl CaptchaFinder for CaptchaHeaderFinder { + async fn find_token(&self, req: &mut Request) -> Option> { req.headers() .get(&self.token_header) - .and_then(|t| t.to_str().ok()) - .map(|t| Self::Token::try_from(t.to_string())) - .transpose() + .map(|t| t.to_str().map(ToString::to_string).ok()) } - async fn find_answer(&self, req: &mut Request) -> Result, Self::AError> { + async fn find_answer(&self, req: &mut Request) -> Option> { req.headers() .get(&self.answer_header) - .and_then(|a| a.to_str().ok()) - .map(|a| Self::Answer::try_from(a.to_string())) - .transpose() + .map(|a| a.to_str().map(ToString::to_string).ok()) } } -impl CaptchaFinder for CaptchaFormFinder -where - T: TryFrom + Sync + Send, - A: TryFrom + Sync + Send, - >::Error: Send, - >::Error: std::fmt::Debug, - >::Error: Send, - >::Error: std::fmt::Debug, -{ - type Token = T; - type Answer = A; - - type TError = >::Error; - type AError = >::Error; - - async fn find_token(&self, req: &mut Request) -> Result, Self::TError> { - req.form::(&self.token_name) +impl CaptchaFinder for CaptchaFormFinder { + async fn find_token(&self, req: &mut Request) -> Option> { + req.form_data() .await - .map(|t| Self::Token::try_from(t.to_string())) - .transpose() + .map(|form| form.fields.get(&self.token_name).cloned()) + .ok() } - async fn find_answer(&self, req: &mut Request) -> Result, Self::AError> { - req.form::(&self.answer_name) + async fn find_answer(&self, req: &mut Request) -> Option> { + req.form_data() .await - .map(|a| Self::Answer::try_from(a.to_string())) - .transpose() + .map(|form| form.fields.get(&self.answer_name).cloned()) + .ok() } } @@ -234,77 +166,90 @@ mod tests { #[tokio::test] async fn test_captcha_header_finder() { - let finder = CaptchaHeaderFinder::::new(); + let finder = CaptchaHeaderFinder::new(); let mut req = Request::default(); let headers = req.headers_mut(); - let token = uuid::Uuid::new_v4(); + headers.insert( HeaderName::from_static("x-captcha-token"), - HeaderValue::from_str(&token.to_string()).unwrap(), + HeaderValue::from_str("token").unwrap(), ); headers.insert( HeaderName::from_static("x-captcha-answer"), HeaderValue::from_static("answer"), ); + assert_eq!( finder.find_token(&mut req).await, - Ok(Some(token.to_string())) + Some(Some("token".to_owned())) ); - assert!(matches!( + assert_eq!( finder.find_answer(&mut req).await, - Ok(Some(a)) if a == *"answer" - )); + Some(Some("answer".to_owned())) + ); } #[tokio::test] async fn test_captcha_header_finder_customized() { - let finder = CaptchaHeaderFinder::::new() + let finder = CaptchaHeaderFinder::new() .token_header(HeaderName::from_static("token")) .answer_header(HeaderName::from_static("answer")); + let mut req = Request::default(); let headers = req.headers_mut(); - let token = uuid::Uuid::new_v4(); + headers.insert( HeaderName::from_static("token"), - HeaderValue::from_str(&token.to_string()).unwrap(), + HeaderValue::from_str("token").unwrap(), ); headers.insert( HeaderName::from_static("answer"), HeaderValue::from_static("answer"), ); + assert_eq!( finder.find_token(&mut req).await, - Ok(Some(token.to_string())) + Some(Some("token".to_owned())) ); - assert!(matches!( + assert_eq!( finder.find_answer(&mut req).await, - Ok(Some(a)) if a == *"answer" - )); + Some(Some("answer".to_owned())) + ); } #[tokio::test] async fn test_captcha_header_finder_none() { - let finder = CaptchaHeaderFinder::::new(); + let finder = CaptchaHeaderFinder::new(); let mut req = Request::default(); - assert_eq!(finder.find_token(&mut req).await, Ok(None)); - assert_eq!(finder.find_answer(&mut req).await, Ok(None)); + assert_eq!(finder.find_token(&mut req).await, None); + assert_eq!(finder.find_answer(&mut req).await, None); } #[tokio::test] async fn test_captcha_header_finder_customized_none() { - let finder = CaptchaHeaderFinder::::new() + let finder = CaptchaHeaderFinder::new() .token_header(HeaderName::from_static("token")) .answer_header(HeaderName::from_static("answer")); let mut req = Request::default(); + let headers = req.headers_mut(); - assert_eq!(finder.find_token(&mut req).await, Ok(None)); - assert_eq!(finder.find_answer(&mut req).await, Ok(None)); + headers.insert( + HeaderName::from_static("x-captcha-token"), + HeaderValue::from_str("token").unwrap(), + ); + headers.insert( + HeaderName::from_static("x-captcha-answer"), + HeaderValue::from_static("answer"), + ); + + assert_eq!(finder.find_token(&mut req).await, None); + assert_eq!(finder.find_answer(&mut req).await, None); } #[tokio::test] async fn test_captcha_form_finder() { - let finder = CaptchaFormFinder::::new(); + let finder = CaptchaFormFinder::new(); let mut req = Request::default(); *req.body_mut() = ReqBody::Once("captcha_token=token&captcha_answer=answer".into()); let headers = req.headers_mut(); @@ -315,17 +260,17 @@ mod tests { assert_eq!( finder.find_token(&mut req).await, - Ok(Some("token".to_string())) + Some(Some("token".to_owned())) ); - assert!(matches!( + assert_eq!( finder.find_answer(&mut req).await, - Ok(Some(a)) if a == *"answer" - )); + Some(Some("answer".to_owned())) + ); } #[tokio::test] async fn test_captcha_form_finder_customized() { - let finder = CaptchaFormFinder::::new() + let finder = CaptchaFormFinder::new() .token_name("token".to_string()) .answer_name("answer".to_string()); let mut req = Request::default(); @@ -338,17 +283,17 @@ mod tests { assert_eq!( finder.find_token(&mut req).await, - Ok(Some("token".to_string())) + Some(Some("token".to_owned())) ); - assert!(matches!( + assert_eq!( finder.find_answer(&mut req).await, - Ok(Some(a)) if a == *"answer" - )); + Some(Some("answer".to_owned())) + ); } #[tokio::test] async fn test_captcha_form_finder_none() { - let finder = CaptchaFormFinder::::new(); + let finder = CaptchaFormFinder::new(); let mut req = Request::default(); *req.body_mut() = ReqBody::Once("".into()); let headers = req.headers_mut(); @@ -357,13 +302,13 @@ mod tests { HeaderValue::from_str(&ContentType::form_url_encoded().to_string()).unwrap(), ); - assert_eq!(finder.find_token(&mut req).await, Ok(None)); - assert_eq!(finder.find_answer(&mut req).await, Ok(None)); + assert_eq!(finder.find_token(&mut req).await, Some(None)); + assert_eq!(finder.find_answer(&mut req).await, Some(None)); } #[tokio::test] async fn test_captcha_form_finder_customized_none() { - let finder = CaptchaFormFinder::::new() + let finder = CaptchaFormFinder::new() .token_name("token".to_string()) .answer_name("answer".to_string()); let mut req = Request::default(); @@ -374,13 +319,13 @@ mod tests { HeaderValue::from_str(&ContentType::form_url_encoded().to_string()).unwrap(), ); - assert_eq!(finder.find_token(&mut req).await, Ok(None)); - assert_eq!(finder.find_answer(&mut req).await, Ok(None)); + assert_eq!(finder.find_token(&mut req).await, Some(None)); + assert_eq!(finder.find_answer(&mut req).await, Some(None)); } #[tokio::test] async fn test_captcha_form_finder_invalid() { - let finder = CaptchaFormFinder::::new(); + let finder = CaptchaFormFinder::new(); let mut req = Request::default(); *req.body_mut() = ReqBody::Once("captcha_token=token&captcha_answer=answer".into()); let headers = req.headers_mut(); @@ -389,7 +334,7 @@ mod tests { HeaderValue::from_str(&ContentType::json().to_string()).unwrap(), ); - assert_eq!(finder.find_token(&mut req).await, Ok(None)); - assert_eq!(finder.find_answer(&mut req).await, Ok(None)); + assert_eq!(finder.find_token(&mut req).await, None); + assert_eq!(finder.find_answer(&mut req).await, None); } } diff --git a/src/lib.rs b/src/lib.rs index 6a30e99..1ddde57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ pub const CAPTCHA_STATE_KEY: &str = "::salvo_captcha::captcha_state"; pub struct Captcha where S: CaptchaStorage, - F: CaptchaFinder, + F: CaptchaFinder, { /// The captcha finder, used to find the captcha token and answer from the request. finder: F, @@ -47,20 +47,20 @@ where } /// The captcha states of the request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum CaptchaState { + /// The captcha check is skipped. This depends on the skipper. + #[default] + Skipped, /// The captcha is checked and passed. If the captcha is passed, it will be cleared from the storage. Passed, - /// The captcha check is skipped. This depends on the skipper. - Skipped, /// Can't find the captcha token in the request TokenNotFound, /// Can't find the captcha answer in the request AnswerNotFound, - /// The captcha token is wrong, can't find the captcha in the storage. - /// Maybe the captcha token entered by the user is wrong, or the captcha is expired, because the storage has been cleared. + /// Can't find the captcha token in the storage or the token is wrong (not valid string) WrongToken, - /// The captcha answer is wrong. This will not clear the captcha from the storage. + /// Can't find the captcha answer in the storage or the answer is wrong (not valid string) WrongAnswer, /// Storage error StorageError, @@ -69,13 +69,13 @@ pub enum CaptchaState { impl Captcha where S: CaptchaStorage, - F: CaptchaFinder, + F: CaptchaFinder, { /// Create a new Captcha - pub fn new(storage: impl Into, finder: impl Into) -> Self { + pub fn new(storage: S, finder: F) -> Self { Self { - finder: finder.into(), - storage: storage.into(), + finder, + storage, skipper: Box::new(none_skipper), } } @@ -94,19 +94,22 @@ where /// The captcha extension of the depot. /// Used to get the captcha info from the depot. -#[easy_ext::ext(CaptchaDepotExt)] -impl Depot { +pub trait CaptchaDepotExt { /// Get the captcha state from the depot - pub fn get_captcha_state(&self) -> Option<&CaptchaState> { - self.get(CAPTCHA_STATE_KEY).ok() + fn get_captcha_state(&self) -> CaptchaState; +} + +impl CaptchaDepotExt for Depot { + fn get_captcha_state(&self) -> CaptchaState { + self.get(CAPTCHA_STATE_KEY).cloned().unwrap_or_default() } } -#[async_trait::async_trait] +#[salvo_core::async_trait] impl Handler for Captcha where S: CaptchaStorage, - F: CaptchaFinder + 'static, + F: CaptchaFinder + 'static, // why? { async fn handle( &self, @@ -122,28 +125,28 @@ where } let token = match self.finder.find_token(req).await { - Ok(Some(token)) => token, - Ok(None) => { + Some(Some(token)) => token, + Some(None) => { log::info!("Captcha token is not found in request"); depot.insert(CAPTCHA_STATE_KEY, CaptchaState::TokenNotFound); return; } - Err(err) => { - log::error!("Failed to find captcha token from request: {err:?}"); + None => { + log::error!("Invalid token found in request"); depot.insert(CAPTCHA_STATE_KEY, CaptchaState::WrongToken); return; } }; let answer = match self.finder.find_answer(req).await { - Ok(Some(answer)) => answer, - Ok(None) => { + Some(Some(answer)) => answer, + Some(None) => { log::info!("Captcha answer is not found in request"); depot.insert(CAPTCHA_STATE_KEY, CaptchaState::AnswerNotFound); return; } - Err(err) => { - log::error!("Failed to find captcha answer from request: {err:?}"); + None => { + log::error!("Invalid answer found in request"); depot.insert(CAPTCHA_STATE_KEY, CaptchaState::WrongAnswer); return; } diff --git a/src/storage.rs b/src/storage.rs index 1919a8c..6043ddb 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -26,24 +26,20 @@ pub trait CaptchaStorage: Send + Sync + 'static where Self: Clone + std::fmt::Debug, { - /// The token type - type Token: TryFrom + std::fmt::Display + Send + Sync; - /// The answer type - type Answer: std::cmp::PartialEq + From + Send + Sync; /// The error type of the storage. type Error: std::fmt::Display + std::fmt::Debug + Send; /// Store the captcha token and answer. fn store_answer( &self, - answer: Self::Answer, - ) -> impl std::future::Future> + Send; + answer: String, + ) -> impl std::future::Future> + Send; /// Returns the answer of the captcha token. This method will return None if the token is not exist. fn get_answer( &self, - token: &Self::Token, - ) -> impl std::future::Future, Self::Error>> + Send; + token: &str, + ) -> impl std::future::Future, Self::Error>> + Send; /// Clear the expired captcha. fn clear_expired( @@ -54,7 +50,7 @@ where /// Clear the captcha by token. fn clear_by_token( &self, - token: &Self::Token, + token: &str, ) -> impl std::future::Future> + Send; } @@ -86,19 +82,17 @@ impl CacacheStorage { #[cfg(feature = "cacache-storage")] impl CaptchaStorage for CacacheStorage { type Error = cacache::Error; - type Token = String; - type Answer = String; - async fn store_answer(&self, answer: Self::Answer) -> Result { + async fn store_answer(&self, answer: String) -> Result { let token = uuid::Uuid::new_v4(); log::info!("Storing captcha answer to cacache for token: {token}"); cacache::write(&self.cache_dir, token.to_string(), answer.as_bytes()).await?; Ok(token.to_string()) } - async fn get_answer(&self, token: &Self::Token) -> Result, Self::Error> { + async fn get_answer(&self, token: &str) -> Result, Self::Error> { log::info!("Getting captcha answer from cacache for token: {token}"); - match cacache::read(&self.cache_dir, token.to_string()).await { + match cacache::read(&self.cache_dir, token).await { Ok(answer) => { log::info!("Captcha answer is exist in cacache for token: {token}"); Ok(Some( @@ -120,7 +114,7 @@ impl CaptchaStorage for CacacheStorage { async fn clear_expired(&self, expired_after: Duration) -> Result<(), Self::Error> { let now = SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .expect("SystemTime must be later than UNIX_EPOCH") + .expect("SystemTime before UNIX EPOCH!") .as_millis(); let expired_after = expired_after.as_millis(); @@ -143,10 +137,10 @@ impl CaptchaStorage for CacacheStorage { Ok(()) } - async fn clear_by_token(&self, token: &Self::Token) -> Result<(), Self::Error> { + async fn clear_by_token(&self, token: &str) -> Result<(), Self::Error> { log::info!("Clearing captcha token from cacache: {token}"); let remove_opts = cacache::RemoveOpts::new().remove_fully(true); - remove_opts.remove(&self.cache_dir, token.to_string()).await + remove_opts.remove(&self.cache_dir, token).await } } @@ -155,20 +149,18 @@ where T: CaptchaStorage, { type Error = T::Error; - type Token = T::Token; - type Answer = T::Answer; fn store_answer( &self, - answer: Self::Answer, - ) -> impl std::future::Future> + Send { + answer: String, + ) -> impl std::future::Future> + Send { self.as_ref().store_answer(answer) } fn get_answer( &self, - token: &Self::Token, - ) -> impl std::future::Future, Self::Error>> + Send { + token: &str, + ) -> impl std::future::Future, Self::Error>> + Send { self.as_ref().get_answer(token) } @@ -181,7 +173,7 @@ where fn clear_by_token( &self, - token: &Self::Token, + token: &str, ) -> impl std::future::Future> + Send { self.as_ref().clear_by_token(token) } @@ -281,7 +273,7 @@ mod tests { .expect("failed to check if token is exist") .is_some()); assert!(storage - .get_answer(&"token".to_owned()) + .get_answer("token") .await .expect("failed to check if token is exist") .is_none()); @@ -308,7 +300,7 @@ mod tests { Some("answer".to_owned()) ); assert!(storage - .get_answer(&"token".to_owned()) + .get_answer("token") .await .expect("failed to get captcha answer") .is_none());