diff --git a/crates/core/result/Cargo.toml b/crates/core/result/Cargo.toml index 23c87e57..2c31908b 100644 --- a/crates/core/result/Cargo.toml +++ b/crates/core/result/Cargo.toml @@ -11,12 +11,20 @@ description = "Revolt Backend: Result and Error types" [features] serde = [ "dep:serde" ] schemas = [ "dep:schemars" ] +rocket = [ "dep:rocket", "dep:serde_json" ] +okapi = [ "dep:revolt_rocket_okapi", "dep:revolt_okapi" ] default = [ "serde" ] [dependencies] # Serialisation +serde_json = { version = "1", optional = true } serde = { version = "1", features = ["derive"], optional = true } # Spec Generation schemars = { version = "0.8.8", optional = true } + +# Rocket +rocket = { optional = true, version = "0.5.0-rc.2", default-features = false } +revolt_rocket_okapi = { version = "0.9.1", optional = true } +revolt_okapi = { version = "0.9.1", optional = true } diff --git a/crates/core/result/src/lib.rs b/crates/core/result/src/lib.rs index 7e0122da..7115405d 100644 --- a/crates/core/result/src/lib.rs +++ b/crates/core/result/src/lib.rs @@ -6,6 +6,12 @@ extern crate serde; #[macro_use] extern crate schemars; +#[cfg(feature = "rocket")] +pub mod rocket; + +#[cfg(feature = "okapi")] +pub mod okapi; + /// Result type with custom Error pub type Result = std::result::Result; diff --git a/crates/core/result/src/okapi.rs b/crates/core/result/src/okapi.rs new file mode 100644 index 00000000..0d6e724f --- /dev/null +++ b/crates/core/result/src/okapi.rs @@ -0,0 +1,49 @@ +use revolt_okapi::openapi3::SchemaObject; +use revolt_rocket_okapi::revolt_okapi::openapi3; +use schemars::schema::Schema; + +use crate::Error; + +impl revolt_rocket_okapi::response::OpenApiResponderInner for Error { + fn responses( + gen: &mut revolt_rocket_okapi::gen::OpenApiGenerator, + ) -> std::result::Result { + let mut content = revolt_okapi::Map::new(); + + let settings = schemars::gen::SchemaSettings::default().with(|s| { + s.option_nullable = true; + s.option_add_null_type = false; + s.definitions_path = "#/components/schemas/".to_string(); + }); + + let mut schema_generator = settings.into_generator(); + let schema = schema_generator.root_schema_for::(); + + let definitions = gen.schema_generator().definitions_mut(); + for (key, value) in schema.definitions { + definitions.insert(key, value); + } + + definitions.insert("Error".to_string(), Schema::Object(schema.schema)); + + content.insert( + "application/json".to_string(), + openapi3::MediaType { + schema: Some(SchemaObject { + reference: Some("#/components/schemas/Error".to_string()), + ..Default::default() + }), + ..Default::default() + }, + ); + + Ok(openapi3::Responses { + default: Some(openapi3::RefOr::Object(openapi3::Response { + content, + description: "An error occurred.".to_string(), + ..Default::default() + })), + ..Default::default() + }) + } +} diff --git a/crates/core/result/src/rocket.rs b/crates/core/result/src/rocket.rs new file mode 100644 index 00000000..a1723905 --- /dev/null +++ b/crates/core/result/src/rocket.rs @@ -0,0 +1,87 @@ +use std::io::Cursor; + +use rocket::{ + http::{ContentType, Status}, + response::{self, Responder}, + Request, Response, +}; + +use crate::{Error, ErrorType}; + +/// HTTP response builder for Error enum +impl<'r> Responder<'r, 'static> for Error { + fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { + let status = match self.error_type { + ErrorType::LabelMe => Status::InternalServerError, + + ErrorType::AlreadyOnboarded => Status::Forbidden, + + ErrorType::UnknownUser => Status::NotFound, + ErrorType::InvalidUsername => Status::BadRequest, + ErrorType::UsernameTaken => Status::Conflict, + ErrorType::AlreadyFriends => Status::Conflict, + ErrorType::AlreadySentRequest => Status::Conflict, + ErrorType::Blocked => Status::Conflict, + ErrorType::BlockedByOther => Status::Forbidden, + ErrorType::NotFriends => Status::Forbidden, + + ErrorType::UnknownChannel => Status::NotFound, + ErrorType::UnknownMessage => Status::NotFound, + ErrorType::UnknownAttachment => Status::BadRequest, + ErrorType::CannotEditMessage => Status::Forbidden, + ErrorType::CannotJoinCall => Status::BadRequest, + ErrorType::TooManyAttachments { .. } => Status::BadRequest, + ErrorType::TooManyReplies { .. } => Status::BadRequest, + ErrorType::EmptyMessage => Status::UnprocessableEntity, + ErrorType::PayloadTooLarge => Status::UnprocessableEntity, + ErrorType::CannotRemoveYourself => Status::BadRequest, + ErrorType::GroupTooLarge { .. } => Status::Forbidden, + ErrorType::AlreadyInGroup => Status::Conflict, + ErrorType::NotInGroup => Status::NotFound, + + ErrorType::UnknownServer => Status::NotFound, + ErrorType::InvalidRole => Status::NotFound, + ErrorType::Banned => Status::Forbidden, + + ErrorType::TooManyServers { .. } => Status::BadRequest, + ErrorType::TooManyEmoji { .. } => Status::BadRequest, + ErrorType::TooManyChannels { .. } => Status::BadRequest, + ErrorType::TooManyRoles { .. } => Status::BadRequest, + + ErrorType::ReachedMaximumBots => Status::BadRequest, + ErrorType::IsBot => Status::BadRequest, + ErrorType::BotIsPrivate => Status::Forbidden, + + ErrorType::CannotReportYourself => Status::BadRequest, + + ErrorType::MissingPermission { .. } => Status::Forbidden, + ErrorType::MissingUserPermission { .. } => Status::Forbidden, + ErrorType::NotElevated => Status::Forbidden, + ErrorType::NotPrivileged => Status::Forbidden, + ErrorType::CannotGiveMissingPermissions => Status::Forbidden, + ErrorType::NotOwner => Status::Forbidden, + + ErrorType::DatabaseError { .. } => Status::InternalServerError, + ErrorType::InternalError => Status::InternalServerError, + ErrorType::InvalidOperation => Status::BadRequest, + ErrorType::InvalidCredentials => Status::Unauthorized, + ErrorType::InvalidProperty => Status::BadRequest, + ErrorType::InvalidSession => Status::Unauthorized, + ErrorType::DuplicateNonce => Status::Conflict, + ErrorType::VosoUnavailable => Status::BadRequest, + ErrorType::NotFound => Status::NotFound, + ErrorType::NoEffect => Status::Ok, + ErrorType::FailedValidation { .. } => Status::BadRequest, + }; + + // Serialize the error data structure into JSON. + let string = serde_json::to_string(&self).unwrap(); + + // Build and send the request. + Response::build() + .sized_body(string.len(), Cursor::new(string)) + .header(ContentType::new("application", "json")) + .status(status) + .ok() + } +}