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
use crate::{
    ent::{Ent, EntMutationError, Query, ValueType},
    AsAny, Id,
};
use derive_more::Display;
use std::sync::{Arc, Weak};

/// Represents a thread-safe reference to a boxed database trait object
pub type DatabaseRc = Arc<Box<dyn Database>>;

/// Converts a typed database into a `DatabaseRc`
#[inline]
pub fn db_to_rc<D: Database>(db: D) -> DatabaseRc {
    Arc::new(Box::new(db))
}

/// Represents a weak thread-safe reference to a boxed database trait object
pub type WeakDatabaseRc = Weak<Box<dyn Database>>;

/// Alias to a result that can contain a database error
pub type DatabaseResult<T> = Result<T, DatabaseError>;

/// Represents some error the can occur when accessing the database
#[derive(Debug, Display)]
pub enum DatabaseError {
    #[display(fmt = "Connection Issue: {}", source)]
    Connection { source: Box<dyn std::error::Error> },

    #[display(fmt = "Disconnected")]
    Disconnected,

    #[display(fmt = "Missing Field: {}", name)]
    MissingField { name: String },

    #[display(fmt = "Missing Edge: {}", name)]
    MissingEdge { name: String },

    #[display(fmt = "Missing Ent: {}", id)]
    MissingEnt { id: Id },

    #[display(fmt = "Mutation Failed (Ent = {}): {}", id, source)]
    EntMutationFailed { id: Id, source: EntMutationError },

    #[display(fmt = "Expected type {}, but got type {}", expected, actual)]
    WrongType {
        expected: ValueType,
        actual: ValueType,
    },

    #[display(fmt = "Corrupted Ent {}: {}", id, source)]
    CorruptedEnt {
        id: Id,
        source: Box<dyn std::error::Error>,
    },

    #[display(fmt = "Broken Edge {}", name)]
    BrokenEdge { name: String },

    #[display(fmt = "Ent Capacity Reached")]
    EntCapacityReached,

    #[display(fmt = "{}", source)]
    Other { source: Box<dyn std::error::Error> },
}

impl std::error::Error for DatabaseError {}

/// Represents a synchronous database, which performs blocking CRUD
/// operations using ents. Given that many database implementations handle
/// interior mutability themselves, the API of this trait does not provide
/// any mut guarantees itself.
pub trait Database: AsAny + Send + Sync {
    /// Retrieves a copy of a single, generic ent with the corresponding id
    ///
    /// This should not connect the ent back to the database upon return as
    /// that decision should be made outside of the database itself.
    fn get(&self, id: Id) -> DatabaseResult<Option<Box<dyn Ent>>>;

    /// Removes the ent with the corresponding id, triggering edge
    /// processing for all disconnected ents. Returns a boolean indicating
    /// if an ent was removed.
    fn remove(&self, id: Id) -> DatabaseResult<bool>;

    /// Inserts a new ent using its id as the primary index, overwriting
    /// any ent with a matching id. If the ent's id is set to the ephemeral
    /// id (of 0), a unique id will be assigned to the ent prior to being
    /// inserted.
    ///
    /// The ent's id is returned after being inserted.
    fn insert(&self, ent: Box<dyn Ent>) -> DatabaseResult<Id>;

    /// Performs a retrieval of multiple ents of any type
    fn get_all(&self, ids: Vec<Id>) -> DatabaseResult<Vec<Box<dyn Ent>>>;

    /// Finds all generic ents that match the query
    fn find_all(&self, query: Query) -> DatabaseResult<Vec<Box<dyn Ent>>>;
}

/// Implementation for a generic trait object of [`Database`] that provides
/// methods to downcast into a concrete type
impl dyn Database {
    /// Attempts to convert this dynamic Database ref into a concrete Database
    /// ref by downcasting
    pub fn as_database<D: Database>(&self) -> Option<&D> {
        self.as_any().downcast_ref::<D>()
    }

    /// Attempts to convert this dynamic Database mutable ref into a concrete
    /// Database mutable ref by downcasting
    pub fn as_mut_database<D: Database>(&mut self) -> Option<&mut D> {
        self.as_mut_any().downcast_mut::<D>()
    }
}

pub trait DatabaseExt: Database {
    /// Inserts an ent of a specific type
    fn insert_typed<E: Ent>(&self, ent: E) -> DatabaseResult<Id>;

    /// Retrieves an ent by id with a specific type
    fn get_typed<E: Ent>(&self, id: Id) -> DatabaseResult<Option<E>>;

    /// Retrieves ents by id with a specific type
    fn get_all_typed<E: Ent>(&self, ids: Vec<Id>) -> DatabaseResult<Vec<E>>;

    /// Finds ents that match the specified query and are of the specified type
    fn find_all_typed<E: Ent>(&self, query: Query) -> DatabaseResult<Vec<E>>;
}

impl<T: Database> DatabaseExt for T {
    fn insert_typed<E: Ent>(&self, ent: E) -> DatabaseResult<Id> {
        self.insert(Box::from(ent))
    }

    fn get_typed<E: Ent>(&self, id: Id) -> DatabaseResult<Option<E>> {
        self.get(id).map(|x| x.and_then(|ent| ent.to_ent::<E>()))
    }

    fn get_all_typed<E: Ent>(&self, ids: Vec<Id>) -> DatabaseResult<Vec<E>> {
        self.get_all(ids)
            .map(|x| x.into_iter().filter_map(|ent| ent.to_ent::<E>()).collect())
    }

    fn find_all_typed<E: Ent>(&self, query: Query) -> DatabaseResult<Vec<E>> {
        self.find_all(query)
            .map(|x| x.into_iter().filter_map(|ent| ent.to_ent::<E>()).collect())
    }
}

impl DatabaseExt for dyn Database {
    fn insert_typed<E: Ent>(&self, ent: E) -> DatabaseResult<Id> {
        self.insert(Box::from(ent))
    }

    fn get_typed<E: Ent>(&self, id: Id) -> DatabaseResult<Option<E>> {
        self.get(id).map(|x| x.and_then(|ent| ent.to_ent::<E>()))
    }

    fn get_all_typed<E: Ent>(&self, ids: Vec<Id>) -> DatabaseResult<Vec<E>> {
        self.get_all(ids)
            .map(|x| x.into_iter().filter_map(|ent| ent.to_ent::<E>()).collect())
    }

    fn find_all_typed<E: Ent>(&self, query: Query) -> DatabaseResult<Vec<E>> {
        self.find_all(query)
            .map(|x| x.into_iter().filter_map(|ent| ent.to_ent::<E>()).collect())
    }
}