diff --git a/crates/synor-gateway/src/lib.rs b/crates/synor-gateway/src/lib.rs index 61edc68..6fac3d9 100644 --- a/crates/synor-gateway/src/lib.rs +++ b/crates/synor-gateway/src/lib.rs @@ -56,6 +56,8 @@ pub mod auth; pub mod config; pub mod error; pub mod middleware; +#[cfg(feature = "openapi")] +pub mod openapi; pub mod response; pub mod routes; pub mod server; diff --git a/crates/synor-gateway/src/openapi.rs b/crates/synor-gateway/src/openapi.rs new file mode 100644 index 0000000..82afccc --- /dev/null +++ b/crates/synor-gateway/src/openapi.rs @@ -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")); + } +} diff --git a/crates/synor-gateway/src/response.rs b/crates/synor-gateway/src/response.rs index 56f9303..fd06d6f 100644 --- a/crates/synor-gateway/src/response.rs +++ b/crates/synor-gateway/src/response.rs @@ -7,6 +7,8 @@ use axum::{ }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "openapi")] +use utoipa::ToSchema; /// Standard API response wrapper. #[derive(Debug, Serialize, Deserialize)] @@ -43,6 +45,7 @@ pub struct ResponseMeta { /// Pagination metadata. #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(ToSchema))] pub struct PaginationMeta { /// Total number of items pub total: u64, @@ -247,6 +250,7 @@ impl Default for SortOrder { /// Health check response. #[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(ToSchema))] pub struct HealthResponse { /// Overall health status pub status: HealthStatus, @@ -265,6 +269,7 @@ pub struct HealthResponse { /// Health status enum. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(ToSchema))] pub enum HealthStatus { Healthy, Degraded, @@ -273,6 +278,7 @@ pub enum HealthStatus { /// Individual service health. #[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(ToSchema))] pub struct ServiceHealth { /// Service name pub name: String,