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,
}