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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
//! Core functionality and types for BonsaiDb.

#![forbid(unsafe_code)]
#![warn(
    clippy::cargo,
    missing_docs,
    // clippy::missing_docs_in_private_items,
    clippy::pedantic,
    future_incompatible,
    rust_2018_idioms,
)]
#![allow(
    clippy::missing_errors_doc, // TODO clippy::missing_errors_doc
    clippy::option_if_let_else,
    clippy::module_name_repetitions,
    clippy::use_self, // false positives that can't be allowed on the type declaration itself.
)]

/// Types for creating and validating permissions.
pub mod permissions;

/// Database administration types and functionality.
pub mod admin;
/// Types for interacting with BonsaiDb.
pub mod connection;
pub mod document;
pub mod limits;
/// Types for defining database schema.
pub mod schema;
/// Types for executing transactions.
pub mod transaction;

/// Types for utilizing a lightweight atomic Key-Value store.
pub mod keyvalue;

/// Traits for tailoring a server.
pub mod api;

/// Key trait and related types.
pub mod key;

/// Types for implementing the BonsaiDb network protocol.
pub mod networking;

/// Types for Publish/Subscribe (`PubSub`) messaging.
pub mod pubsub;

use std::fmt::Display;
use std::string::FromUtf8Error;

use schema::{view, CollectionName, SchemaName, ViewName};
use serde::{Deserialize, Serialize};
pub use {
    actionable, arc_bytes, async_trait, circulate, num_traits, ordered_varint, transmog,
    transmog_pot,
};

use crate::api::ApiName;
use crate::connection::HasSchema;
use crate::document::{DocumentId, Header, InvalidHexadecimal};
use crate::key::time::TimeError;
use crate::key::NextValueError;
use crate::schema::InsertError;

/// an enumeration of errors that this crate can produce
#[derive(Clone, thiserror::Error, Debug, Serialize, Deserialize)]
pub enum Error {
    /// The database named `database_name` was created with a different schema
    /// (`stored_schema`) than provided (`schema`).
    #[error(
        "database '{database_name}' was created with schema '{stored_schema}', not '{schema}'"
    )]
    SchemaMismatch {
        /// The name of the database being accessed.
        database_name: String,

        /// The schema provided for the database.
        schema: SchemaName,

        /// The schema stored for the database.
        stored_schema: SchemaName,
    },

    /// The [`SchemaName`] returned has already been registered.
    #[error("schema '{0}' was already registered")]
    SchemaAlreadyRegistered(SchemaName),

    /// The [`SchemaName`] requested was not registered.
    #[error("schema '{0}' is not registered")]
    SchemaNotRegistered(SchemaName),

    /// The [`ViewName`] returned has already been registered.
    #[error("view '{0}' was already registered")]
    ViewAlreadyRegistered(ViewName),

    /// An invalid database name was specified. See
    /// [`StorageConnection::create_database()`](connection::StorageConnection::create_database)
    /// for database name requirements.
    #[error("invalid database name: {0}")]
    InvalidDatabaseName(String),

    /// The database name given was not found.
    #[error("database '{0}' was not found")]
    DatabaseNotFound(String),

    /// The view was not found.
    #[error("view was not found")]
    ViewNotFound,

    /// The collection was not found.
    #[error("collection was not found")]
    CollectionNotFound,

    /// The api invoked was not found.
    #[error("api '{0}' was not found")]
    ApiNotFound(ApiName),

    /// The database name already exists.
    #[error("a database with name '{0}' already exists")]
    DatabaseNameAlreadyTaken(String),

    /// An error occurred from networking.
    #[error("a networking error occurred: '{0}'")]
    Networking(networking::Error),

    /// A `Collection` being added already exists. This can be caused by a collection name not being unique.
    #[error("attempted to define a collection that already has been defined")]
    CollectionAlreadyDefined,

    /// An attempt to update a document that doesn't exist.
    #[error("the requested document id {1} from collection {0} was not found")]
    DocumentNotFound(CollectionName, Box<DocumentId>),

    /// A value provided as a [`DocumentId`] exceeded [`DocumentId::MAX_LENGTH`].
    #[error(
        "an value was provided for a `DocumentId` that was larger than `DocumentId::MAX_LENGTH`"
    )]
    DocumentIdTooLong,

    /// When updating a document, if a situation is detected where the contents
    /// have changed on the server since the `Revision` provided, a Conflict
    /// error will be returned.
    #[error("a conflict was detected while updating document {1} from collection {0}")]
    DocumentConflict(CollectionName, Box<Header>),

    /// When saving a document in a collection with unique views, a document
    /// emits a key that is already emitted by an existing ocument, this error
    /// is returned.
    #[error("a unique key violation occurred: document `{existing_document}` already has the same key as `{conflicting_document}` for {view}")]
    UniqueKeyViolation {
        /// The name of the view that the unique key violation occurred.
        view: ViewName,
        /// The document that caused the violation.
        conflicting_document: Box<Header>,
        /// The document that already uses the same key.
        existing_document: Box<Header>,
    },

    /// When pushing a document, an error occurred while generating the next unique id.
    #[error("an error occurred generating a new unique id for {0}: {1}")]
    DocumentPush(CollectionName, NextValueError),

    /// An invalid name was specified during schema creation.
    #[error("an invalid name was used in a schema: {0}")]
    InvalidName(#[from] schema::InvalidNameError),

    /// Permission was denied.
    #[error("permission error: {0}")]
    PermissionDenied(#[from] actionable::PermissionDenied),

    /// An internal error handling passwords was encountered.
    #[error("error with password: {0}")]
    Password(String),

    /// The user specified was not found. This will not be returned in response
    /// to an invalid username being used during login. It will be returned in
    /// other APIs that operate upon users.
    #[error("user not found")]
    UserNotFound,

    /// An error occurred converting from bytes to Utf-8.
    #[error("invalid string: {0}")]
    InvalidUnicode(String),

    /// The credentials specified are not valid.
    #[error("invalid credentials")]
    InvalidCredentials,

    /// Returned when the a view's reduce() function is unimplemented.
    #[error("reduce is unimplemented")]
    ReduceUnimplemented,

    /// A floating point operation yielded Not a Number.
    #[error("floating point operation yielded NaN")]
    NotANumber,

    /// An error while operating with a time
    #[error("time error: {0}")]
    Time(#[from] TimeError),

    /// An error from another crate.
    #[error("error from {origin}: {error}")]
    Other {
        /// The origin of the error.
        origin: String,
        /// The error message.
        error: String,
    },
}

impl Error {
    /// Returns an instance of [`Self::Other`] with the given parameters.
    pub fn other(origin: impl Display, error: impl Display) -> Self {
        Self::Other {
            origin: origin.to_string(),
            error: error.to_string(),
        }
    }

    /// Returns true if this error is a [`Error::UniqueKeyViolation`] from
    /// `View`.
    pub fn is_unique_key_error<View: schema::View, C: HasSchema>(&self, connection: &C) -> bool {
        if let Self::UniqueKeyViolation { view, .. } = self {
            if let Ok(schema_view) = connection.schematic().view::<View>() {
                return view == &schema_view.view_name();
            }
        }

        false
    }

    /// Returns the header of the conflicting document if this error is a
    /// [`Error::DocumentConflict`] from `Collection`.
    #[must_use]
    pub fn conflicting_document<Collection: schema::Collection>(&self) -> Option<Header> {
        match self {
            Self::DocumentConflict(collection, header)
                if collection == &Collection::collection_name() =>
            {
                Some(header.as_ref().clone())
            }
            _ => None,
        }
    }
}

impl From<pot::Error> for Error {
    fn from(err: pot::Error) -> Self {
        Self::other("pot", err)
    }
}

impl<T> From<InsertError<T>> for Error {
    fn from(err: InsertError<T>) -> Self {
        err.error
    }
}

impl From<view::Error> for Error {
    fn from(err: view::Error) -> Self {
        Self::other("view", err)
    }
}

impl From<FromUtf8Error> for Error {
    fn from(err: FromUtf8Error) -> Self {
        Self::InvalidUnicode(err.to_string())
    }
}

impl From<InvalidHexadecimal> for Error {
    fn from(err: InvalidHexadecimal) -> Self {
        Self::other("invalid hexadecimal", err)
    }
}

/// Shared schemas and utilities used for unit testing.
#[cfg(any(feature = "test-util", test))]
#[allow(missing_docs)]
pub mod test_util;

/// When true, encryption was enabled at build-time.
#[cfg(feature = "encryption")]
pub const ENCRYPTION_ENABLED: bool = true;

/// When true, encryption was enabled at build-time.
#[cfg(not(feature = "encryption"))]
pub const ENCRYPTION_ENABLED: bool = false;

/// A type that implements [`Error`](std::error::Error) and is threadsafe.
pub trait AnyError: std::error::Error + Send + Sync + 'static {}

impl<T> AnyError for T where T: std::error::Error + Send + Sync + 'static {}