added swagget-ui
Continuous integration / Check (push) Successful in 42s
Continuous integration / Rustfmt (push) Successful in 34s
Continuous integration / build (push) Successful in 43s
Continuous integration / build-release (push) Successful in 1m12s

This commit is contained in:
wt committed 2026-02-15 18:04:11 +07:00
1 parent 78ca22e1b7
commit 9f0681563e
17 files changed
+544 -19

No files matched your search

Generated
+181
View File
@@ -32,6 +32,15 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -1066,6 +1075,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
@@ -1279,6 +1290,8 @@ dependencies = [
"tower-http",
"tracing",
"tracing-subscriber",
"utoipa",
"utoipa-swagger-ui",
"uuid",
]
@@ -1604,6 +1617,30 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
@@ -1696,6 +1733,35 @@ dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]]
name = "ron"
version = "0.7.1"
@@ -1727,6 +1793,40 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-embed"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.111",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rust-ini"
version = "0.18.0"
@@ -1762,6 +1862,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.28"
@@ -2801,6 +2910,47 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utoipa"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
dependencies = [
"indexmap",
"serde",
"serde_json",
"utoipa-gen",
]
[[package]]
name = "utoipa-gen"
version = "4.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"regex",
"syn 2.0.111",
]
[[package]]
name = "utoipa-swagger-ui"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b39868d43c011961e04b41623e050aedf2cc93652562ff7935ce0f819aaf2da"
dependencies = [
"axum",
"mime_guess",
"regex",
"rust-embed",
"serde",
"serde_json",
"utoipa",
"zip",
]
[[package]]
name = "uuid"
version = "1.18.1"
@@ -2830,6 +2980,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -2912,6 +3072,15 @@ dependencies = [
"wasite",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "windows-core"
version = "0.62.2"
@@ -3244,6 +3413,18 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
"flate2",
]
[[package]]
name = "zune-inflate"
version = "0.2.54"
+2
View File
@@ -20,3 +20,5 @@ uuid = { version = "1.0", features = ["v4"] }
image = "0.24"
config = "0.13"
symphonia = { version = "0.5", features = ["all"] }
utoipa = { version = "4.2", features = ["axum_extras", "chrono"] }
utoipa-swagger-ui = { version = "6.0", features = ["axum"] }
+9
View File
@@ -6,6 +6,15 @@ use sqlx::Row;
use std::sync::Arc;
// УПРОЩЕННАЯ версия получения чатов
#[utoipa::path(
get,
path = "/api/chats",
responses(
(status = 200, description = "List of chat partners", body = Vec<ChatPartner>),
(status = 401, description = "Unauthorized", body = Vec<ChatPartner>)
),
security(("bearerAuth" = []))
)]
pub async fn get_chats_handler(
State(state): State<Arc<state::AppState>>,
headers: axum::http::HeaderMap,
+9
View File
@@ -7,6 +7,15 @@ use chrono::{Duration, Utc};
use sqlx::Row;
use std::sync::Arc;
#[utoipa::path(
post,
path = "/api/login",
request_body = LoginRequest,
responses(
(status = 200, description = "Login successful", body = AuthResponse),
(status = 400, description = "Invalid credentials", body = AuthResponse)
)
)]
pub async fn login_handler(
State(state): State<Arc<state::AppState>>,
Json(payload): Json<model::LoginRequest>,
+10
View File
@@ -6,6 +6,16 @@ use axum::extract::{Json, State};
use std::sync::Arc;
// Mark messages as read endpoint
#[utoipa::path(
post,
path = "/api/mark-read",
request_body = MarkReadRequest,
responses(
(status = 200, description = "Messages marked as read", body = serde_json::Value),
(status = 401, description = "Unauthorized", body = serde_json::Value)
),
security(("bearerAuth" = []))
)]
pub async fn mark_read_handler(
State(state): State<Arc<state::AppState>>,
headers: axum::http::HeaderMap,
+10
View File
@@ -7,6 +7,16 @@ use std::sync::Arc;
use tokio::fs;
// Delete message endpoint
#[utoipa::path(
post,
path = "/api/delete-message",
request_body = DeleteMessageRequest,
responses(
(status = 200, description = "Message deletion result", body = DeleteMessageResponse),
(status = 401, description = "Unauthorized", body = DeleteMessageResponse)
),
security(("bearerAuth" = []))
)]
pub async fn delete_message_handler(
State(state): State<Arc<state::AppState>>,
headers: axum::http::HeaderMap,
+10
View File
@@ -6,6 +6,16 @@ use std::sync::Arc;
use uuid::Uuid;
// Upload avatar endpoint
#[utoipa::path(
post,
path = "/api/upload-avatar",
request_body(content = Vec<u8>, description = "Avatar image file", content_type = "multipart/form-data"),
responses(
(status = 200, description = "Avatar upload result", body = UploadAvatarResponse),
(status = 401, description = "Unauthorized", body = UploadAvatarResponse)
),
security(("bearerAuth" = []))
)]
pub async fn upload_avatar_handler(
State(state): State<Arc<state::AppState>>,
headers: axum::http::HeaderMap,
+9
View File
@@ -5,6 +5,15 @@ use axum::extract::{Json, State};
use sqlx::Row;
use std::sync::Arc;
#[utoipa::path(
get,
path = "/api/profile",
responses(
(status = 200, description = "User profile information", body = UserProfile),
(status = 401, description = "Unauthorized", body = UserProfile)
),
security(("bearerAuth" = []))
)]
pub async fn profile_handler(
State(state): State<Arc<state::AppState>>,
headers: axum::http::HeaderMap,
+9
View File
@@ -6,6 +6,15 @@ use bcrypt::{DEFAULT_COST, hash};
use chrono::{Duration, Utc};
use std::sync::Arc;
#[utoipa::path(
post,
path = "/api/register",
request_body = RegisterRequest,
responses(
(status = 200, description = "Registration successful", body = AuthResponse),
(status = 400, description = "Registration failed", body = AuthResponse)
)
)]
pub async fn register_handler(
State(state): State<Arc<state::AppState>>,
Json(payload): Json<model::RegisterRequest>,
+10
View File
@@ -5,6 +5,16 @@ use sqlx::Row;
use std::sync::Arc;
// New endpoint to search users
#[utoipa::path(
get,
path = "/api/search-users",
params(
("q" = String, Query, description = "Search query string")
),
responses(
(status = 200, description = "List of matching users", body = Vec<ChatPartner>)
)
)]
pub async fn search_users_handler(
State(state): State<Arc<state::AppState>>,
Query(params): Query<model::SearchQuery>,
+9
View File
@@ -5,6 +5,15 @@ use axum::extract::{Json, State};
use sqlx::Row;
use std::sync::Arc;
#[utoipa::path(
post,
path = "/api/validate-token",
request_body = ValidateTokenRequest,
responses(
(status = 200, description = "Token validation result", body = ValidateTokenResponse),
(status = 400, description = "Invalid token", body = ValidateTokenResponse)
)
)]
pub async fn validate_token_handler(
State(state): State<Arc<state::AppState>>,
Json(payload): Json<model::ValidateTokenRequest>,
+10
View File
@@ -12,6 +12,16 @@ use symphonia::core::probe::Hint;
use tokio::fs;
use tokio::io::AsyncWriteExt;
#[utoipa::path(
post,
path = "/api/upload",
request_body(content = Vec<u8>, description = "File to upload", content_type = "multipart/form-data"),
responses(
(status = 200, description = "File upload result", body = serde_json::Value),
(status = 401, description = "Unauthorized", body = serde_json::Value)
),
security(("bearerAuth" = []))
)]
pub async fn upload_file_handler(
State(state): State<Arc<state::AppState>>,
headers: axum::http::HeaderMap,
+21 -1
View File
@@ -12,7 +12,27 @@ use futures_util::{SinkExt, StreamExt};
use std::sync::Arc;
use tokio::sync::mpsc;
// WebSocket handlers
/// WebSocket endpoint for real-time chat messaging
///
/// ## Connection Flow
/// 1. Client connects to `/ws`
/// 2. Client sends authentication message: `{"type": "auth", "token": "your_token"}`
/// 3. Server responds with system message on success or error message on failure
/// 4. After successful authentication, client can send/receive chat messages
///
/// ## Message Types
/// - Authentication: `{"type": "auth", "token": "jwt_token"}`
/// - Chat message: `{"id": "...", "from_user": "...", "to_user": "...", "content": "...", "timestamp": 12345, "message_type": "text"}`
/// - System message: `{"type": "system", "content": "Connected successfully"}`
/// - Error message: `{"type": "error", "content": "Invalid token"}`
#[utoipa::path(
get,
path = "/ws",
responses(
(status = 101, description = "WebSocket upgrade successful")
),
tag = "WebSocket"
)]
pub async fn websocket_handler(
ws: WebSocketUpgrade,
State(state): State<Arc<state::AppState>>,
+7
View File
@@ -6,13 +6,17 @@ use axum::{
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
use tokio::sync::RwLock;
use tower_http::services::ServeDir;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
mod api;
mod conf;
mod db;
mod model;
mod openapi;
mod state;
mod utils;
mod websocket_doc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -64,6 +68,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
)
.route("/api/upload", post(api::upload::upload_file_handler))
.route("/api/profile", get(api::profile::get::profile_handler))
.merge(
SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()),
)
.nest_service("/", ServeDir::new(&state.config.files.web_dir))
.layer(DefaultBodyLimit::max(state.config.files.max_upload_size))
.with_state(state.clone());
+136 -18
View File
@@ -1,108 +1,154 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct AuthResponse {
/// Indicates if the operation was successful
pub success: bool,
/// Message describing the result
pub message: String,
/// Username of the authenticated user
pub username: Option<String>,
/// Authentication token
pub token: Option<String>,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, ToSchema)]
pub struct RegisterRequest {
/// Phone number for registration
pub phone: String,
/// Desired username
pub username: String,
/// Password for account
pub password: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, ToSchema)]
pub struct LoginRequest {
/// Phone number associated with the account
pub phone: String,
/// Password for the account
pub password: String,
}
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct ValidateTokenResponse {
/// Indicates if the token is valid
pub success: bool,
/// Username associated with the token
pub username: Option<String>,
/// Phone number associated with the token
pub phone: Option<String>,
/// URL to the user's avatar
pub avatar_url: Option<String>,
/// Additional message about the validation
pub message: Option<String>,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, ToSchema)]
pub struct ValidateTokenRequest {
/// Authentication token to validate
pub token: String,
}
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct ChatPartner {
/// Username of the chat partner
pub username: String,
/// Phone number of the chat partner
pub phone: String,
/// URL to the partner's avatar
pub avatar_url: Option<String>,
/// Content of the last message
pub last_message: Option<String>,
/// Timestamp of the last message
pub last_message_time: Option<i64>,
/// Number of unread messages
pub unread_count: i64,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, ToSchema)]
pub struct SearchQuery {
/// Search query string
pub q: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, ToSchema)]
pub struct MarkReadRequest {
/// User who sent the messages
pub from_user: String,
/// User who received the messages
pub to_user: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, ToSchema)]
pub struct DeleteMessageRequest {
/// ID of the message to delete
pub message_id: String,
/// Whether to delete for everyone
pub for_everyone: bool,
}
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct DeleteMessageResponse {
/// Indicates if the deletion was successful
pub success: bool,
/// Message describing the result
pub message: String,
}
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct UploadAvatarResponse {
/// Indicates if the upload was successful
pub success: bool,
/// URL to the uploaded avatar
pub avatar_url: Option<String>,
/// Message describing the result
pub message: String,
}
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct UserProfile {
/// Username of the profile
pub username: String,
/// Phone number of the profile
pub phone: String,
/// URL to the user's avatar
pub avatar_url: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub enum MessageType {
/// Text message
#[serde(rename = "text")]
Text,
/// File attachment
#[serde(rename = "file")]
File,
/// Photo attachment
#[serde(rename = "photo")]
Photo,
/// Video attachment
#[serde(rename = "video")]
Video,
/// Audio attachment
#[serde(rename = "audio")]
Audio,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct ChatMessage {
/// Unique identifier for the message
pub id: String,
/// User who sent the message
pub from_user: String,
/// User who received the message
pub to_user: String,
/// Content of the message
pub content: String, // Store serialized content as JSON string
/// Timestamp of when the message was sent
pub timestamp: i64,
/// Type of the message
pub message_type: MessageType,
}
@@ -214,38 +260,110 @@ pub struct ChatMessage {
// Video(VideoInfo),
// }
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct FileInfo {
/// Original filename
pub filename: String,
/// Path to the stored file
pub filepath: String,
/// Size of the file in bytes
pub size: u64,
/// MIME type of the file
pub mime_type: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct PhotoInfo {
/// Original filename
pub filename: String,
/// Path to the stored photo
pub filepath: String,
/// Size of the photo in bytes
pub size: u64,
/// Width of the photo in pixels
pub width: u32,
/// Height of the photo in pixels
pub height: u32,
/// MIME type of the photo
pub mime_type: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct VideoInfo {
/// Original filename
pub filename: String,
/// Path to the stored video
pub filepath: String,
/// Size of the video in bytes
pub size: u64,
/// Duration of the video in seconds
pub duration: Option<f64>, // in seconds
/// MIME type of the video
pub mime_type: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct AudioInfo {
/// Original filename
pub filename: String,
/// Path to the stored audio
pub filepath: String,
/// Size of the audio in bytes
pub size: u64,
/// Duration of the audio in seconds
pub duration: Option<f64>, // in seconds
/// MIME type of the audio
pub mime_type: String,
}
// WebSocket message types
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct WsAuthMessage {
/// Type of the message (should be "auth")
#[schema(example = "auth")]
pub r#type: String,
/// Authentication token
#[schema(example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")]
pub token: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct WsChatMessage {
/// Unique identifier for the message
#[schema(example = "123e4567-e89b-12d3-a456-426614174000")]
pub id: String,
/// User who sent the message
#[schema(example = "john_doe")]
pub from_user: String,
/// User who received the message
#[schema(example = "jane_doe")]
pub to_user: String,
/// Content of the message
#[schema(example = "Hello, world!")]
pub content: String,
/// Timestamp of when the message was sent
#[schema(example = 1678886400)]
pub timestamp: i64,
/// Type of the message
pub message_type: MessageType,
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct WsSystemMessage {
/// Type of the system message
#[schema(example = "system")]
pub r#type: String,
/// Content of the system message
#[schema(example = "Connected successfully")]
pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct WsErrorMessage {
/// Type of the error message
#[schema(example = "error")]
pub r#type: String,
/// Content of the error message
#[schema(example = "Invalid or expired token")]
pub content: String,
}
+74
View File
@@ -0,0 +1,74 @@
use crate::api;
use crate::model::*;
use crate::websocket_doc::WebSocketApiDoc;
use utoipa::OpenApi;
/// Chat messaging API
#[derive(OpenApi)]
#[openapi(
info(
title = "Chat Messaging API",
description = "A simple chat messaging API with user registration, login, and message handling.",
version = "1.0.0"
),
paths(
api::websock::websocket_handler,
api::login::login_handler,
api::register::register_handler,
api::security::validate_token_handler,
api::chats::get_chats_handler,
api::search::search_users_handler,
api::messages::mark_read_handler,
api::messages::delete::delete_message_handler,
api::profile::get::profile_handler,
api::profile::avatar::upload_avatar_handler,
api::upload::upload_file_handler
),
components(
schemas(
AuthResponse,
RegisterRequest,
LoginRequest,
ValidateTokenResponse,
ValidateTokenRequest,
ChatPartner,
SearchQuery,
MarkReadRequest,
DeleteMessageRequest,
DeleteMessageResponse,
UploadAvatarResponse,
UserProfile,
MessageType,
ChatMessage,
FileInfo,
PhotoInfo,
VideoInfo,
AudioInfo,
WsAuthMessage,
WsChatMessage,
WsSystemMessage,
WsErrorMessage
)
),
modifiers(&SecurityAddon, &WebSocketApiDoc)
)]
pub struct ApiDoc;
struct SecurityAddon;
impl utoipa::Modify for SecurityAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
if let Some(components) = openapi.components.as_mut() {
use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme};
components.add_security_scheme(
"bearerAuth",
SecurityScheme::Http(
HttpBuilder::new()
.scheme(HttpAuthScheme::Bearer)
.bearer_format("JWT")
.build(),
),
);
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use utoipa::Modify;
/// Расширенная документация для WebSocket API
pub struct WebSocketApiDoc;
impl Modify for WebSocketApiDoc {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
// Обновляем описание, добавляя информацию о WebSocket
let current_description = openapi.info.description.clone().unwrap_or_default();
let extended_description = format!(
"{}\n\n## WebSocket API\n\n### Connection Flow\n1. Connect to `/ws`\n2. Send auth message: `{{\"type\": \"auth\", \"token\": \"your_jwt\"}}`\n3. Receive system/error message\n4. Send/receive chat messages\n\n### Message Types:\n- **Auth**: `{{\"type\": \"auth\", \"token\": \"...\"}}`\n- **Chat**: `{{\"id\": \"...\", \"from_user\": \"...\", \"to_user\": \"...\", \"content\": \"...\", \"timestamp\": 12345, \"message_type\": \"text\"}}`\n- **System**: `{{\"type\": \"system\", \"content\": \"...\"}}`\n- **Error**: `{{\"type\": \"error\", \"content\": \"...\"}}`",
current_description
);
openapi.info.description = Some(extended_description);
// Добавляем тег для WebSocket
let websocket_tag = utoipa::openapi::tag::TagBuilder::new()
.name("WebSocket")
.description(Some("Real-time chat messaging via WebSocket"))
.build();
if let Some(tags) = &mut openapi.tags {
tags.push(websocket_tag);
} else {
openapi.tags = Some(vec![websocket_tag]);
}
}
}