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
98
99
100
use serde::{Deserialize, Serialize};

use chrono::{DateTime, Utc};

use super::common::ID;
use crate::utils::serde::parse_stringint;

/// Type alias for clarity.
pub type Annotations = Vec<Annotation>;

/// Represents an annotation as returned from the API.
///
/// Annotations are in Annotatorjs format. <https://annotatorjs.org/>
/// See <http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html> for documentation on the
/// format.
///
#[derive(Deserialize, Serialize, Debug)]
pub struct Annotation {
    /// The unique integral id of the annotation.
    pub id: ID,

    /// A schema version to presumably support updates in the future. Currently all annotations
    /// appear to be `v1.0`. Hopefully this isn't going to get breaking changes any time soon.
    pub annotator_schema_version: String,

    /// When the annotation was created on the server.
    pub created_at: DateTime<Utc>,

    /// The quoted (or highlighted) text from the entry.
    pub quote: Option<String>,

    /// A list of ranges from the entry that the annotation covers. Most annotations cover a single
    /// range.
    pub ranges: Vec<Range>,

    /// The content of the annotation - any text the user added to annotate the entry.
    pub text: String,

    /// Timestamp of when the annotation was last updated. This is independent of the associated
    /// entry.
    pub updated_at: DateTime<Utc>,

    /// Possibly part of wallabag planning on supporting sharing between users. Currently this
    /// field is always `None`.
    pub user: Option<String>,
}

/// This is implemented so that an Annotation can be used interchangeably with an ID for some
/// client methods. For convenience.
impl From<Annotation> for ID {
    fn from(ann: Annotation) -> Self {
        ann.id
    }
}

/// This is implemented so that an &Annotation can be used interchangeably with an ID
/// for some client methods. For convenience.
impl From<&Annotation> for ID {
    fn from(ann: &Annotation) -> Self {
        ann.id
    }
}

/// Intermediary struct for deserializing a list of annotations.
#[derive(Deserialize, Debug)]
pub(crate) struct AnnotationRows {
    pub rows: Annotations,
}

/// Represents an annotation to be created (hence no ID yet).
/// Fields are defined as in a full annotation.
#[derive(Serialize, Debug)]
pub struct NewAnnotation {
    /// TODO, XXX: quote must not be an empty string.
    pub quote: String,
    pub ranges: Vec<Range>,
    pub text: String,
}

/// Range as used in an `Annotation`. Shows where the annotation is in the
/// content. Part of Annotationjs annotation format. I quote from their docs for the field
/// descriptions.
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Range {
    /// (relative) XPath to start element.
    pub start: Option<String>,

    /// (relative) XPath to end element.
    pub end: Option<String>,

    /// Character offset within start element.  Note: these offset values have been observed as
    /// literal strings and integers. Grrr loosely typed languages with coercion...
    #[serde(deserialize_with = "parse_stringint")]
    pub start_offset: u32,

    /// Character offset within end element.
    #[serde(deserialize_with = "parse_stringint")]
    pub end_offset: u32,
}