#![doc(html_root_url = "https://docs.rs/sdforge/0.3.0")]
#![warn(missing_docs)]
extern crate self as sdforge;
pub use sdforge_macros::{service_api, service_module, test_macro};
#[macro_export]
macro_rules! impl_default_new {
($type:ident) => {
impl $type {
pub fn new() -> Self {
Self
}
}
impl Default for $type {
fn default() -> Self {
Self::new()
}
}
};
}
#[cfg(any(
feature = "http",
feature = "mcp",
feature = "websocket",
feature = "grpc",
feature = "cli"
))]
pub use inventory;
#[cfg(feature = "streaming")]
pub use tokio_stream;
#[cfg(feature = "http")]
pub mod axum {
pub use axum::body::Body;
pub use axum::response::IntoResponse;
pub mod routing {
pub use axum::routing::{get, post, MethodRouter};
}
pub mod extract {
pub use axum::extract::{Extension, Form, Json, Path, Query};
pub use axum_extra::TypedHeader;
}
pub mod http {
pub use axum::http::Request;
pub mod header {
pub use axum::http::header::CONTENT_TYPE;
}
pub mod status {
pub use axum::http::StatusCode;
}
}
pub mod handler {
pub use axum::handler::Handler;
}
pub use axum::serve;
}
pub mod prelude {
#[cfg(feature = "http")]
pub use crate::core::validation::validators::{validate_email, validate_length};
pub use crate::core::{ApiError, ApiMetadata, ServiceError, ServiceResponse};
#[cfg(feature = "http")]
pub use crate::http::{HttpRoute, RouteRegistration};
#[cfg(feature = "mcp")]
pub use crate::mcp::McpToolInstance;
#[cfg(feature = "http")]
pub use crate::axum::IntoResponse;
pub use sdforge_macros::{service_api, service_module, test_macro};
}
pub mod core;
#[cfg(feature = "http")]
pub mod http;
#[cfg(feature = "mcp")]
pub mod mcp;
#[cfg(feature = "mcp")]
pub use mcp::{
build, get_mcp_tools, InputRequiredResult, McpHeaderInfo, McpToolInstance, McpToolRegistration,
MrtrSession, SdForgeMcpServer, SdForgeTool, StatelessServerHandler,
};
#[cfg(feature = "streaming")]
pub mod streaming;
#[cfg(feature = "streaming")]
pub use streaming::{create_stream_channel, stream_to_sse, StreamEvent, StreamResponse};
#[cfg(any(feature = "security", feature = "ratelimit"))]
pub mod security;
#[cfg(feature = "security")]
pub use security::{
auth_middleware,
ApiKeyAuth,
AppApiKeyAuth,
AppApiKeyAuthBuilder,
AppAuditLogger,
AppAuditLoggerBuilder,
AuditLog,
AuditLogger,
AuditResult,
AuthContext,
AuthError,
AuthExtractor,
AuthMetadata,
AuthResult,
BearerAuth,
BearerAuthBuilder,
};
#[cfg(feature = "http")]
pub mod config;
#[cfg(feature = "http")]
pub use config::{
ApiConfig, AppConfig, AuthConfig, ConfigError, CorsConfig, EnvHelper, ServerConfig, TlsConfig,
TracingConfig,
};
#[cfg(feature = "cache")]
pub use oxcache;
#[cfg(feature = "cache")]
pub mod cache;
#[cfg(feature = "cache")]
pub use cache::{Cache, CacheKey, DashMapCache, OxcacheSyncCache, SharedCache, SyncCache};
#[cfg(feature = "websocket")]
pub mod websocket;
#[cfg(feature = "websocket")]
pub use websocket::{
parse_websocket_message, websocket_upgrade, BoxFuture, ConnectionManager,
ValidatedWebSocketUpgrade, WebSocketConfig, WebSocketConnection, WebSocketHandler,
WebSocketMessage, WebSocketRoute,
};
#[cfg(feature = "grpc")]
pub mod grpc;
#[cfg(feature = "grpc")]
pub use grpc::{
build_server, build_server_with_config, GrpcRoute, GrpcServerConfig, SdForgeGrpcService,
};
#[cfg(feature = "logging")]
pub mod logging;
#[cfg(feature = "logging")]
pub use logging::{
get_global_logger, init_global_logger, LogEntry, LogLevel, LoggerConfig, StructuredLogger,
};
#[cfg(feature = "grpc")]
pub use grpc::sdforge_v1::{
sd_forge_service_server::SdForgeServiceServer, CallRequest, CallResponse, InfoRequest,
InfoResponse,
};
#[cfg(feature = "http")]
pub use http::version_routing::{build_version_router, VersionRouterConfig, VersionedRoute};
#[cfg(feature = "cli")]
pub mod cli;
#[cfg(feature = "openapi")]
pub mod openapi;
#[cfg(feature = "openapi")]
pub use openapi::{generate_openapi_spec, OpenApiBuilder, OpenApiPathParam, OpenApiRouteInfo};
#[cfg(feature = "docs")]
pub mod docs;
#[cfg(feature = "docs")]
pub use docs::{generate_docs, write_docs, DocError, DocFormat};
#[cfg(feature = "docs")]
pub use docs::swagger_ui_router;
#[cfg(any(
feature = "http",
feature = "mcp",
feature = "websocket",
feature = "grpc",
feature = "cli"
))]
pub fn init_all_plugins() -> PluginCounts {
use std::sync::Mutex;
use std::sync::OnceLock;
#[cfg(feature = "http")]
let routes = {
use crate::http::RouteRegistration;
static ROUTES: OnceLock<Mutex<Vec<&'static RouteRegistration>>> = OnceLock::new();
let routes =
ROUTES.get_or_init(|| Mutex::new(inventory::iter::<RouteRegistration>().collect()));
routes.lock().map(|g| g.len()).unwrap_or_else(|e| {
log::error!("inventory Mutex poisoned: {}", e);
0
})
};
#[cfg(not(feature = "http"))]
let routes = 0;
#[cfg(feature = "mcp")]
let mcp_tools = {
use crate::mcp::McpToolRegistration;
static MCP_TOOLS: OnceLock<Mutex<Vec<&'static McpToolRegistration>>> = OnceLock::new();
let tools = MCP_TOOLS
.get_or_init(|| Mutex::new(inventory::iter::<McpToolRegistration>().collect()));
tools.lock().map(|g| g.len()).unwrap_or_else(|e| {
log::error!("inventory Mutex poisoned: {}", e);
0
})
};
#[cfg(feature = "websocket")]
let ws_routes = {
use crate::websocket::WebSocketRoute;
static WS_ROUTES: OnceLock<Mutex<Vec<&'static WebSocketRoute>>> = OnceLock::new();
let routes =
WS_ROUTES.get_or_init(|| Mutex::new(inventory::iter::<WebSocketRoute>().collect()));
routes.lock().map(|g| g.len()).unwrap_or_else(|e| {
log::error!("inventory Mutex poisoned: {}", e);
0
})
};
#[cfg(feature = "grpc")]
let grpc_routes = {
use crate::grpc::GrpcRouteRegistration;
static GRPC_ROUTES: OnceLock<Mutex<Vec<&'static GrpcRouteRegistration>>> = OnceLock::new();
let routes = GRPC_ROUTES
.get_or_init(|| Mutex::new(inventory::iter::<GrpcRouteRegistration>().collect()));
routes.lock().map(|g| g.len()).unwrap_or_else(|e| {
log::error!("inventory Mutex poisoned: {}", e);
0
})
};
#[cfg(feature = "cli")]
let cli_commands = {
use crate::cli::{CliCommandRegistration, CliHandlerRegistration};
static CLI_CMDS: OnceLock<Mutex<Vec<&'static CliCommandRegistration>>> = OnceLock::new();
let cmds = CLI_CMDS
.get_or_init(|| Mutex::new(inventory::iter::<CliCommandRegistration>().collect()));
static CLI_HANDLERS: OnceLock<Mutex<Vec<&'static CliHandlerRegistration>>> =
OnceLock::new();
let _handlers = CLI_HANDLERS
.get_or_init(|| Mutex::new(inventory::iter::<CliHandlerRegistration>().collect()));
cmds.lock().map(|g| g.len()).unwrap_or_else(|e| {
log::error!("inventory Mutex poisoned: {}", e);
0
})
};
PluginCounts {
routes,
#[cfg(feature = "mcp")]
mcp_tools,
#[cfg(feature = "websocket")]
ws_routes,
#[cfg(feature = "grpc")]
grpc_routes,
#[cfg(feature = "cli")]
cli_commands,
}
}
#[cfg(any(
feature = "http",
feature = "mcp",
feature = "websocket",
feature = "grpc",
feature = "cli"
))]
pub struct PluginCounts {
pub routes: usize,
#[cfg(feature = "mcp")]
pub mcp_tools: usize,
#[cfg(feature = "websocket")]
pub ws_routes: usize,
#[cfg(feature = "grpc")]
pub grpc_routes: usize,
#[cfg(feature = "cli")]
pub cli_commands: usize,
}
#[cfg(feature = "websocket")]
pub fn get_websocket_routes() -> Vec<&'static crate::websocket::WebSocketRoute> {
inventory::iter::<crate::websocket::WebSocketRoute>().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[serial_test::serial]
fn test_init_all_plugins_returns_counts() {
let counts = init_all_plugins();
let _ = counts.routes;
#[cfg(feature = "mcp")]
let _ = counts.mcp_tools;
#[cfg(feature = "websocket")]
let _ = counts.ws_routes;
#[cfg(feature = "grpc")]
let _ = counts.grpc_routes;
#[cfg(feature = "cli")]
let _ = counts.cli_commands;
}
#[test]
#[serial_test::serial]
fn test_init_all_plugins_is_idempotent() {
let first = init_all_plugins();
let second = init_all_plugins();
assert_eq!(first.routes, second.routes);
#[cfg(feature = "mcp")]
assert_eq!(first.mcp_tools, second.mcp_tools);
#[cfg(feature = "websocket")]
assert_eq!(first.ws_routes, second.ws_routes);
#[cfg(feature = "grpc")]
assert_eq!(first.grpc_routes, second.grpc_routes);
#[cfg(feature = "cli")]
assert_eq!(first.cli_commands, second.cli_commands);
}
#[cfg(feature = "mcp")]
#[test]
fn test_get_mcp_tools_returns_vec() {
let tools = get_mcp_tools();
let _ = tools.len();
}
#[cfg(feature = "websocket")]
#[test]
fn test_get_websocket_routes_returns_vec() {
let routes = get_websocket_routes();
let _ = routes.len();
}
#[test]
fn test_impl_default_new_macro() {
struct EmptyConfig;
impl_default_new!(EmptyConfig);
let config = EmptyConfig::new();
let _default = EmptyConfig;
let _: EmptyConfig = config;
}
#[test]
#[allow(clippy::default_constructed_unit_structs)] fn test_impl_default_new_macro_generates_default_trait() {
struct ConfigA;
impl_default_new!(ConfigA);
let from_new = ConfigA::new();
let from_default = ConfigA::default();
let _: ConfigA = from_new;
let _: ConfigA = from_default;
}
#[test]
#[allow(clippy::default_constructed_unit_structs)] fn test_impl_default_new_macro_multiple_structs() {
struct FirstConfig;
struct SecondConfig;
impl_default_new!(FirstConfig);
impl_default_new!(SecondConfig);
let _a = FirstConfig::new();
let _b = SecondConfig::new();
let _a2 = FirstConfig::default();
let _b2 = SecondConfig::default();
}
#[test]
#[serial_test::serial]
fn test_plugin_counts_routes_field_is_usize() {
let counts = init_all_plugins();
let routes: usize = counts.routes;
let _ = routes;
}
#[test]
#[serial_test::serial]
fn test_init_all_plugins_counts_are_stable_across_calls() {
let a = init_all_plugins();
let b = init_all_plugins();
let c = init_all_plugins();
assert_eq!(a.routes, b.routes);
assert_eq!(b.routes, c.routes);
}
}