bonsaidb_core/
lib.rs

1//! Core functionality and types for BonsaiDb.
2
3#![forbid(unsafe_code)]
4#![warn(
5    clippy::cargo,
6    missing_docs,
7    // clippy::missing_docs_in_private_items,
8    clippy::pedantic,
9    future_incompatible,
10    rust_2018_idioms,
11)]
12#![allow(
13    clippy::missing_errors_doc, // TODO clippy::missing_errors_doc
14    clippy::option_if_let_else,
15    clippy::module_name_repetitions,
16    clippy::use_self, // false positives that can't be allowed on the type declaration itself.
17)]
18
19/// Types for creating and validating permissions.
20pub mod permissions;
21
22/// Database administration types and functionality.
23pub mod admin;
24/// Types for interacting with BonsaiDb.
25pub mod connection;
26pub mod document;
27pub mod limits;
28/// Types for defining database schema.
29pub mod schema;
30/// Types for executing transactions.
31pub mod transaction;
32
33/// Types for utilizing a lightweight atomic Key-Value store.
34pub mod keyvalue;
35
36/// Traits for tailoring a server.
37pub mod api;
38
39/// Key trait and related types.
40pub mod key;
41
42/// Types for implementing the BonsaiDb network protocol.
43pub mod networking;
44
45/// Types for Publish/Subscribe (`PubSub`) messaging.
46pub mod pubsub;
47
48use std::fmt::Display;
49use std::string::FromUtf8Error;
50
51use schema::{view, CollectionName, SchemaName, ViewName};
52use serde::{Deserialize, Serialize};
53pub use {
54    actionable, arc_bytes, async_trait, circulate, num_traits, ordered_varint, transmog,
55    transmog_pot,
56};
57
58use crate::api::ApiName;
59use crate::connection::HasSchema;
60use crate::document::{DocumentId, Header, InvalidHexadecimal};
61use crate::key::time::TimeError;
62use crate::key::NextValueError;
63use crate::schema::InsertError;
64
65/// an enumeration of errors that this crate can produce
66#[derive(Clone, thiserror::Error, Debug, Serialize, Deserialize)]
67pub enum Error {
68    /// The database named `database_name` was created with a different schema
69    /// (`stored_schema`) than provided (`schema`).
70    #[error(
71        "database '{database_name}' was created with schema '{stored_schema}', not '{schema}'"
72    )]
73    SchemaMismatch {
74        /// The name of the database being accessed.
75        database_name: String,
76
77        /// The schema provided for the database.
78        schema: SchemaName,
79
80        /// The schema stored for the database.
81        stored_schema: SchemaName,
82    },
83
84    /// The [`SchemaName`] returned has already been registered.
85    #[error("schema '{0}' was already registered")]
86    SchemaAlreadyRegistered(SchemaName),
87
88    /// The [`SchemaName`] requested was not registered.
89    #[error("schema '{0}' is not registered")]
90    SchemaNotRegistered(SchemaName),
91
92    /// The [`ViewName`] returned has already been registered.
93    #[error("view '{0}' was already registered")]
94    ViewAlreadyRegistered(ViewName),
95
96    /// An invalid database name was specified. See
97    /// [`StorageConnection::create_database()`](connection::StorageConnection::create_database)
98    /// for database name requirements.
99    #[error("invalid database name: {0}")]
100    InvalidDatabaseName(String),
101
102    /// The database name given was not found.
103    #[error("database '{0}' was not found")]
104    DatabaseNotFound(String),
105
106    /// The view was not found.
107    #[error("view was not found")]
108    ViewNotFound,
109
110    /// The collection was not found.
111    #[error("collection was not found")]
112    CollectionNotFound,
113
114    /// The api invoked was not found.
115    #[error("api '{0}' was not found")]
116    ApiNotFound(ApiName),
117
118    /// The database name already exists.
119    #[error("a database with name '{0}' already exists")]
120    DatabaseNameAlreadyTaken(String),
121
122    /// An error occurred from networking.
123    #[error("a networking error occurred: '{0}'")]
124    Networking(networking::Error),
125
126    /// A `Collection` being added already exists. This can be caused by a collection name not being unique.
127    #[error("attempted to define a collection that already has been defined")]
128    CollectionAlreadyDefined,
129
130    /// An attempt to update a document that doesn't exist.
131    #[error("the requested document id {1} from collection {0} was not found")]
132    DocumentNotFound(CollectionName, Box<DocumentId>),
133
134    /// A value provided as a [`DocumentId`] exceeded [`DocumentId::MAX_LENGTH`].
135    #[error(
136        "an value was provided for a `DocumentId` that was larger than `DocumentId::MAX_LENGTH`"
137    )]
138    DocumentIdTooLong,
139
140    /// When updating a document, if a situation is detected where the contents
141    /// have changed on the server since the `Revision` provided, a Conflict
142    /// error will be returned.
143    #[error("a conflict was detected while updating document {1} from collection {0}")]
144    DocumentConflict(CollectionName, Box<Header>),
145
146    /// When saving a document in a collection with unique views, a document
147    /// emits a key that is already emitted by an existing ocument, this error
148    /// is returned.
149    #[error("a unique key violation occurred: document `{existing_document}` already has the same key as `{conflicting_document}` for {view}")]
150    UniqueKeyViolation {
151        /// The name of the view that the unique key violation occurred.
152        view: ViewName,
153        /// The document that caused the violation.
154        conflicting_document: Box<Header>,
155        /// The document that already uses the same key.
156        existing_document: Box<Header>,
157    },
158
159    /// When pushing a document, an error occurred while generating the next unique id.
160    #[error("an error occurred generating a new unique id for {0}: {1}")]
161    DocumentPush(CollectionName, NextValueError),
162
163    /// An invalid name was specified during schema creation.
164    #[error("an invalid name was used in a schema: {0}")]
165    InvalidName(#[from] schema::InvalidNameError),
166
167    /// Permission was denied.
168    #[error("permission error: {0}")]
169    PermissionDenied(#[from] actionable::PermissionDenied),
170
171    /// An internal error handling passwords was encountered.
172    #[error("error with password: {0}")]
173    Password(String),
174
175    /// The user specified was not found. This will not be returned in response
176    /// to an invalid username being used during login. It will be returned in
177    /// other APIs that operate upon users.
178    #[error("user not found")]
179    UserNotFound,
180
181    /// An error occurred converting from bytes to Utf-8.
182    #[error("invalid string: {0}")]
183    InvalidUnicode(String),
184
185    /// The credentials specified are not valid.
186    #[error("invalid credentials")]
187    InvalidCredentials,
188
189    /// Returned when the a view's reduce() function is unimplemented.
190    #[error("reduce is unimplemented")]
191    ReduceUnimplemented,
192
193    /// A floating point operation yielded Not a Number.
194    #[error("floating point operation yielded NaN")]
195    NotANumber,
196
197    /// An error while operating with a time
198    #[error("time error: {0}")]
199    Time(#[from] TimeError),
200
201    /// An error from another crate.
202    #[error("error from {origin}: {error}")]
203    Other {
204        /// The origin of the error.
205        origin: String,
206        /// The error message.
207        error: String,
208    },
209}
210
211impl Error {
212    /// Returns an instance of [`Self::Other`] with the given parameters.
213    pub fn other(origin: impl Display, error: impl Display) -> Self {
214        Self::Other {
215            origin: origin.to_string(),
216            error: error.to_string(),
217        }
218    }
219
220    /// Returns true if this error is a [`Error::UniqueKeyViolation`] from
221    /// `View`.
222    pub fn is_unique_key_error<View: schema::View, C: HasSchema>(&self, connection: &C) -> bool {
223        if let Self::UniqueKeyViolation { view, .. } = self {
224            if let Ok(schema_view) = connection.schematic().view::<View>() {
225                return view == &schema_view.view_name();
226            }
227        }
228
229        false
230    }
231
232    /// Returns the header of the conflicting document if this error is a
233    /// [`Error::DocumentConflict`] from `Collection`.
234    #[must_use]
235    pub fn conflicting_document<Collection: schema::Collection>(&self) -> Option<Header> {
236        match self {
237            Self::DocumentConflict(collection, header)
238                if collection == &Collection::collection_name() =>
239            {
240                Some(header.as_ref().clone())
241            }
242            _ => None,
243        }
244    }
245}
246
247impl From<pot::Error> for Error {
248    fn from(err: pot::Error) -> Self {
249        Self::other("pot", err)
250    }
251}
252
253impl<T> From<InsertError<T>> for Error {
254    fn from(err: InsertError<T>) -> Self {
255        err.error
256    }
257}
258
259impl From<view::Error> for Error {
260    fn from(err: view::Error) -> Self {
261        Self::other("view", err)
262    }
263}
264
265impl From<FromUtf8Error> for Error {
266    fn from(err: FromUtf8Error) -> Self {
267        Self::InvalidUnicode(err.to_string())
268    }
269}
270
271impl From<InvalidHexadecimal> for Error {
272    fn from(err: InvalidHexadecimal) -> Self {
273        Self::other("invalid hexadecimal", err)
274    }
275}
276
277/// Shared schemas and utilities used for unit testing.
278#[cfg(any(feature = "test-util", test))]
279#[allow(missing_docs)]
280pub mod test_util;
281
282/// When true, encryption was enabled at build-time.
283#[cfg(feature = "encryption")]
284pub const ENCRYPTION_ENABLED: bool = true;
285
286/// When true, encryption was enabled at build-time.
287#[cfg(not(feature = "encryption"))]
288pub const ENCRYPTION_ENABLED: bool = false;
289
290/// A type that implements [`Error`](std::error::Error) and is threadsafe.
291pub trait AnyError: std::error::Error + Send + Sync + 'static {}
292
293impl<T> AnyError for T where T: std::error::Error + Send + Sync + 'static {}