1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_with::{DeserializeFromStr, SerializeDisplay};
4use std::{
5 fmt::{Display, Formatter, Result as FmtResult},
6 str::FromStr,
7};
8
9use crate::{
10 error::{Error, Result},
11 resources::bucket::Id as BucketId,
12 resources::user::Username,
13 CommentFilter,
14};
15
16#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
17pub struct TransformTag(pub String);
18
19impl FromStr for TransformTag {
20 type Err = Error;
21
22 fn from_str(string: &str) -> Result<Self> {
23 Ok(Self(string.to_owned()))
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Default)]
28pub struct StatisticsRequestParams {
29 pub comment_filter: CommentFilter,
30}
31
32#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
33pub struct Source {
34 pub id: Id,
35 pub owner: Username,
36 pub name: Name,
37 pub title: String,
38 pub description: String,
39 pub language: String,
40 pub should_translate: bool,
41 pub created_at: DateTime<Utc>,
42 pub updated_at: DateTime<Utc>,
43 pub bucket_id: Option<BucketId>,
44
45 #[serde(rename = "_kind")]
46 pub kind: SourceKind,
47 #[serde(default, rename = "email_transform_tag")]
48 pub transform_tag: Option<TransformTag>,
49}
50
51impl Source {
52 pub fn full_name(&self) -> FullName {
53 FullName(format!("{}/{}", self.owner.0, self.name.0))
54 }
55}
56
57#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
58pub struct Name(pub String);
59
60#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
61pub struct FullName(pub String);
62
63impl FromStr for FullName {
64 type Err = Error;
65
66 fn from_str(string: &str) -> Result<Self> {
67 if string.split('/').count() == 2 {
68 Ok(FullName(string.into()))
69 } else {
70 Err(Error::BadSourceIdentifier {
71 identifier: string.into(),
72 })
73 }
74 }
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
78pub struct Id(pub String);
79
80#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
83pub enum Identifier {
84 Id(Id),
85 FullName(FullName),
86}
87
88impl From<Id> for Identifier {
89 fn from(id: Id) -> Self {
90 Identifier::Id(id)
91 }
92}
93
94impl From<FullName> for Identifier {
95 fn from(full_name: FullName) -> Self {
96 Identifier::FullName(full_name)
97 }
98}
99
100impl From<&Source> for Identifier {
101 fn from(source: &Source) -> Self {
102 Identifier::FullName(source.full_name())
103 }
104}
105
106impl FromStr for Identifier {
107 type Err = Error;
108
109 fn from_str(string: &str) -> Result<Self> {
110 if string.chars().all(|c| c.is_ascii_hexdigit()) {
111 Ok(Identifier::Id(Id(string.into())))
112 } else {
113 FullName::from_str(string).map(Identifier::FullName)
114 }
115 }
116}
117
118impl Display for Identifier {
119 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
120 write!(
121 formatter,
122 "{}",
123 match self {
124 Identifier::Id(id) => &id.0,
125 Identifier::FullName(full_name) => &full_name.0,
126 }
127 )
128 }
129}
130
131#[derive(Debug, Clone, SerializeDisplay, DeserializeFromStr, PartialEq, Eq, Hash)]
132pub enum SourceKind {
133 Call,
134 Chat,
135 Unknown(Box<str>),
136 IxpRuntime,
137 IxpDesignTime,
138}
139
140impl FromStr for SourceKind {
141 type Err = Error;
142
143 fn from_str(string: &str) -> Result<Self> {
144 Ok(match string {
145 "call" => SourceKind::Call,
146 "chat" => SourceKind::Chat,
147 "ixp_runtime" => SourceKind::IxpRuntime,
148 "ixp_design" => SourceKind::IxpDesignTime,
149 value => SourceKind::Unknown(value.into()),
150 })
151 }
152}
153
154impl Display for SourceKind {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(
157 f,
158 "{}",
159 match self {
160 SourceKind::Call => "call",
161 SourceKind::Chat => "chat",
162 SourceKind::Unknown(value) => value.as_ref(),
163 SourceKind::IxpRuntime => "ixp_runtime",
164 SourceKind::IxpDesignTime => "ixp_design",
165 }
166 )
167 }
168}
169
170#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
171pub struct NewSource<'request> {
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub title: Option<&'request str>,
174
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub description: Option<&'request str>,
177
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub language: Option<&'request str>,
180
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub should_translate: Option<bool>,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub bucket_id: Option<BucketId>,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub sensitive_properties: Option<Vec<&'request str>>,
189
190 #[serde(skip_serializing_if = "Option::is_none", rename = "_kind")]
191 pub kind: Option<&'request SourceKind>,
192
193 #[serde(
194 skip_serializing_if = "Option::is_none",
195 rename = "email_transform_tag"
196 )]
197 pub transform_tag: Option<&'request TransformTag>,
198}
199
200#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
201pub(crate) struct CreateRequest<'request> {
202 pub source: NewSource<'request>,
203}
204
205#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
206pub(crate) struct CreateResponse {
207 pub source: Source,
208}
209
210#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
211pub(crate) struct GetAvailableResponse {
212 pub sources: Vec<Source>,
213}
214
215#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
216pub(crate) struct GetResponse {
217 pub source: Source,
218}
219
220#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
221pub struct UpdateSource<'request> {
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub title: Option<&'request str>,
224
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub description: Option<&'request str>,
227
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub should_translate: Option<bool>,
230
231 pub bucket_id: Option<BucketId>,
232
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub sensitive_properties: Option<Vec<&'request str>>,
235
236 #[serde(
237 skip_serializing_if = "Option::is_none",
238 rename = "email_transform_tag"
239 )]
240 pub transform_tag: Option<&'request TransformTag>,
241}
242
243#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
244pub(crate) struct UpdateRequest<'request> {
245 pub source: UpdateSource<'request>,
246}
247
248#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
249pub(crate) struct UpdateResponse {
250 pub source: Source,
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn source_kind_roundtrips() {
259 assert_eq!(SourceKind::Call, SourceKind::from_str("call").unwrap());
260 assert_eq!(
261 &serde_json::ser::to_string(&SourceKind::Call).unwrap(),
262 "\"call\""
263 );
264
265 assert_eq!(SourceKind::Chat, SourceKind::from_str("chat").unwrap());
266 assert_eq!(
267 &serde_json::ser::to_string(&SourceKind::Chat).unwrap(),
268 "\"chat\""
269 );
270 }
271
272 #[test]
273 fn unknown_source_kind_roundtrips() {
274 let kind = SourceKind::from_str("unknown").unwrap();
275 match &kind {
276 SourceKind::Unknown(error) => assert_eq!(&**error, "unknown"),
277 _ => panic!("Expected error to be parsed as Unknown(..)"),
278 }
279
280 assert_eq!(&serde_json::ser::to_string(&kind).unwrap(), "\"unknown\"")
281 }
282}