use std::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TaskMode {
Async,
Sync,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum TaskType {
#[default]
RecaptchaV2TaskProxyless,
RecaptchaV2TaskProxylessS9,
RecaptchaV2STaskProxyless,
RecaptchaV2EnterpriseTaskProxyless,
RecaptchaV2SEnterpriseTaskProxyless,
RecaptchaV2Classification,
RecaptchaV3TaskProxyless,
RecaptchaV3TaskProxylessS9,
RecaptchaV3EnterpriseTaskProxyless,
RecaptchaV3EnterpriseTaskProxylessS9,
FuncaptchaTaskProxyless,
FuncaptchaClassification,
PerimeterX,
Hcaptcha,
HcaptchaClassification,
AkamaiWebTaskProxyless,
AkamaiSbsdTaskProxyless,
TlsTask,
Cloudflare5sTask,
CloudflareTurnstileTask,
DataDomeTaskProxyless,
DataDomeTagsTaskProxyless,
IncapsulaTaskProxyless,
}
impl TaskType {
pub const KNOWN: &'static [Self] = &[
Self::RecaptchaV2TaskProxyless,
Self::RecaptchaV2TaskProxylessS9,
Self::RecaptchaV2STaskProxyless,
Self::RecaptchaV2EnterpriseTaskProxyless,
Self::RecaptchaV2SEnterpriseTaskProxyless,
Self::RecaptchaV2Classification,
Self::RecaptchaV3TaskProxyless,
Self::RecaptchaV3TaskProxylessS9,
Self::RecaptchaV3EnterpriseTaskProxyless,
Self::RecaptchaV3EnterpriseTaskProxylessS9,
Self::FuncaptchaTaskProxyless,
Self::FuncaptchaClassification,
Self::PerimeterX,
Self::Hcaptcha,
Self::HcaptchaClassification,
Self::AkamaiWebTaskProxyless,
Self::AkamaiSbsdTaskProxyless,
Self::TlsTask,
Self::Cloudflare5sTask,
Self::CloudflareTurnstileTask,
Self::DataDomeTaskProxyless,
Self::DataDomeTagsTaskProxyless,
Self::IncapsulaTaskProxyless,
];
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::RecaptchaV2TaskProxyless => "ReCaptchaV2TaskProxyless",
Self::RecaptchaV2TaskProxylessS9 => "ReCaptchaV2TaskProxylessS9",
Self::RecaptchaV2STaskProxyless => "ReCaptchaV2STaskProxyless",
Self::RecaptchaV2EnterpriseTaskProxyless => "ReCaptchaV2EnterpriseTaskProxyless",
Self::RecaptchaV2SEnterpriseTaskProxyless => "ReCaptchaV2SEnterpriseTaskProxyless",
Self::RecaptchaV2Classification => "ReCaptchaV2Classification",
Self::RecaptchaV3TaskProxyless => "ReCaptchaV3TaskProxyless",
Self::RecaptchaV3TaskProxylessS9 => "ReCaptchaV3TaskProxylessS9",
Self::RecaptchaV3EnterpriseTaskProxyless => "ReCaptchaV3EnterpriseTaskProxyless",
Self::RecaptchaV3EnterpriseTaskProxylessS9 => "ReCaptchaV3EnterpriseTaskProxylessS9",
Self::FuncaptchaTaskProxyless => "FuncaptchaTaskProxyless",
Self::FuncaptchaClassification => "FunCaptchaClassification",
Self::PerimeterX => "PerimeterX",
Self::Hcaptcha => "HCaptcha",
Self::HcaptchaClassification => "HCaptchaClassification",
Self::AkamaiWebTaskProxyless => "AkamaiWEBTaskProxyless",
Self::AkamaiSbsdTaskProxyless => "AkamaiSBSDTaskProxyless",
Self::TlsTask => "TlsTask",
Self::Cloudflare5sTask => "CloudFlare5STask",
Self::CloudflareTurnstileTask => "CloudFlareTurnstileTask",
Self::DataDomeTaskProxyless => "DataDomeTaskProxyless",
Self::DataDomeTagsTaskProxyless => "DataDomeTagsTaskProxyless",
Self::IncapsulaTaskProxyless => "IncapsulaTaskProxyless",
}
}
#[must_use]
pub const fn mode(&self) -> Option<TaskMode> {
match self {
Self::RecaptchaV2TaskProxyless
| Self::RecaptchaV2TaskProxylessS9
| Self::RecaptchaV2STaskProxyless
| Self::RecaptchaV2EnterpriseTaskProxyless
| Self::RecaptchaV2SEnterpriseTaskProxyless
| Self::RecaptchaV3TaskProxyless
| Self::RecaptchaV3TaskProxylessS9
| Self::RecaptchaV3EnterpriseTaskProxyless
| Self::RecaptchaV3EnterpriseTaskProxylessS9
| Self::FuncaptchaTaskProxyless
| Self::PerimeterX
| Self::Hcaptcha
| Self::Cloudflare5sTask
| Self::CloudflareTurnstileTask => Some(TaskMode::Async),
Self::RecaptchaV2Classification
| Self::FuncaptchaClassification
| Self::HcaptchaClassification
| Self::AkamaiWebTaskProxyless
| Self::AkamaiSbsdTaskProxyless
| Self::TlsTask
| Self::DataDomeTaskProxyless
| Self::DataDomeTagsTaskProxyless
| Self::IncapsulaTaskProxyless => Some(TaskMode::Sync),
}
}
fn from_wire(value: &str) -> Option<Self> {
Self::KNOWN
.iter()
.find(|task_type| task_type.as_str().eq_ignore_ascii_case(value))
.copied()
}
}
pub trait TaskTypeName {
fn task_type_name(&self) -> &str;
}
impl TaskTypeName for TaskType {
fn task_type_name(&self) -> &str {
self.as_str()
}
}
impl TaskTypeName for str {
fn task_type_name(&self) -> &str {
self
}
}
impl TaskTypeName for String {
fn task_type_name(&self) -> &str {
self
}
}
impl<T> TaskTypeName for &T
where
T: TaskTypeName + ?Sized,
{
fn task_type_name(&self) -> &str {
(*self).task_type_name()
}
}
impl fmt::Display for TaskType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Serialize for TaskType {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for TaskType {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::from_wire(&value)
.ok_or_else(|| D::Error::custom(format!("unknown task type `{value}`")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_type_deserialization_is_case_insensitive() -> Result<(), serde_json::Error> {
let task_type: TaskType = serde_json::from_str(r#""recaptchav2taskproxyless""#)?;
assert_eq!(task_type, TaskType::RecaptchaV2TaskProxyless);
assert_eq!(
serde_json::to_string(&task_type)?,
r#""ReCaptchaV2TaskProxyless""#
);
Ok(())
}
#[test]
fn a_plain_string_can_stand_in_for_a_task_type() {
assert_eq!("BrandNewTaskType".task_type_name(), "BrandNewTaskType");
assert_eq!(
TaskType::RecaptchaV2TaskProxyless.task_type_name(),
"ReCaptchaV2TaskProxyless"
);
}
#[test]
fn unknown_type_is_rejected() {
let error = serde_json::from_str::<TaskType>(r#""FutureTask""#).unwrap_err();
assert!(error.to_string().contains("unknown task type `FutureTask`"));
}
}