1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use chrono::NaiveDateTime;
use std::ops::Range;

#[cfg(feature = "serde-1")]
use serde::{Deserialize, Serialize};

/// A single chat message.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
pub struct Message {
    /// The message [`Metadata`].
    pub meta: Metadata,
    /// The message's [`Body`].
    pub body: Body,
    /// The location of this [`Message`] in its source text.
    pub span: Span,
}

/// [`Message`] metadata.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
pub struct Metadata {
    /// When the message was received.
    pub timestamp: NaiveDateTime,
    /// Who sent the message?
    pub sender: String,
    /// The location of the [`Metadata`] field in the source text.
    pub span: Span,
}

/// A [`Message`]'s contents.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case", tag = "type"))]
pub enum Body {
    /// A [`DirectMessage`].
    DirectMessage(DirectMessage),
    /// An [`Attachment`].
    Attachment(Attachment),
}

impl From<DirectMessage> for Body {
    fn from(other: DirectMessage) -> Body { Body::DirectMessage(other) }
}

impl From<Attachment> for Body {
    fn from(other: Attachment) -> Body { Body::Attachment(other) }
}

/// A message with text.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
pub struct DirectMessage {
    /// The raw body of the message.
    pub content: String,
    /// Where the [`DirectMessage`] occurred in its source text.
    pub span: Span,
}

/// A link to an attachment.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
pub struct Attachment {
    /// The name of the attached file.
    pub name: String,
    /// Where the [`Attachment`] occurred in its source text.
    pub span: Span,
}

/// The half-open interval representing the location of something in some text.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
pub struct Span {
    /// The index of the first character.
    pub start: usize,
    /// The index one after the last character.
    pub end: usize,
}

impl Span {
    /// Create a new [`Span`].
    pub const fn new(start: usize, end: usize) -> Self { Span { start, end } }

    /// Get the [`Span`] as a [`Range`].
    pub const fn as_range(self) -> Range<usize> { self.start..self.end }

    /// Look up the text this [`Span`] corresponds to.
    ///
    /// This can fail if the [`Span`] didn't come from the same string, or if
    /// [`Span::start`] or [`Span::end`] don't lie on a UTF-8 code boundary.
    pub fn get(self, text: &str) -> Option<&str> { text.get(self.as_range()) }
}