use serde::{Deserialize, Deserializer, Serialize};
use validator::Validate;
fn de_opt_string_from_number_or_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let v = serde_json::Value::deserialize(deserializer)?;
match v {
serde_json::Value::Null => Ok(None),
serde_json::Value::String(s) => Ok(Some(s)),
serde_json::Value::Number(n) => Ok(Some(n.to_string())),
other => Err(serde::de::Error::custom(format!(
"expected string or number, got {}",
other
))),
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum ModerationModel {
#[serde(rename = "moderation")]
#[default]
Moderation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ModerationInput {
Text(String),
Multimedia(MultimediaInput),
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct MultimediaInput {
#[serde(rename = "type")]
pub content_type: MediaType,
#[validate(url)]
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MediaType {
#[serde(rename = "image")]
Image,
#[serde(rename = "audio")]
Audio,
#[serde(rename = "video")]
Video,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModerationRequest {
#[serde(default)]
pub model: ModerationModel,
pub input: ModerationInput,
}
impl ModerationRequest {
pub fn new_text(text: impl Into<String>) -> Self {
Self {
model: ModerationModel::default(),
input: ModerationInput::Text(text.into()),
}
}
pub fn new_multimedia(content_type: MediaType, url: impl Into<String>) -> Self {
Self {
model: ModerationModel::default(),
input: ModerationInput::Multimedia(MultimediaInput {
content_type,
url: url.into(),
}),
}
}
pub fn validate(&self) -> Result<(), validator::ValidationErrors> {
let mut errors = validator::ValidationErrors::new();
if let ModerationInput::Text(text) = &self.input
&& text.len() > 2000
{
errors.add(
"input",
validator::ValidationError::new("text_length_exceeded"),
);
}
if let ModerationInput::Multimedia(multimedia) = &self.input
&& multimedia.url.parse::<url::Url>().is_err()
{
errors.add("input", validator::ValidationError::new("invalid_url"));
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskLevel {
#[serde(rename = "PASS")]
Pass,
#[serde(rename = "REVIEW")]
Review,
#[serde(rename = "REJECT")]
Reject,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskType {
#[serde(rename = "porn")]
Porn,
#[serde(rename = "violence")]
Violence,
#[serde(rename = "illegal")]
Illegal,
#[serde(rename = "politics")]
Politics,
#[serde(rename = "other")]
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModerationResult {
#[serde(rename = "content_type")]
pub content_type: String,
#[serde(rename = "risk_level")]
pub risk_level: RiskLevel,
#[serde(rename = "risk_type")]
pub risk_types: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModerationUsage {
#[serde(rename = "moderation_text")]
pub moderation_text: ModerationTextUsage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModerationTextUsage {
#[serde(rename = "call_count")]
pub call_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModerationResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<u64>,
#[serde(
rename = "request_id",
skip_serializing_if = "Option::is_none",
deserialize_with = "de_opt_string_from_number_or_string"
)]
pub request_id: Option<String>,
#[serde(rename = "result_list", skip_serializing_if = "Option::is_none")]
pub result_list: Option<Vec<ModerationResult>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<ModerationUsage>,
}