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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use crate::{
    database_version::{DatabaseVersion, DatabaseVersionError},
    date_of_creation::{DateOfCreation, DateOfCreationError},
    shared::{text_uint, SharedError},
};
use roxmltree::Node;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use thiserror::Error;

/// Error while parsing the header.
#[derive(Debug, Error, PartialEq, Eq)]
pub enum HeaderError {
    #[error("(Header) Database version: {0}")]
    DatabaseVersion(#[from] DatabaseVersionError),
    #[error("(Header) Date of creation: {0}")]
    DateOfCreation(#[from] DateOfCreationError),
    #[error("(Header) Shared: {0}")]
    Shared(#[from] SharedError),
    #[error("(Header) Incomplete header provided")]
    Incomplete,
}

/// Contains identification information about the version of the file.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Header {
    /// Denotes the version of the database structure.
    pub file_version: u8,
    /// The database version.
    pub database_version: DatabaseVersion,
    /// The date on which the database was created.
    pub date_of_creation: DateOfCreation,
}

struct HeaderBuilder {
    database_version: Option<DatabaseVersion>,
    date_of_creation: Option<DateOfCreation>,
    file_version: Option<u8>,
}

impl HeaderBuilder {
    pub fn new() -> Self {
        Self {
            database_version: None,
            date_of_creation: None,
            file_version: None,
        }
    }

    pub fn build(self) -> Result<Header, HeaderError> {
        let database_version = self.database_version.ok_or(HeaderError::Incomplete)?;
        let date_of_creation = self.date_of_creation.ok_or(HeaderError::Incomplete)?;
        let file_version = self.file_version.ok_or(HeaderError::Incomplete)?;

        Ok(Header {
            database_version,
            date_of_creation,
            file_version,
        })
    }
}

impl<'a, 'input> TryFrom<Node<'a, 'input>> for Header {
    type Error = HeaderError;

    fn try_from(node: Node) -> Result<Self, Self::Error> {
        let mut builder = HeaderBuilder::new();
        for child in node.children() {
            match child.tag_name().name() {
                "database_version" => {
                    builder.database_version = Some(DatabaseVersion::try_from(child)?);
                }
                "date_of_creation" => {
                    builder.date_of_creation = Some(DateOfCreation::try_from(child)?);
                }
                "file_version" => {
                    builder.file_version = Some(text_uint(&child)?);
                }
                _ => {}
            }
        }
        builder.build()
    }
}

#[cfg(test)]
mod tests {
    use super::Header;
    use crate::{
        database_version::DatabaseVersion, date_of_creation::DateOfCreation, test_shared::DOC,
    };
    use std::convert::TryFrom;

    #[test]
    fn parses_header() {
        let node = DOC
            .descendants()
            .find(|node| node.has_tag_name("header"))
            .unwrap();
        let header = Header::try_from(node);
        assert_eq!(
            header,
            Ok(Header {
                date_of_creation: DateOfCreation {
                    year: 2021,
                    month: 6,
                    day: 25,
                },
                database_version: DatabaseVersion {
                    year: 2021,
                    version: 176,
                },
                file_version: 4,
            })
        )
    }
}