salvo_captcha/captcha_gen/
simple_generator.rs

1// Copyright (c) 2024-2025, Awiteb <a@4rs.nl>
2//     A captcha middleware for Salvo framework.
3//
4// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10// THE SOFTWARE.
11
12use crate::CaptchaGenerator;
13
14use std::fmt::Display;
15
16/// Supported captcha names
17///
18/// See [`README.md`](https://git.4rs.nl/awiteb/salvo-captcha.git/about/#captcha-name-and-difficulty) for more information.
19#[derive(Debug, Clone, Copy)]
20pub enum CaptchaName {
21    /// Plain text, without any distortion
22    Normal,
23    /// Slightly twisted text
24    SlightlyTwisted,
25    /// Very twisted text
26    VeryTwisted,
27}
28
29/// Supported captcha difficulties
30///
31/// See [`README.md`](https://git.4rs.nl/awiteb/salvo-captcha.git/about/#captcha-name-and-difficulty) for more information.
32#[derive(Debug, Clone, Copy)]
33pub enum CaptchaDifficulty {
34    /// Easy to read text
35    Easy,
36    /// Medium difficulty text
37    Medium,
38    /// Hard to read text
39    Hard,
40}
41
42impl From<CaptchaName> for captcha::CaptchaName {
43    /// Function to convert the [`CaptchaName`] to the [`captcha::CaptchaName`]
44    fn from(value: CaptchaName) -> Self {
45        match value {
46            CaptchaName::Normal => Self::Lucy,
47            CaptchaName::SlightlyTwisted => Self::Amelia,
48            CaptchaName::VeryTwisted => Self::Mila,
49        }
50    }
51}
52
53impl From<CaptchaDifficulty> for captcha::Difficulty {
54    /// Function to convert the [`CaptchaDifficulty`] to the [`captcha::Difficulty`]
55    fn from(value: CaptchaDifficulty) -> captcha::Difficulty {
56        match value {
57            CaptchaDifficulty::Easy => Self::Easy,
58            CaptchaDifficulty::Medium => Self::Medium,
59            CaptchaDifficulty::Hard => Self::Hard,
60        }
61    }
62}
63
64#[derive(Debug)]
65/// Error type for the [`SimpleGenerator`]
66pub enum SimpleGeneratorError {
67    /// Failed to encode the captcha to png image
68    FaildEncodedToPng,
69}
70
71impl Display for SimpleGeneratorError {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        write!(f, "Faild to encode the captcha to png image")
74    }
75}
76
77impl std::error::Error for SimpleGeneratorError {}
78
79/// A simple captcha generator, using the [`captcha`](https://crates.io/crates/captcha) crate.
80pub struct SimpleGenerator {
81    name: CaptchaName,
82    difficulty: CaptchaDifficulty,
83}
84
85impl SimpleGenerator {
86    /// Create new [`SimpleGenerator`] instance
87    pub const fn new(name: CaptchaName, difficulty: CaptchaDifficulty) -> Self {
88        Self { name, difficulty }
89    }
90}
91
92impl CaptchaGenerator for SimpleGenerator {
93    type Error = SimpleGeneratorError;
94
95    /// The returned captcha image is 220x110 pixels in png format.
96    async fn new_captcha(&self) -> Result<(String, Vec<u8>), Self::Error> {
97        captcha::by_name(self.difficulty.into(), self.name.into())
98            .as_tuple()
99            .ok_or(SimpleGeneratorError::FaildEncodedToPng)
100    }
101}