feat: add OpenAPI specification generation for gateway

Adds utoipa-based OpenAPI 3.1 documentation:
- OpenAPI module with API metadata, server info, and tags
- Security scheme definitions (API key and JWT bearer)
- ToSchema derives for response types
- JSON specification generation endpoint
This commit is contained in:
Gulshan Yadav 2026-01-28 15:18:53 +05:30
parent f8c536b7cd
commit 8ab9c6c7a2
3 changed files with 111 additions and 0 deletions

View file

@ -56,6 +56,8 @@ pub mod auth;
pub mod config; pub mod config;
pub mod error; pub mod error;
pub mod middleware; pub mod middleware;
#[cfg(feature = "openapi")]
pub mod openapi;
pub mod response; pub mod response;
pub mod routes; pub mod routes;
pub mod server; pub mod server;

View file

@ -0,0 +1,103 @@
//! OpenAPI documentation generation.
//!
//! This module generates OpenAPI 3.1 specifications for all gateway endpoints
//! using utoipa.
use utoipa::openapi::security::{ApiKey, ApiKeyValue, HttpAuthScheme, HttpBuilder, SecurityScheme};
use utoipa::{Modify, OpenApi};
use crate::response::{HealthResponse, HealthStatus, PaginationMeta, ServiceHealth};
/// OpenAPI documentation for the Synor Gateway API.
#[derive(OpenApi)]
#[openapi(
info(
title = "Synor Gateway API",
version = "1.0.0",
description = "Unified REST API gateway for all Synor blockchain services.\n\nProvides access to wallet management, blockchain RPC, decentralized storage, DEX trading, IBC cross-chain operations, zero-knowledge proofs, and smart contract compilation.",
contact(
name = "Synor Team",
email = "api@synor.io",
url = "https://synor.io"
),
license(
name = "MIT OR Apache-2.0",
url = "https://github.com/synortech/synor/blob/main/LICENSE"
)
),
servers(
(url = "https://api.synor.io/v1", description = "Production API"),
(url = "https://testnet.api.synor.io/v1", description = "Testnet API"),
(url = "http://localhost:8000/v1", description = "Local development")
),
tags(
(name = "Health", description = "Service health and status endpoints"),
(name = "Wallet", description = "Wallet management and key operations"),
(name = "RPC", description = "Blockchain RPC and chain state"),
(name = "Storage", description = "Decentralized storage operations"),
(name = "DEX", description = "Decentralized exchange trading"),
(name = "IBC", description = "Inter-Blockchain Communication"),
(name = "ZK", description = "Zero-knowledge proof operations"),
(name = "Compiler", description = "Smart contract compilation and analysis")
),
components(
schemas(
// Response types
HealthResponse,
HealthStatus,
ServiceHealth,
PaginationMeta,
)
),
modifiers(&SecurityAddon)
)]
pub struct ApiDoc;
/// Security scheme modifier for OpenAPI documentation.
struct SecurityAddon;
impl Modify for SecurityAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
if let Some(components) = openapi.components.as_mut() {
components.add_security_scheme(
"api_key",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("X-API-Key"))),
);
components.add_security_scheme(
"bearer_auth",
SecurityScheme::Http(
HttpBuilder::new()
.scheme(HttpAuthScheme::Bearer)
.bearer_format("JWT")
.build(),
),
);
}
}
}
/// Generate the OpenAPI JSON specification.
pub fn generate_openapi_json() -> String {
ApiDoc::openapi().to_json().unwrap_or_else(|e| {
tracing::error!("Failed to generate OpenAPI JSON: {}", e);
"{}".to_string()
})
}
/// Generate the OpenAPI YAML specification.
pub fn generate_openapi_yaml() -> String {
// utoipa 4.x doesn't have to_yaml, use JSON for now
generate_openapi_json()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_openapi_generation() {
let json = generate_openapi_json();
assert!(!json.is_empty());
assert!(json.contains("Synor Gateway API"));
}
}

View file

@ -7,6 +7,8 @@ use axum::{
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
/// Standard API response wrapper. /// Standard API response wrapper.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -43,6 +45,7 @@ pub struct ResponseMeta {
/// Pagination metadata. /// Pagination metadata.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PaginationMeta { pub struct PaginationMeta {
/// Total number of items /// Total number of items
pub total: u64, pub total: u64,
@ -247,6 +250,7 @@ impl Default for SortOrder {
/// Health check response. /// Health check response.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct HealthResponse { pub struct HealthResponse {
/// Overall health status /// Overall health status
pub status: HealthStatus, pub status: HealthStatus,
@ -265,6 +269,7 @@ pub struct HealthResponse {
/// Health status enum. /// Health status enum.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub enum HealthStatus { pub enum HealthStatus {
Healthy, Healthy,
Degraded, Degraded,
@ -273,6 +278,7 @@ pub enum HealthStatus {
/// Individual service health. /// Individual service health.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ServiceHealth { pub struct ServiceHealth {
/// Service name /// Service name
pub name: String, pub name: String,