1use serde::{Deserialize, Deserializer, Serialize};
14use validator::Validate;
15
16fn de_opt_string_from_number_or_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
18where
19 D: Deserializer<'de>,
20{
21 let v = serde_json::Value::deserialize(deserializer)?;
22 match v {
23 serde_json::Value::Null => Ok(None),
24 serde_json::Value::String(s) => Ok(Some(s)),
25 serde_json::Value::Number(n) => Ok(Some(n.to_string())),
26 other => Err(serde::de::Error::custom(format!(
27 "expected string or number, got {}",
28 other
29 ))),
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub enum ModerationModel {
36 #[serde(rename = "moderation")]
38 Moderation,
39}
40
41impl Default for ModerationModel {
42 fn default() -> Self {
43 Self::Moderation
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(untagged)]
50pub enum ModerationInput {
51 Text(String),
53 Multimedia(MultimediaInput),
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
59pub struct MultimediaInput {
60 #[serde(rename = "type")]
62 pub content_type: MediaType,
63 #[validate(url)]
65 pub url: String,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum MediaType {
71 #[serde(rename = "image")]
73 Image,
74 #[serde(rename = "audio")]
76 Audio,
77 #[serde(rename = "video")]
79 Video,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ModerationRequest {
85 #[serde(default)]
87 pub model: ModerationModel,
88 pub input: ModerationInput,
90}
91
92impl ModerationRequest {
93 pub fn new_text(text: impl Into<String>) -> Self {
95 Self {
96 model: ModerationModel::default(),
97 input: ModerationInput::Text(text.into()),
98 }
99 }
100
101 pub fn new_multimedia(content_type: MediaType, url: impl Into<String>) -> Self {
103 Self {
104 model: ModerationModel::default(),
105 input: ModerationInput::Multimedia(MultimediaInput {
106 content_type,
107 url: url.into(),
108 }),
109 }
110 }
111
112 pub fn validate(&self) -> Result<(), validator::ValidationErrors> {
114 let mut errors = validator::ValidationErrors::new();
115
116 if let ModerationInput::Text(text) = &self.input {
118 if text.len() > 2000 {
119 errors.add(
120 "input",
121 validator::ValidationError::new("text_length_exceeded"),
122 );
123 }
124 }
125
126 if let ModerationInput::Multimedia(multimedia) = &self.input {
128 if multimedia.url.parse::<url::Url>().is_err() {
129 errors.add("input", validator::ValidationError::new("invalid_url"));
130 }
131 }
132
133 if errors.is_empty() {
134 Ok(())
135 } else {
136 Err(errors)
137 }
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub enum RiskLevel {
144 #[serde(rename = "PASS")]
146 Pass,
147 #[serde(rename = "REVIEW")]
149 Review,
150 #[serde(rename = "REJECT")]
152 Reject,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub enum RiskType {
158 #[serde(rename = "porn")]
160 Porn,
161 #[serde(rename = "violence")]
163 Violence,
164 #[serde(rename = "illegal")]
166 Illegal,
167 #[serde(rename = "politics")]
169 Politics,
170 #[serde(rename = "other")]
172 Other,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct ModerationResult {
178 #[serde(rename = "content_type")]
180 pub content_type: String,
181 #[serde(rename = "risk_level")]
183 pub risk_level: RiskLevel,
184 #[serde(rename = "risk_type")]
186 pub risk_types: Vec<String>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ModerationUsage {
192 #[serde(rename = "moderation_text")]
194 pub moderation_text: ModerationTextUsage,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct ModerationTextUsage {
200 #[serde(rename = "call_count")]
202 pub call_count: u32,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ModerationResponse {
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub id: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub created: Option<u64>,
214 #[serde(
216 rename = "request_id",
217 skip_serializing_if = "Option::is_none",
218 deserialize_with = "de_opt_string_from_number_or_string"
219 )]
220 pub request_id: Option<String>,
221 #[serde(rename = "result_list", skip_serializing_if = "Option::is_none")]
223 pub result_list: Option<Vec<ModerationResult>>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub usage: Option<ModerationUsage>,
227}