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}