reinfer_client/resources/
source.rs

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// TODO(mcobzarenco)[3963]: Make `Identifier` into a trait (ensure it still implements
81// `FromStr` so we can take T: Identifier as a clap command line argument).
82#[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}