summaryrefslogtreecommitdiff
path: root/src/server.rs
blob: 31a03fcaac2c7c3b159aaa7a16e8e0e507ea1036 (plain)
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! Stuff related to the Obnam chunk server.

use crate::chunk::DataChunk;
use crate::chunkid::ChunkId;
use crate::chunkmeta::ChunkMeta;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::default::Default;
use std::path::{Path, PathBuf};

/// Server configuration.
#[derive(Debug, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct ServerConfig {
    /// Path to directory where chunks are stored.
    pub chunks: PathBuf,
    /// Address where server is to listen.
    pub address: String,
    /// Path to TLS key.
    pub tls_key: PathBuf,
    /// Path to TLS certificate.
    pub tls_cert: PathBuf,
}

/// Possible errors wittht server configuration.
#[derive(Debug, thiserror::Error)]
pub enum ServerConfigError {
    /// The chunks directory doesn't exist.
    #[error("Directory for chunks {0} does not exist")]
    ChunksDirNotFound(PathBuf),

    /// The TLS certificate doesn't exist.
    #[error("TLS certificate {0} does not exist")]
    TlsCertNotFound(PathBuf),

    /// The TLS key doesn't exist.
    #[error("TLS key {0} does not exist")]
    TlsKeyNotFound(PathBuf),

    /// Server address is wrong.
    #[error("server address can't be resolved")]
    BadServerAddress,

    /// Failed to read configuration file.
    #[error("failed to read configuration file {0}: {1}")]
    Read(PathBuf, std::io::Error),

    /// Failed to parse configuration file as YAML.
    #[error("failed to parse configuration file as YAML: {0}")]
    YamlParse(serde_yaml::Error),
}

impl ServerConfig {
    /// Read, parse, and check the server configuration file.
    pub fn read_config(filename: &Path) -> Result<Self, ServerConfigError> {
        let config = match std::fs::read_to_string(filename) {
            Ok(config) => config,
            Err(err) => return Err(ServerConfigError::Read(filename.to_path_buf(), err)),
        };
        let config: Self = serde_yaml::from_str(&config).map_err(ServerConfigError::YamlParse)?;
        config.check()?;
        Ok(config)
    }

    /// Check the configuration.
    pub fn check(&self) -> Result<(), ServerConfigError> {
        if !self.chunks.exists() {
            return Err(ServerConfigError::ChunksDirNotFound(self.chunks.clone()));
        }
        if !self.tls_cert.exists() {
            return Err(ServerConfigError::TlsCertNotFound(self.tls_cert.clone()));
        }
        if !self.tls_key.exists() {
            return Err(ServerConfigError::TlsKeyNotFound(self.tls_key.clone()));
        }
        Ok(())
    }
}

/// Result of creating a chunk.
#[derive(Debug, Serialize)]
pub struct Created {
    id: ChunkId,
}

impl Created {
    /// Create a new created chunk id.
    pub fn new(id: ChunkId) -> Self {
        Created { id }
    }

    /// Convert to JSON.
    pub fn to_json(&self) -> String {
        serde_json::to_string(&self).unwrap()
    }
}

/// Result of retrieving a chunk.
#[derive(Debug, Serialize)]
pub struct Fetched {
    id: ChunkId,
    chunk: DataChunk,
}

impl Fetched {
    /// Create a new id for a fetched chunk.
    pub fn new(id: ChunkId, chunk: DataChunk) -> Self {
        Fetched { id, chunk }
    }

    /// Convert to JSON.
    pub fn to_json(&self) -> String {
        serde_json::to_string(&self).unwrap()
    }
}

/// Result of a search.
#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct SearchHits {
    map: HashMap<String, ChunkMeta>,
}

impl SearchHits {
    /// Insert a new chunk id to search results.
    pub fn insert(&mut self, id: ChunkId, meta: ChunkMeta) {
        self.map.insert(id.to_string(), meta);
    }

    /// Convert from JSON.
    pub fn from_json(s: &str) -> Result<Self, serde_json::Error> {
        let map = serde_json::from_str(s)?;
        Ok(SearchHits { map })
    }

    /// Convert to JSON.
    pub fn to_json(&self) -> String {
        serde_json::to_string(&self.map).unwrap()
    }
}

#[cfg(test)]
mod test_search_hits {
    use super::{ChunkMeta, SearchHits};
    use crate::checksummer::Checksum;

    #[test]
    fn no_search_hits() {
        let hits = SearchHits::default();
        assert_eq!(hits.to_json(), "{}");
    }

    #[test]
    fn one_search_hit() {
        let id = "abc".parse().unwrap();
        let sum = Checksum::sha256_from_str_unchecked("123");
        let meta = ChunkMeta::new(&sum);
        let mut hits = SearchHits::default();
        hits.insert(id, meta);
        eprintln!("hits: {:?}", hits);
        let json = hits.to_json();
        let hits2 = SearchHits::from_json(&json).unwrap();
        assert_eq!(hits, hits2);
    }
}