1use serde::{Deserialize, Deserializer, Serialize};
15use validator::Validate;
16
17fn de_opt_string_from_number_or_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
19where
20 D: Deserializer<'de>,
21{
22 let v = serde_json::Value::deserialize(deserializer)?;
23 match v {
24 serde_json::Value::Null => Ok(None),
25 serde_json::Value::String(s) => Ok(Some(s)),
26 serde_json::Value::Number(n) => Ok(Some(n.to_string())),
27 other => Err(serde::de::Error::custom(format!(
28 "expected string or number, got {}",
29 other
30 ))),
31 }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
36pub enum ModerationModel {
37 #[serde(rename = "moderation")]
39 #[default]
40 Moderation,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(untagged)]
46pub enum ModerationInput {
47 Text(String),
49 Multimedia(MultimediaInput),
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
55pub struct MultimediaInput {
56 #[serde(rename = "type")]
58 pub content_type: MediaType,
59 #[validate(url)]
61 pub url: String,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub enum MediaType {
67 #[serde(rename = "image")]
69 Image,
70 #[serde(rename = "audio")]
72 Audio,
73 #[serde(rename = "video")]
75 Video,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ModerationRequest {
81 #[serde(default)]
83 pub model: ModerationModel,
84 pub input: ModerationInput,
86}
87
88impl ModerationRequest {
89 pub fn new_text(text: impl Into<String>) -> Self {
91 Self {
92 model: ModerationModel::default(),
93 input: ModerationInput::Text(text.into()),
94 }
95 }
96
97 pub fn new_multimedia(content_type: MediaType, url: impl Into<String>) -> Self {
99 Self {
100 model: ModerationModel::default(),
101 input: ModerationInput::Multimedia(MultimediaInput {
102 content_type,
103 url: url.into(),
104 }),
105 }
106 }
107
108 pub fn validate(&self) -> Result<(), validator::ValidationErrors> {
110 let mut errors = validator::ValidationErrors::new();
111
112 if let ModerationInput::Text(text) = &self.input
114 && text.len() > 2000
115 {
116 errors.add(
117 "input",
118 validator::ValidationError::new("text_length_exceeded"),
119 );
120 }
121
122 if let ModerationInput::Multimedia(multimedia) = &self.input
124 && multimedia.url.parse::<url::Url>().is_err()
125 {
126 errors.add("input", validator::ValidationError::new("invalid_url"));
127 }
128
129 if errors.is_empty() {
130 Ok(())
131 } else {
132 Err(errors)
133 }
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum RiskLevel {
140 #[serde(rename = "PASS")]
142 Pass,
143 #[serde(rename = "REVIEW")]
145 Review,
146 #[serde(rename = "REJECT")]
148 Reject,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub enum RiskType {
154 #[serde(rename = "porn")]
156 Porn,
157 #[serde(rename = "violence")]
159 Violence,
160 #[serde(rename = "illegal")]
162 Illegal,
163 #[serde(rename = "politics")]
165 Politics,
166 #[serde(rename = "other")]
168 Other,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct ModerationResult {
174 #[serde(rename = "content_type")]
176 pub content_type: String,
177 #[serde(rename = "risk_level")]
179 pub risk_level: RiskLevel,
180 #[serde(rename = "risk_type")]
182 pub risk_types: Vec<String>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ModerationUsage {
188 #[serde(rename = "moderation_text")]
190 pub moderation_text: ModerationTextUsage,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct ModerationTextUsage {
196 #[serde(rename = "call_count")]
198 pub call_count: u32,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ModerationResponse {
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub id: Option<String>,
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub created: Option<u64>,
210 #[serde(
212 rename = "request_id",
213 skip_serializing_if = "Option::is_none",
214 deserialize_with = "de_opt_string_from_number_or_string"
215 )]
216 pub request_id: Option<String>,
217 #[serde(rename = "result_list", skip_serializing_if = "Option::is_none")]
219 pub result_list: Option<Vec<ModerationResult>>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub usage: Option<ModerationUsage>,
223}