From 955598dce9aeb5626654c72b0ef94850123fa8ac Mon Sep 17 00:00:00 2001 From: Tolmachev Igor Date: Sun, 14 Sep 2025 23:27:25 +0300 Subject: Add openapi specs and docs --- src/response/error.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ src/response/mod.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ src/response/success.rs | 33 +++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 src/response/error.rs create mode 100644 src/response/mod.rs create mode 100644 src/response/success.rs (limited to 'src/response') diff --git a/src/response/error.rs b/src/response/error.rs new file mode 100644 index 0000000..db39da8 --- /dev/null +++ b/src/response/error.rs @@ -0,0 +1,69 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; +use serde::Serialize; +use utoipa::ToSchema; + +use crate::ApiError; + +#[derive(Serialize, ToSchema)] +#[schema(examples("fail or error"))] +enum ErrorStatus { + #[serde(rename = "fail")] + Fail, + #[serde(rename = "error")] + Error, +} + +#[derive(Serialize, ToSchema)] +pub struct ErrorResponse { + status: ErrorStatus, + #[schema(examples("SomeErrorKind", "NotAuthorized", "Database"))] + kind: String, + #[schema(examples("some error text"))] + message: String, +} + +impl ErrorResponse { + pub fn fail(kind: impl Into, message: impl Into) -> Self { + Self { + status: ErrorStatus::Fail, + kind: kind.into(), + message: message.into(), + } + } + + pub fn error(kind: impl Into, message: impl Into) -> Self { + Self { + status: ErrorStatus::Error, + kind: kind.into(), + message: message.into(), + } + } +} + +impl IntoResponse for ErrorResponse { + fn into_response(self) -> Response { + ( + match self.status { + ErrorStatus::Fail => StatusCode::BAD_REQUEST, + ErrorStatus::Error => StatusCode::INTERNAL_SERVER_ERROR, + }, + axum::Json(self), + ) + .into_response() + } +} + +impl From for ErrorResponse +where + T: Into, +{ + fn from(value: T) -> Self { + match value.into() { + ApiError::Client(e) => Self::fail(e.kind(), e.into_message()), + ApiError::Server(e) => Self::fail(e.kind(), e.into_message()), + } + } +} diff --git a/src/response/mod.rs b/src/response/mod.rs new file mode 100644 index 0000000..166bc13 --- /dev/null +++ b/src/response/mod.rs @@ -0,0 +1,70 @@ +mod error; +mod success; + +pub use error::ErrorResponse; +use serde_json::json; +pub use success::SuccessResponse; + +use std::collections::BTreeMap; + +use utoipa::{ + IntoResponses, ToSchema, + openapi::{ + ContentBuilder, RefOr, ResponseBuilder, ResponsesBuilder, example::ExampleBuilder, + response::Response, schema::RefBuilder, + }, +}; + +pub type ApiResult = Result, ErrorResponse>; + +pub struct GlobalResponses; + +impl IntoResponses for GlobalResponses { + fn responses() -> BTreeMap> { + ResponsesBuilder::new() + .response( + "400", + ResponseBuilder::new() + .content( + "application/json", + ContentBuilder::new() + .schema(Some( + RefBuilder::new() + .ref_location_from_schema_name(ErrorResponse::name()), + )) + .examples_from_iter([( + "Fail", + ExampleBuilder::new().value(Some(json!(ErrorResponse::fail( + "SomeFailKind", + "some fail message" + )))), + )]) + .build(), + ) + .description("General response for invalid request"), + ) + .response( + "500", + ResponseBuilder::new() + .content( + "application/json", + ContentBuilder::new() + .schema(Some( + RefBuilder::new() + .ref_location_from_schema_name(ErrorResponse::name()), + )) + .examples_from_iter([( + "Error", + ExampleBuilder::new().value(Some(json!(ErrorResponse::error( + "SomeErrorKind", + "some error message" + )))), + )]) + .build(), + ) + .description("General response when a server error occurs"), + ) + .build() + .into() + } +} diff --git a/src/response/success.rs b/src/response/success.rs new file mode 100644 index 0000000..c2ec4e5 --- /dev/null +++ b/src/response/success.rs @@ -0,0 +1,33 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; +use serde::Serialize; +use utoipa::ToSchema; + +#[derive(Serialize, ToSchema)] +enum SuccessStatus { + #[serde(rename = "success")] + Success, +} + +#[derive(Serialize, ToSchema)] +pub struct SuccessResponse { + status: SuccessStatus, + data: T, +} + +impl SuccessResponse { + pub fn ok(data: T) -> Self { + Self { + status: SuccessStatus::Success, + data, + } + } +} + +impl IntoResponse for SuccessResponse { + fn into_response(self) -> Response { + (StatusCode::OK, axum::Json(self)).into_response() + } +} -- cgit v1.3