#![deny(clippy::pedantic)] #![deny(rust_2018_idioms)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::doc_markdown)] // `sql_function` fails this check macro_rules! derive_diesel_json { ($typ:ident$(<$lt:lifetime>)?) => { impl<$($lt, )?B: diesel::backend::Backend> diesel::deserialize::FromSql for $typ$(<$lt>)? where Vec: diesel::deserialize::FromSql, { fn from_sql(bytes: Option<&B::RawValue>) -> diesel::deserialize::Result { let bytes = >::from_sql(bytes)?; // todo: we either have to allocate or deal with a raw pointer... serde_json::from_slice(&bytes).map_err(|_| "Invalid Json".into()) } } impl<$($lt, )?B: diesel::backend::Backend> diesel::serialize::ToSql for $typ$(<$lt>)? { fn to_sql( &self, out: &mut diesel::serialize::Output<'_, W, B>, ) -> diesel::serialize::Result { serde_json::to_writer(out, self) .map(|_| diesel::serialize::IsNull::No) .map_err(Into::into) } } }; } pub mod crates; pub mod organisations; pub mod permissions; pub mod schema; pub mod server_private_key; pub mod users; pub mod uuid; #[macro_use] extern crate diesel; #[macro_use] extern crate diesel_migrations; use diesel::{ expression::{grouped::Grouped, AsExpression, Expression}, r2d2::{ConnectionManager, Pool}, sql_types::{Integer, Nullable}, }; use displaydoc::Display; use std::sync::Arc; use thiserror::Error; #[cfg(feature = "sqlite")] pub type Connection = diesel_tracing::sqlite::InstrumentedSqliteConnection; #[cfg(feature = "postgres")] pub type Connection = diesel_tracing::pg::InstrumentedPgConnection; #[cfg(all(feature = "sqlite", feature = "postgres"))] compile_error!("Only one database backend must be enabled using --features [sqlite|postgres]"); #[cfg(not(any(feature = "sqlite", feature = "postgres")))] compile_error!( "At least one database backend must be enabled using `--features [sqlite|postgres]`" ); #[cfg(not(any(feature = "sqlite", feature = "postgres")))] pub type Connection = unimplemented!(); pub type ConnectionPool = Arc>>; pub type Result = std::result::Result; embed_migrations!(); pub fn init(connection_uri: &str) -> Result { let connection_uri = parse_connection_uri(connection_uri)?; let pool = Pool::new(ConnectionManager::new(connection_uri))?; embedded_migrations::run_with_output(&pool.get()?, &mut std::io::stdout())?; Ok(Arc::new(pool)) } #[cfg(feature = "sqlite")] pub fn parse_connection_uri(connection_uri: &str) -> Result<&str> { if connection_uri.starts_with("sqlite://") { Ok(connection_uri.trim_start_matches("sqlite://")) } else { Err(Error::SqliteConnectionUri) } } #[cfg(feature = "postgres")] pub fn parse_connection_uri(connection_uri: &str) -> Result<&str> { if connection_uri.starts_with("postgres://") { Ok(connection_uri) } else { Err(Error::PostgresConnectionUri) } } #[derive(Error, Display, Debug)] pub enum Error { /// connection_uri must be in the format `sqlite:///path/to/file.db` or `sqlite://:memory:` SqliteConnectionUri, /** * connection_uri must be a postgres connection uri as described in: * https://www.postgresql.org/docs/9.4/libpq-connect.html#LIBPQ-CONNSTRING */ PostgresConnectionUri, /// Failed to initialise to database connection pool Connection(#[from] diesel::r2d2::PoolError), /// Failed to run migrations to bring database schema up-to-date: {0} MigrationError(#[from] diesel_migrations::RunMigrationsError), /// {0} Query(#[from] diesel::result::Error), /// Failed to complete query task TaskJoin(#[from] tokio::task::JoinError), /// Key parse failure: `{0}` KeyParse(#[from] thrussh_keys::Error), /// You don't have the {0:?} permission for this crate MissingCratePermission(crate::permissions::UserPermission), /// You don't have the {0:?} permission for this organisation MissingOrganisationPermission(crate::permissions::UserPermission), /// The requested crate does not exist MissingCrate, /// The requested organisation does not exist MissingOrganisation, /// Version {0} already exists for this crate VersionConflict(String), /// Username is already taken UsernameTaken, } impl Error { #[must_use] pub fn status_code(&self) -> http::StatusCode { match self { Self::MissingCrate => http::StatusCode::NOT_FOUND, Self::MissingCratePermission(v) | Self::MissingOrganisationPermission(v) if v.contains(crate::permissions::UserPermission::VISIBLE) => { http::StatusCode::NOT_FOUND } Self::MissingCratePermission(_) | Self::MissingOrganisationPermission(_) => { http::StatusCode::FORBIDDEN } Self::KeyParse(_) | Self::VersionConflict(_) => http::StatusCode::BAD_REQUEST, _ => http::StatusCode::INTERNAL_SERVER_ERROR, } } } sql_function!(fn coalesce(x: Nullable, y: Integer) -> Integer); diesel_infix_operator!(BitwiseAnd, " & ", Integer); diesel_infix_operator!(BitwiseOr, " | ", Integer); trait BitwiseExpressionMethods: Expression + Sized { fn bitwise_and>( self, other: T, ) -> Grouped> { Grouped(BitwiseAnd::new(self.as_expression(), other.as_expression())) } fn bitwise_or>( self, other: T, ) -> Grouped> { Grouped(BitwiseOr::new(self.as_expression(), other.as_expression())) } } impl> BitwiseExpressionMethods for T {}