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
//! Types for the configuration and implementation of database backends.

use {
    crate::{
        models::{
            Chapter, Character, Comment, Origin, Pairing, Part, Post, Series, Story, Tag, User,
            Warning,
        },
        uri::Uri,
    },
    std::{collections::HashMap, error::Error, ops::Deref},
};

/// A wrapper type to indicate that a type has no backend id.
#[rustfmt::skip]
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[derive(serde::Deserialize, serde::Serialize)]
pub struct New<T> {
    inner: T,
}

impl<T> Deref for New<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

/// A wrapper type to indicate that a type has a backend id.
#[rustfmt::skip]
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[derive(serde::Deserialize, serde::Serialize)]
pub struct Existing<T> {
    pub id: Id,
    inner: T,
}

impl<T> Deref for Existing<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

/// A backend 'factory' that acts as a constructor for backends.
#[cfg(feature = "with-backend")]
#[async_trait::async_trait]
pub trait BackendFactory {
    type Error: Error;
    type Backend: Backend<Self::Error>;

    /// Essentially a `new` function.
    async fn create(&self, config: Uri) -> Result<Self::Backend, Self::Error>;
}

/// A supported database backend that depends on a series of entries.
///
/// Requires that the backend also implements [`BackendEntry`] for these types
/// (sharing the same error type):
///
///   - Core Types
///     - [`Comment`]
///     - [`Part`]
///     - [`User`]
///   - Blog Types
///     - [`Post`]
///   - Story Types
///     - [`Chapter`]
///     - [`Character`]
///     - [`Origin`]
///     - [`Pairing`]
///     - [`Series`]
///     - [`Story`]
///     - [`Tag`]
///     - [`Warning`]
#[cfg(feature = "with-backend")]
#[rustfmt::skip]
#[async_trait::async_trait]
pub trait Backend<Err>:
    BackendEntry<Chapter, Err>
    + BackendEntry<Character, Err>
    + BackendEntry<Comment, Err>
    + BackendEntry<Origin, Err>
    + BackendEntry<Pairing, Err>
    + BackendEntry<Part, Err>
    + BackendEntry<Post, Err>
    + BackendEntry<Series, Err>
    + BackendEntry<Story, Err>
    + BackendEntry<Tag, Err>
    + BackendEntry<User, Err>
    + BackendEntry<Warning, Err>
where
    Err: Error,
{
    /// Run any missing migration on the database backend.
    async fn migrate(&self) -> Result<(), Err>;
}

/// A database entry, or something that can be stored and retrieved from a database.
#[cfg(feature = "with-backend")]
#[async_trait::async_trait]
pub trait BackendEntry<Entry, Error> {
    /// Get an entity of type with a specific id.
    async fn get(&self, id: Id) -> Result<Existing<Entry>, Error>;
    /// Get all entities of type using offset cursor
    async fn all(&self, cursor: Id, limit: usize) -> Result<Vec<Existing<Entry>>, Error>;

    /// Create a new entity of type.
    async fn create(&self, data: New<Entry>) -> Result<Id, Error>;
    /// Update an entity of type's data.
    async fn update(&self, data: Existing<Entry>) -> Result<(), Error>;
    /// Remove a entity of type.
    async fn remove(&self, id: Id) -> Result<(), Error>;
}

crate::newtype! {
    /// The database entry id newtype, is a `String` by default
    #[derive(serde::Deserialize, serde::Serialize)]
    Id: String
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub enum StorageType {
    File {
        location: String,
    },
    Parts {
        username: Option<String>,
        password: Option<String>,
        host: String,
        port: Option<String>,
        database: Option<String>,
        params: Option<HashMap<String, String>>,
    },
}

impl StorageType {
    pub fn is_file(&self) -> bool {
        matches!(self, StorageType::File { .. })
    }

    pub fn is_parts(&self) -> bool {
        matches!(self, StorageType::Parts { .. })
    }
}

#[derive(Clone, Copy, Debug, serde::Deserialize)]
pub enum BackendType {
    Postgres,
    Sqlite,
}