reinfer_client/resources/
comment.rs

1use crate::{
2    error::{Error, Result},
3    resources::attachments::AttachmentMetadata,
4    resources::entity_def::Name as EntityName,
5    resources::label_def::Name as LabelName,
6    resources::label_group::Name as LabelGroupName,
7    resources::label_group::DEFAULT_LABEL_GROUP_NAME,
8    ReducibleResponse, SourceId, SplittableRequest,
9};
10use chrono::{DateTime, Utc};
11use ordered_float::NotNan;
12use serde::{
13    de::{Deserializer, Error as SerdeError, MapAccess, Visitor},
14    ser::{SerializeMap, Serializer},
15    Deserialize, Serialize,
16};
17use serde_json::Value as JsonValue;
18use std::{
19    collections::HashMap,
20    fmt::{Formatter, Result as FmtResult},
21    ops::{Deref, DerefMut},
22    path::PathBuf,
23    result::Result as StdResult,
24    str::FromStr,
25};
26
27#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
28pub struct Id(pub String);
29
30impl FromStr for Id {
31    type Err = Error;
32
33    fn from_str(string: &str) -> Result<Self> {
34        Ok(Self(string.to_owned()))
35    }
36}
37
38#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
39pub struct Uid(pub String);
40
41impl FromStr for Uid {
42    type Err = Error;
43
44    fn from_str(string: &str) -> Result<Self> {
45        Ok(Self(string.to_owned()))
46    }
47}
48
49#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
50pub struct ThreadId(pub String);
51
52#[derive(Debug, Clone, Serialize, Deserialize, Default)]
53pub struct CommentTimestampFilter {
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub minimum: Option<DateTime<Utc>>,
56
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub maximum: Option<DateTime<Utc>>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum ReviewedFilterEnum {
64    OnlyReviewed,
65    OnlyUnreviewed,
66}
67
68type UserPropertyName = String;
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct UserPropertiesFilter(pub HashMap<UserPropertyName, PropertyFilter>);
72
73#[derive(Default, Debug, Clone, Serialize, Deserialize)]
74pub struct PropertyFilter {
75    #[serde(skip_serializing_if = "<[_]>::is_empty", default)]
76    pub one_of: Vec<PropertyValue>,
77    #[serde(skip_serializing_if = "<[_]>::is_empty", default)]
78    pub not_one_of: Vec<PropertyValue>,
79    #[serde(skip_serializing_if = "<[_]>::is_empty", default)]
80    pub domain_not_one_of: Vec<PropertyValue>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub minimum: Option<NotNan<f64>>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub maximum: Option<NotNan<f64>>,
85}
86
87impl PropertyFilter {
88    pub fn new(
89        one_of: Vec<PropertyValue>,
90        not_one_of: Vec<PropertyValue>,
91        domain_not_one_of: Vec<PropertyValue>,
92    ) -> Self {
93        Self {
94            one_of,
95            not_one_of,
96            domain_not_one_of,
97            ..Default::default()
98        }
99    }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
103pub struct CommentFilter {
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub reviewed: Option<ReviewedFilterEnum>,
106
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub timestamp: Option<CommentTimestampFilter>,
109
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub user_properties: Option<UserPropertiesFilter>,
112
113    #[serde(skip_serializing_if = "Vec::is_empty")]
114    #[serde(default)]
115    pub sources: Vec<SourceId>,
116
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub messages: Option<MessagesFilter>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, Default)]
122pub struct MessagesFilter {
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub from: Option<PropertyFilter>,
125
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub to: Option<PropertyFilter>,
128}
129
130#[derive(Debug, Clone, Serialize)]
131#[serde(rename_all(serialize = "lowercase"))]
132pub enum CommentPredictionsThreshold {
133    Auto,
134}
135
136#[derive(Debug, Clone, Serialize)]
137pub struct TriggerLabelThreshold {
138    pub name: Vec<String>,
139    pub threshold: NotNan<f64>,
140}
141
142#[derive(Debug, Clone, Serialize)]
143pub struct GetCommentPredictionsRequest {
144    pub uids: Vec<String>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub threshold: Option<CommentPredictionsThreshold>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub labels: Option<Vec<TriggerLabelThreshold>>,
149}
150
151#[derive(Debug, Clone, Serialize)]
152pub(crate) struct GetRecentRequest<'a> {
153    pub limit: usize,
154    pub filter: &'a CommentFilter,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub continuation: Option<&'a Continuation>,
157}
158
159#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
160pub struct Continuation(pub String);
161
162#[derive(Debug, Clone, Deserialize)]
163pub struct RecentCommentsPage {
164    pub results: Vec<AnnotatedComment>,
165    pub continuation: Option<Continuation>,
166}
167
168#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
169pub struct GetLabellingsAfter(pub String);
170
171#[derive(Debug, Clone, Deserialize)]
172pub struct GetAnnotationsResponse {
173    pub results: Vec<AnnotatedComment>,
174    #[serde(default)]
175    pub after: Option<GetLabellingsAfter>,
176}
177
178#[derive(Debug, Clone, Deserialize)]
179pub struct GetPredictionsResponse {
180    pub predictions: Vec<Prediction>,
181}
182
183#[derive(Debug, Clone, Serialize)]
184pub struct UpdateAnnotationsRequest<'a> {
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub labelling: Option<&'a [NewLabelling]>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub entities: Option<&'a NewEntities>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub moon_forms: Option<&'a [NewMoonForm]>,
191}
192
193#[derive(Debug, Clone, Deserialize)]
194pub struct CommentsIterPage {
195    pub comments: Vec<Comment>,
196    pub continuation: Option<Continuation>,
197}
198
199#[derive(Debug, Clone, Serialize)]
200pub(crate) struct PutCommentsRequest {
201    pub comments: Vec<NewComment>,
202}
203
204impl SplittableRequest for PutCommentsRequest {
205    fn split(self) -> impl Iterator<Item = Self> {
206        self.comments.into_iter().map(|comment| Self {
207            comments: vec![comment],
208        })
209    }
210
211    fn count(&self) -> usize {
212        self.comments.len()
213    }
214}
215
216#[derive(Default, Debug, Copy, Clone, Deserialize)]
217pub struct PutCommentsResponse;
218
219impl ReducibleResponse for PutCommentsResponse {}
220
221#[derive(Debug, Clone, Serialize)]
222pub(crate) struct SyncCommentsRequest {
223    pub comments: Vec<NewComment>,
224}
225
226impl SplittableRequest for SyncCommentsRequest {
227    fn split(self) -> impl Iterator<Item = Self>
228    where
229        Self: Sized,
230    {
231        self.comments.into_iter().map(|comment| Self {
232            comments: vec![comment],
233        })
234    }
235
236    fn count(&self) -> usize {
237        self.comments.len()
238    }
239}
240
241#[derive(Debug, Clone, Deserialize, Default)]
242pub struct SyncCommentsResponse {
243    pub new: usize,
244    pub updated: usize,
245    pub unchanged: usize,
246}
247
248impl ReducibleResponse for SyncCommentsResponse {
249    fn empty() -> Self {
250        Self {
251            new: 0,
252            updated: 0,
253            unchanged: 0,
254        }
255    }
256    fn merge(self, b: Self) -> Self {
257        Self {
258            new: self.new + b.new,
259            updated: self.updated + b.updated,
260            unchanged: self.unchanged + b.unchanged,
261        }
262    }
263}
264
265#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
266pub struct GetCommentResponse {
267    pub comment: Comment,
268}
269
270#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
271pub struct Comment {
272    pub id: Id,
273    pub uid: Uid,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub thread_id: Option<ThreadId>,
276    pub timestamp: DateTime<Utc>,
277    pub messages: Vec<Message>,
278    #[serde(skip_serializing_if = "PropertyMap::is_empty", default)]
279    pub user_properties: PropertyMap,
280    #[serde(skip_serializing_if = "Vec::is_empty", default)]
281    pub attachments: Vec<AttachmentMetadata>,
282    pub created_at: DateTime<Utc>,
283
284    #[serde(default)]
285    pub has_annotations: bool,
286}
287
288#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
289pub struct NewComment {
290    pub id: Id,
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub thread_id: Option<ThreadId>,
293    pub timestamp: DateTime<Utc>,
294    pub messages: Vec<Message>,
295    #[serde(skip_serializing_if = "PropertyMap::is_empty", default)]
296    pub user_properties: PropertyMap,
297    #[serde(skip_serializing_if = "Vec::is_empty", default)]
298    pub attachments: Vec<AttachmentMetadata>,
299}
300
301#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
302pub struct Message {
303    pub body: MessageBody,
304
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub language: Option<String>,
307
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub subject: Option<MessageSubject>,
310
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub signature: Option<MessageSignature>,
313
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub from: Option<String>,
316
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub to: Option<Vec<String>>,
319
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub cc: Option<Vec<String>>,
322
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub bcc: Option<Vec<String>>,
325
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub sent_at: Option<DateTime<Utc>>,
328}
329
330#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
331pub struct MessageBody {
332    pub text: String,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub translated_from: Option<String>,
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub text_markup: Option<JsonValue>,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub translated_from_markup: Option<JsonValue>,
339}
340
341#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
342pub struct MessageSubject {
343    pub text: String,
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub translated_from: Option<String>,
346}
347
348#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
349pub struct MessageSignature {
350    pub text: String,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub translated_from: Option<String>,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub text_markup: Option<JsonValue>,
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub translated_from_markup: Option<JsonValue>,
357}
358
359#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
360pub enum Sentiment {
361    #[serde(rename = "positive")]
362    Positive,
363
364    #[serde(rename = "negative")]
365    Negative,
366}
367
368#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
369pub struct AttachmentReference(pub String);
370
371#[derive(Debug, Clone, PartialEq, Default, Eq)]
372pub struct PropertyMap(HashMap<String, PropertyValue>);
373
374#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
375#[serde(untagged)]
376pub enum PropertyValue {
377    String(String),
378    Number(NotNan<f64>),
379}
380
381impl Deref for PropertyMap {
382    type Target = HashMap<String, PropertyValue>;
383
384    fn deref(&self) -> &Self::Target {
385        &self.0
386    }
387}
388
389impl DerefMut for PropertyMap {
390    fn deref_mut(&mut self) -> &mut Self::Target {
391        &mut self.0
392    }
393}
394
395impl PropertyMap {
396    #[inline]
397    pub fn new() -> Self {
398        Default::default()
399    }
400
401    #[inline]
402    pub fn with_capacity(capacity: usize) -> Self {
403        PropertyMap(HashMap::with_capacity(capacity))
404    }
405
406    #[inline]
407    pub fn insert_number(&mut self, key: String, value: NotNan<f64>) {
408        self.0.insert(key, PropertyValue::Number(value));
409    }
410
411    #[inline]
412    pub fn insert_string(&mut self, key: String, value: String) {
413        self.0.insert(key, PropertyValue::String(value));
414    }
415
416    // Provided despite deref, for `skip_serializing_if`.
417    #[inline]
418    pub fn is_empty(&self) -> bool {
419        self.0.is_empty()
420    }
421}
422
423const STRING_PROPERTY_PREFIX: &str = "string:";
424const NUMBER_PROPERTY_PREFIX: &str = "number:";
425
426impl Serialize for PropertyMap {
427    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
428        let mut state = serializer.serialize_map(Some(self.len()))?;
429        if self.0.is_empty() {
430            return state.end();
431        }
432
433        let mut full_name = String::with_capacity(32);
434        for (key, value) in &self.0 {
435            full_name.clear();
436            match value {
437                PropertyValue::String(value) => {
438                    if !value.trim().is_empty() {
439                        full_name.push_str(STRING_PROPERTY_PREFIX);
440                        full_name.push_str(key);
441                        state.serialize_entry(&full_name, &value)?;
442                    }
443                }
444                PropertyValue::Number(value) => {
445                    full_name.push_str(NUMBER_PROPERTY_PREFIX);
446                    full_name.push_str(key);
447                    state.serialize_entry(&full_name, &value)?;
448                }
449            }
450        }
451        state.end()
452    }
453}
454
455impl<'de> Deserialize<'de> for PropertyMap {
456    #[inline]
457    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
458        deserializer.deserialize_any(PropertyMapVisitor)
459    }
460}
461
462struct PropertyMapVisitor;
463impl<'de> Visitor<'de> for PropertyMapVisitor {
464    type Value = PropertyMap;
465
466    fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
467        write!(formatter, "a user property map")
468    }
469
470    #[inline]
471    fn visit_unit<E>(self) -> StdResult<PropertyMap, E> {
472        Ok(PropertyMap::new())
473    }
474
475    fn visit_map<M>(self, mut access: M) -> StdResult<PropertyMap, M::Error>
476    where
477        M: MapAccess<'de>,
478    {
479        let mut values = PropertyMap::with_capacity(access.size_hint().unwrap_or(0));
480
481        while let Some(mut key) = access.next_key()? {
482            if strip_prefix(&mut key, STRING_PROPERTY_PREFIX) {
483                values.insert(key, PropertyValue::String(access.next_value()?));
484            } else if strip_prefix(&mut key, NUMBER_PROPERTY_PREFIX) {
485                values.insert(key, PropertyValue::Number(access.next_value()?));
486            } else {
487                return Err(M::Error::custom(format!(
488                    "user property full name `{key}` has invalid \
489                     type prefix"
490                )));
491            }
492        }
493
494        Ok(values)
495    }
496}
497
498#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
499pub struct AnnotatedComment {
500    pub comment: Comment,
501    #[serde(skip_serializing_if = "should_skip_serializing_labelling")]
502    pub labelling: Option<Vec<Labelling>>,
503    #[serde(skip_serializing_if = "should_skip_serializing_entities")]
504    pub entities: Option<Entities>,
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub thread_properties: Option<ThreadProperties>,
507    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec", default)]
508    pub moon_forms: Option<Vec<MoonForm>>,
509    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec", default)]
510    pub label_properties: Option<Vec<LabelProperty>>,
511}
512
513#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
514pub struct Prediction {
515    pub uid: Uid,
516    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec")]
517    pub labels: Option<Vec<PredictedLabel>>,
518    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec")]
519    pub entities: Option<Vec<Entity>>,
520}
521
522pub fn get_default_labelling_group(labelling: &Option<Vec<Labelling>>) -> Option<&Labelling> {
523    labelling
524        .iter()
525        .flatten()
526        .find(|&labelling_group| labelling_group.is_default_group())
527}
528
529impl Labelling {
530    pub fn is_default_group(&self) -> bool {
531        self.group == *DEFAULT_LABEL_GROUP_NAME
532    }
533}
534
535impl NewLabelling {
536    pub fn is_default_group(&self) -> bool {
537        self.group == *DEFAULT_LABEL_GROUP_NAME
538    }
539}
540
541impl HasAnnotations for AnnotatedComment {
542    fn has_annotations(&self) -> bool {
543        let has_labels = self.labelling.iter().flatten().any(|labelling_group| {
544            !labelling_group.assigned.is_empty() || !labelling_group.dismissed.is_empty()
545        });
546        let has_entities = self
547            .entities
548            .as_ref()
549            .map(|entities| !entities.assigned.is_empty() || !entities.dismissed.is_empty())
550            .unwrap_or(false);
551        has_labels || has_entities || self.moon_forms.has_annotations()
552    }
553}
554
555impl AnnotatedComment {
556    pub fn without_predictions(mut self) -> Self {
557        self.labelling = self.labelling.and_then(|mut labelling| {
558            if labelling.iter().all(|labelling_group| {
559                labelling_group.assigned.is_empty() && labelling_group.dismissed.is_empty()
560            }) {
561                None
562            } else {
563                for comment_labelling in &mut labelling {
564                    comment_labelling.predicted = None;
565                }
566                Some(labelling)
567            }
568        });
569        self.entities = self.entities.and_then(|mut entities| {
570            if entities.assigned.is_empty() && entities.dismissed.is_empty() {
571                None
572            } else {
573                entities.predicted = None;
574                Some(entities)
575            }
576        });
577        self
578    }
579}
580
581#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
582pub struct ThreadProperties {
583    duration: Option<NotNan<f64>>,
584    response_time: Option<NotNan<f64>>,
585    num_messages: u64,
586    thread_position: Option<u64>,
587    first_sender: Option<String>,
588}
589
590#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
591#[serde(untagged)]
592pub enum EitherLabelling {
593    Labelling(Vec<NewLabelling>),
594    LegacyLabelling(NewLegacyLabelling),
595}
596
597impl EitherLabelling {
598    fn into_new_labellings(self) -> Vec<NewLabelling> {
599        match self {
600            EitherLabelling::Labelling(new_labelling_vec) => new_labelling_vec,
601            EitherLabelling::LegacyLabelling(new_legacy_labelling) => {
602                vec![NewLabelling {
603                    group: DEFAULT_LABEL_GROUP_NAME.clone(),
604                    assigned: new_legacy_labelling.assigned,
605                    dismissed: new_legacy_labelling.dismissed,
606                }]
607            }
608        }
609    }
610}
611
612impl From<EitherLabelling> for Vec<NewLabelling> {
613    fn from(either_labelling: EitherLabelling) -> Vec<NewLabelling> {
614        either_labelling.into_new_labellings()
615    }
616}
617
618impl HasAnnotations for EitherLabelling {
619    fn has_annotations(&self) -> bool {
620        match self {
621            EitherLabelling::Labelling(new_labelling) => {
622                new_labelling.iter().any(|labelling_group| {
623                    labelling_group.assigned.is_some() || labelling_group.dismissed.is_some()
624                })
625            }
626            EitherLabelling::LegacyLabelling(new_legacy_labelling) => {
627                new_legacy_labelling.assigned.is_some() || new_legacy_labelling.dismissed.is_some()
628            }
629        }
630    }
631}
632
633#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
634pub struct NewAnnotatedComment {
635    pub comment: NewComment,
636    #[serde(skip_serializing_if = "Option::is_none")]
637    pub labelling: Option<EitherLabelling>,
638    #[serde(skip_serializing_if = "Option::is_none")]
639    pub entities: Option<NewEntities>,
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub audio_path: Option<PathBuf>,
642    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec", default)]
643    pub moon_forms: Option<Vec<NewMoonForm>>,
644}
645
646impl<T> HasAnnotations for Option<T>
647where
648    T: HasAnnotations,
649{
650    fn has_annotations(&self) -> bool {
651        self.as_ref().is_some_and(HasAnnotations::has_annotations)
652    }
653}
654
655impl HasAnnotations for Vec<MoonForm> {
656    fn has_annotations(&self) -> bool {
657        self.iter().any(|form| !form.assigned.is_empty())
658    }
659}
660
661impl HasAnnotations for Vec<NewMoonForm> {
662    fn has_annotations(&self) -> bool {
663        self.iter().any(|form| !form.assigned.is_empty())
664    }
665}
666
667impl NewAnnotatedComment {
668    pub fn has_annotations(&self) -> bool {
669        self.labelling.has_annotations()
670            || self.entities.has_annotations()
671            || self.moon_forms.has_annotations()
672    }
673}
674
675#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
676pub struct Labelling {
677    pub group: LabelGroupName,
678    #[serde(skip_serializing_if = "Vec::is_empty", default)]
679    pub assigned: Vec<Label>,
680    #[serde(skip_serializing_if = "Vec::is_empty", default)]
681    pub dismissed: Vec<Label>,
682    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec", default)]
683    pub predicted: Option<Vec<PredictedLabel>>,
684}
685
686#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
687pub struct NewLabelling {
688    pub group: LabelGroupName,
689    #[serde(skip_serializing_if = "Option::is_none", default)]
690    pub assigned: Option<Vec<Label>>,
691    #[serde(skip_serializing_if = "Option::is_none", default)]
692    pub dismissed: Option<Vec<Label>>,
693}
694
695/// Old, pre-label group labelling format.
696#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
697pub struct NewLegacyLabelling {
698    #[serde(skip_serializing_if = "Option::is_none", default)]
699    pub assigned: Option<Vec<Label>>,
700    #[serde(skip_serializing_if = "Option::is_none", default)]
701    pub dismissed: Option<Vec<Label>>,
702}
703
704#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
705pub struct Label {
706    pub name: LabelName,
707    pub sentiment: Sentiment,
708    #[serde(skip_serializing_if = "Option::is_none", default)]
709    pub metadata: Option<HashMap<String, JsonValue>>,
710}
711
712#[derive(Debug, Hash, Clone, Deserialize, Serialize, PartialEq, Eq)]
713#[serde(untagged)]
714pub enum PredictedLabelName {
715    Parts(Vec<String>),
716    String(LabelName),
717}
718
719impl PredictedLabelName {
720    pub fn to_label_name(&self) -> LabelName {
721        match self {
722            Self::Parts(parts) => LabelName(parts.join(" > ")),
723            Self::String(string) => string.clone(),
724        }
725    }
726}
727
728#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
729pub struct PredictedLabel {
730    pub name: PredictedLabelName,
731    #[serde(skip_serializing_if = "Option::is_none")]
732    pub sentiment: Option<NotNan<f64>>,
733    pub probability: NotNan<f64>,
734    #[serde(skip_serializing_if = "Option::is_none")]
735    pub auto_thresholds: Option<Vec<String>>,
736}
737
738#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
739pub struct LabelProperty {
740    pub id: String,
741    #[serde(skip_serializing_if = "Option::is_none")]
742    pub name: Option<String>,
743    pub value: NotNan<f64>,
744    #[serde(skip_serializing_if = "Option::is_none")]
745    pub breakdown: Option<LabelPropertyBreakdown>,
746}
747
748#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
749pub struct LabelPropertyBreakdown {
750    pub label_contributions: Vec<LabelPropertyContribution>,
751    pub other_group_contributions: Vec<LabelPropertyGroupContribution>,
752}
753
754#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
755pub struct LabelPropertyContribution {
756    pub name: LabelName,
757    pub value: NotNan<f64>,
758}
759
760#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
761pub struct LabelPropertyGroupContribution {
762    pub name: LabelGroupName,
763    pub value: NotNan<f64>,
764}
765
766#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
767pub struct Entities {
768    #[serde(skip_serializing_if = "Vec::is_empty")]
769    pub assigned: Vec<Entity>,
770    #[serde(skip_serializing_if = "Vec::is_empty")]
771    pub dismissed: Vec<Entity>,
772    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec", default)]
773    pub predicted: Option<Vec<Entity>>,
774}
775
776#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
777pub struct NewEntities {
778    #[serde(skip_serializing_if = "Vec::is_empty", default)]
779    pub assigned: Vec<NewEntity>,
780    #[serde(skip_serializing_if = "Vec::is_empty", default)]
781    pub dismissed: Vec<NewEntity>,
782}
783
784#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
785pub struct MoonFormCapture {
786    #[serde(skip_serializing_if = "Vec::is_empty", default)]
787    pub fields: Vec<MoonFormFieldAnnotation>,
788}
789
790#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
791pub struct MoonFormLabelCaptures {
792    pub label: Label,
793    #[serde(skip_serializing_if = "Vec::is_empty", default)]
794    pub captures: Vec<MoonFormCapture>,
795}
796
797#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
798pub struct MoonForm {
799    pub group: LabelGroupName,
800    #[serde(default)]
801    pub assigned: Vec<MoonFormLabelCaptures>,
802    #[serde(skip_serializing_if = "should_skip_serializing_optional_vec", default)]
803    pub predicted: Option<Vec<MoonFormLabelCaptures>>,
804    #[serde(default)]
805    pub dismissed: Vec<MoonFormLabelCaptures>,
806}
807
808#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
809pub struct NewMoonFormCapture {
810    #[serde(skip_serializing_if = "Vec::is_empty", default)]
811    pub fields: Vec<MoonFormFieldAnnotationNew>,
812}
813
814#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
815pub struct NewMoonFormLabelCaptures {
816    pub label: Label,
817    #[serde(skip_serializing_if = "Vec::is_empty", default)]
818    pub captures: Vec<NewMoonFormCapture>,
819}
820
821#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
822pub struct MoonFormDismissedUpdate {
823    #[serde(skip_serializing_if = "Option::is_none")]
824    pub captures: Option<Vec<NewMoonFormLabelCaptures>>,
825    pub entities: Vec<MoonFormFieldAnnotationNew>,
826    #[serde(default)]
827    pub labels: Vec<Label>,
828}
829
830#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
831pub struct NewMoonForm {
832    pub group: LabelGroupName,
833    #[serde(default)]
834    pub assigned: Vec<NewMoonFormLabelCaptures>,
835}
836
837pub trait HasAnnotations {
838    fn has_annotations(&self) -> bool;
839}
840
841impl HasAnnotations for NewEntities {
842    fn has_annotations(&self) -> bool {
843        !self.assigned.is_empty() || !self.dismissed.is_empty()
844    }
845}
846
847pub fn should_skip_serializing_optional_vec<T>(maybe_vec: &Option<Vec<T>>) -> bool {
848    if let Some(actual_vec) = maybe_vec {
849        actual_vec.is_empty()
850    } else {
851        true
852    }
853}
854
855fn should_skip_serializing_labelling(maybe_labelling: &Option<Vec<Labelling>>) -> bool {
856    if let Some(default_labelling) = get_default_labelling_group(maybe_labelling) {
857        default_labelling.assigned.is_empty()
858            && default_labelling.dismissed.is_empty()
859            && should_skip_serializing_optional_vec(&default_labelling.predicted)
860    } else {
861        true
862    }
863}
864
865fn should_skip_serializing_entities(maybe_entities: &Option<Entities>) -> bool {
866    if let Some(entities) = maybe_entities {
867        entities.assigned.is_empty()
868            && entities.dismissed.is_empty()
869            && should_skip_serializing_optional_vec(&entities.predicted)
870    } else {
871        true
872    }
873}
874
875#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
876#[serde(untagged)]
877pub enum NewEntity {
878    WithSpan(NewEntityWithSpan),
879    WithSpans(NewEntityWithSpans),
880}
881
882#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
883pub struct NewEntityWithSpan {
884    pub name: EntityName,
885    pub formatted_value: String,
886    pub span: NewEntitySpan,
887}
888
889#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
890pub struct FieldId(pub String);
891
892#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
893pub struct NewEntityWithSpans {
894    pub name: EntityName,
895    pub formatted_value: String,
896    pub spans: Vec<NewEntitySpan>,
897}
898
899#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Eq)]
900pub struct NewEntitySpan {
901    content_part: String,
902    message_index: usize,
903    utf16_byte_start: usize,
904    utf16_byte_end: usize,
905}
906
907#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
908pub struct Entity {
909    pub name: EntityName,
910    pub formatted_value: String,
911    pub spans: Vec<EntitySpan>,
912    #[serde(skip_serializing_if = "Option::is_none")]
913    pub field_id: Option<FieldId>,
914}
915
916#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
917pub struct MoonFormFieldAnnotation {
918    pub name: String,
919    pub spans: Vec<EntitySpan>,
920    pub kind: EntityName,
921
922    pub formatted_value: String,
923
924    #[serde(skip_serializing_if = "Option::is_none")]
925    pub field_id: Option<FieldId>,
926
927    #[serde(default)]
928    pub document_spans: Vec<DocumentSpan>,
929}
930
931#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
932pub struct EntitySpanUtf16 {
933    content_part: String,
934    message_index: u32,
935    utf16_byte_start: u32,
936    utf16_byte_end: u32,
937}
938
939#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
940pub struct MoonFormFieldAnnotationNew {
941    pub name: String,
942    pub spans: Vec<EntitySpanUtf16>,
943    pub formatted_value: String,
944    #[serde(default)]
945    pub document_spans: Vec<DocumentSpan>,
946}
947
948#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
949pub struct DocumentSpan {
950    page_index: u32,
951    attachment_index: u32,
952    polygon: PagePolygon,
953}
954
955#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
956pub struct PagePolygon {
957    vertices: Vec<Vertex>,
958}
959
960#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
961pub struct Vertex {
962    x: f64,
963    y: f64,
964}
965
966#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Eq)]
967pub struct EntitySpan {
968    content_part: String,
969    message_index: usize,
970    char_start: usize,
971    char_end: usize,
972    utf16_byte_start: usize,
973    utf16_byte_end: usize,
974}
975
976#[inline]
977fn strip_prefix(string: &mut String, prefix: &str) -> bool {
978    if string.starts_with(prefix) {
979        string.drain(..prefix.len());
980        true
981    } else {
982        false
983    }
984}
985
986#[cfg(test)]
987mod tests {
988    use super::*;
989    use serde_json::{self, json, Value as JsonValue};
990    use std::collections::HashMap;
991
992    #[test]
993    fn property_map_empty_serialize() {
994        assert_eq!(serde_json::to_string(&PropertyMap::new()).expect(""), "{}");
995    }
996
997    #[test]
998    fn property_map_one_number_serialize() {
999        let mut map = PropertyMap::new();
1000        map.insert_number("nps".to_owned(), NotNan::new(7.0).unwrap());
1001        assert_eq!(
1002            serde_json::to_string(&map).expect(""),
1003            r#"{"number:nps":7.0}"#
1004        );
1005    }
1006
1007    #[test]
1008    fn property_map_one_string_serialize() {
1009        let mut map = PropertyMap::new();
1010        map.insert_string("age".to_owned(), "18-25".to_owned());
1011        assert_eq!(
1012            serde_json::to_string(&map).expect(""),
1013            r#"{"string:age":"18-25"}"#
1014        );
1015    }
1016
1017    #[test]
1018    fn property_map_mixed_serialize() {
1019        let mut map = PropertyMap::new();
1020        map.insert_string("age".to_owned(), "18-25".to_owned());
1021        map.insert_string("income".to_owned(), "$6000".to_owned());
1022        map.insert_number("nps".to_owned(), NotNan::new(3.0).unwrap());
1023
1024        let actual_map: HashMap<String, JsonValue> =
1025            serde_json::from_str(&serde_json::to_string(&map).expect("ser")).expect("de");
1026
1027        let mut expected_map = HashMap::new();
1028        expected_map.insert(
1029            "string:age".to_owned(),
1030            JsonValue::String("18-25".to_owned()),
1031        );
1032        expected_map.insert(
1033            "string:income".to_owned(),
1034            JsonValue::String("$6000".to_owned()),
1035        );
1036        expected_map.insert("number:nps".to_owned(), json!(3.0));
1037
1038        assert_eq!(actual_map, expected_map);
1039    }
1040
1041    #[test]
1042    fn property_map_empty_deserialize() {
1043        let map: PropertyMap = serde_json::from_str("{}").expect("");
1044        assert_eq!(map, PropertyMap::new());
1045    }
1046
1047    #[test]
1048    fn property_map_null_deserialize() {
1049        let map: PropertyMap = serde_json::from_str("null").expect("");
1050        assert_eq!(map, PropertyMap::new());
1051    }
1052
1053    #[test]
1054    fn property_map_one_number_float_deserialize() {
1055        let actual: PropertyMap = serde_json::from_str(r#"{"number:nps":7.0}"#).expect("");
1056        let mut expected = PropertyMap::new();
1057        expected.insert_number("nps".to_owned(), NotNan::new(7.0).unwrap());
1058        assert_eq!(actual, expected);
1059    }
1060
1061    #[test]
1062    fn property_map_one_number_unsigned_deserialize() {
1063        let actual: PropertyMap = serde_json::from_str(r#"{"number:nps":7}"#).expect("");
1064        let mut expected = PropertyMap::new();
1065        expected.insert_number("nps".to_owned(), NotNan::new(7.0).unwrap());
1066        assert_eq!(actual, expected);
1067    }
1068
1069    #[test]
1070    fn property_map_one_number_negative_deserialize() {
1071        let actual: PropertyMap = serde_json::from_str(r#"{"number:nps":-7}"#).expect("");
1072        let mut expected = PropertyMap::new();
1073        expected.insert_number("nps".to_owned(), NotNan::new(-7.0).unwrap());
1074        assert_eq!(actual, expected);
1075    }
1076
1077    #[test]
1078    fn property_map_one_string_deserialize() {
1079        let actual: PropertyMap = serde_json::from_str(r#"{"string:age":"18-25"}"#).expect("");
1080        let mut expected = PropertyMap::new();
1081        expected.insert_string("age".to_owned(), "18-25".to_owned());
1082        assert_eq!(actual, expected);
1083    }
1084
1085    #[test]
1086    fn property_map_illegal_prefix_deserialize() {
1087        let result: StdResult<PropertyMap, _> =
1088            serde_json::from_str(r#"{"illegal:something":"18-25"}"#);
1089        assert!(result.is_err());
1090    }
1091
1092    #[test]
1093    fn property_map_illegal_value_for_prefix_deserialize() {
1094        let result: StdResult<PropertyMap, _> =
1095            serde_json::from_str(r#"{"string:something":18.0}"#);
1096        assert!(result.is_err());
1097
1098        let result: StdResult<PropertyMap, _> = serde_json::from_str(r#"{"number:something":"x"}"#);
1099        assert!(result.is_err());
1100    }
1101
1102    #[test]
1103    fn property_map_mixed_deserialize() {
1104        let mut expected = PropertyMap::new();
1105        expected.insert_string("age".to_owned(), "18-25".to_owned());
1106        expected.insert_string("income".to_owned(), "$6000".to_owned());
1107        expected.insert_number("nps".to_owned(), NotNan::new(3.0).unwrap());
1108
1109        let actual: PropertyMap = serde_json::from_str(
1110            r#"{"string:age":"18-25","number:nps":3,"string:income":"$6000"}"#,
1111        )
1112        .expect("");
1113
1114        assert_eq!(actual, expected);
1115    }
1116}