aboutsummaryrefslogtreecommitdiff
path: root/lockchain-core/src/traits.rs
blob: 4e66d8f18764e28b3b31f5214c3c19c1766accd2 (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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Common vault traits for plugin-crates
//!
//! The core of this crate has no functionality and is dependant
//! on other libraries to fill those holes. To make this easer
//! (and sometimes possible), we defined a few common behaviours
//! in traits to expand on in implementations specific to the
//! library.
//!
//! Each trait is documented in more detail and provides default
//! implementations with `unimplemented!` macros to make
//! compilation work without external crates but not calling
//! functions at runtime.

use meta::{MetaDomain, VaultMetadata};
use record::{EncryptedBody, Header, Payload, Record};
use serde::{de::DeserializeOwned, Serialize};

use base64;
use serde_json::{self, Error as SerdeError};
use std::error::Error;

/// A Body trait that can be implemented to hook into the generic Record
/// data module.
///
/// This allows working with both encrypted and cleartext data bodies.
pub trait Body: DeserializeOwned + Serialize + Send {
    ///Get the value of a field from this body
    fn get_field(&self, key: &str) -> Option<&Payload>;
    /// Set the value of a field
    fn set_field(&mut self, key: &str, value: Payload) -> Option<()>;
    /// Remove versioning and flatten the data tree to a single level.
    fn flatten(&mut self) -> Option<()>;
}

/// A simple trait that allows libraries to hook into the
/// `body()` and `record()` hooks for vault records.
pub trait LoadRecord<T: Body> {
    fn header() -> Header {
        unimplemented!()
    }

    fn body() -> T {
        unimplemented!()
    }
}

/// This is purely a marker trait for encryptable types
///
/// Indicates that a type should be handlable by an encryption
/// engine, also relying on the auto encoder functionality.
///
/// Additional functions might be added to this trait further down
/// the road but for now, it's really just a marker that you can easily
/// implement for any type that's also `AutoEncoder`
///
/// ```rust, norun
/// impl Encryptable for YourSpecialType {}
/// ```
pub trait Encryptable: AutoEncoder {}

/// A base trait that describes the basic functionality of
/// an encryption backend which handles encrypted files.
///
/// Encryption is never done directly on the bodies, only via
/// this scheduler type with the help of the [[Encryptable]] trait.
pub trait EncryptionHandler<T>
where
    T: Encryptable + AutoEncoder + Body,
{
    fn encrypt(&mut self, item: T) -> EncryptedBody;
    fn decrypt(&mut self, item: EncryptedBody) -> Option<T>;
}

/// An abstract file loading utility trait
///
/// Any type that implements `FileIO` also has to be
/// `AutoEncoder` in order to be storable. This trait implements
/// common file I/O operations, assuming that any type using it
/// will then provide the required utility functions.
pub trait FileIO: AutoEncoder {
    /// Load a type from a file path
    fn load(path: &str) -> Result<Self, Box<Error>> {
        use std::fs;
        fs::read_to_string(path)
            .and_then(|s| Self::decode(&s).map_err(|e| e.into()))
            .map_err(|e| e.into())
    }

    /// Store a type to a file path
    fn save(&self, path: &str) -> Result<(), Box<Error>> {
        use std::fs::OpenOptions;
        use std::io::Write;

        let mut file = OpenOptions::new().write(true).create(true).open(path)?;
        let content = self.encode()?;
        file.write_all(content.as_bytes())?;

        Ok(())
    }
}

/// Trait for an in-memory representation of a lockchain vault.
///
/// By itself it represents vault metadata (name, users, location)
/// as well as a list of record headers.
///
/// To provide on-disk functionality it requires the `-storage`
/// trait library and for encrypted file access the `-crypto`
/// crate.
///
/// The body backend is being being generic with the `Body` trait.
pub trait Vault<T>: Send
where
    T: Body,
{
    /// A shared constructor for all vault implementations
    fn new(name: &str, location: &str) -> Self;
    /// Get basic vault metadata
    fn metadata(&self) -> VaultMetadata;
    /// Fetch metadata headers for all records
    fn fetch(&mut self);
    /// Pull a specific record from the backend
    fn pull(&mut self, name: &str);
    /// Sync all changes back to the backend
    fn sync(&mut self);
    /// Get a complete record from this vault
    fn get_record(&self, name: &str) -> Option<&Record<T>>;
    /// Probe if a record is contained
    fn contains(&self, name: &str) -> bool;
    /// Add a new record to this vault
    fn add_record(&mut self, key: &str, category: &str, tags: Vec<&str>);
    /// Delete a record from this vault
    fn delete_record(&mut self, record: &str) -> Option<Record<T>>;
    /// Add data to an existing record, overwriting existing fields
    fn add_data(&mut self, record: &str, key: &str, data: Payload) -> Option<()>;
    /// Get the (latest) value of a specific record data field
    fn get_data(&self, record: &str, key: &str) -> Option<&Payload>;
    /// Adds a domain space to the metadata store inside the vault
    ///
    /// A domain is a collection metadata files that can be
    /// returned with a single pull request
    fn meta_add_domain(&mut self, domain: &str) -> Option<()>;
    /// Returns all records from a meta domain
    fn meta_pull_domain(&mut self, domain: &str) -> Option<&MetaDomain>;
    /// Entirely replace a meta domain in the store
    fn meta_push_domain(&mut self, domain: MetaDomain) -> Option<()>;
    /// Set the value of a field inside a domain. Field names **must not** collide
    fn meta_set(&mut self, domain: &str, name: &str, data: Payload) -> Option<()>;
    /// Get the value of a (unique) field inside a domain
    fn meta_get(&mut self, domain: &str, name: &str) -> Option<Payload>;
}

/// Auto-implement this trait to serialise types to json
pub trait AutoEncoder: Serialize + DeserializeOwned {
    fn encode(&self) -> Result<String, SerdeError> {
        serde_json::to_string_pretty(self)
    }

    fn decode(s: &str) -> Result<Self, SerdeError> {
        serde_json::from_str(s)
    }
}

/// Include this trait to monkey-patch base64 functions onto String types
pub trait Base64AutoEncoder {
    fn to_base64(&self) -> String;
    fn from_base64(base64: &str) -> String;
}

impl Base64AutoEncoder for String {
    /// Automatically encode this string to base64
    fn to_base64(&self) -> String {
        base64::encode(self.as_bytes())
    }

    /// Craft a string from an existing base64 string slice
    fn from_base64(base64: &str) -> String {
        String::from_utf8(base64::decode(base64).unwrap()).unwrap()
    }
}

impl Base64AutoEncoder for Vec<u8> {
    /// Automatically encode this string to base64
    fn to_base64(&self) -> String {
        base64::encode(self)
    }

    /// Craft a string from an existing base64 string slice
    fn from_base64(base64: &str) -> String {
        String::from_utf8(base64::decode(base64).unwrap()).unwrap()
    }
}