Skip to main content

modkit/
contracts.rs

1use async_trait::async_trait;
2use axum::Router;
3use tokio_util::sync::CancellationToken;
4
5pub use crate::api::openapi_registry::OpenApiRegistry;
6
7/// System capability: receives runtime internals before init.
8///
9/// This trait is internal to modkit and only used by modules with the "system" capability.
10/// Normal user modules don't implement this.
11#[async_trait]
12pub trait SystemCapability: Send + Sync {
13    /// Optional pre-init hook for system modules.
14    ///
15    /// This runs BEFORE `init()` has completed for ALL modules, and only for system modules.
16    ///
17    /// Default implementation is a no-op so most modules don't need to implement it.
18    ///
19    /// # Errors
20    /// Returns an error if system wiring fails.
21    fn pre_init(&self, _sys: &crate::runtime::SystemContext) -> anyhow::Result<()> {
22        Ok(())
23    }
24
25    /// Optional post-init hook for system modules.
26    ///
27    /// This runs AFTER `init()` has completed for ALL modules, and only for system modules.
28    ///
29    /// Default implementation is a no-op so most modules don't need to implement it.
30    async fn post_init(&self, _sys: &crate::runtime::SystemContext) -> anyhow::Result<()> {
31        Ok(())
32    }
33}
34
35/// Core module: DI/wiring; do not rely on migrated schema here.
36#[async_trait]
37pub trait Module: Send + Sync + 'static {
38    async fn init(&self, ctx: &crate::context::ModuleCtx) -> anyhow::Result<()>;
39}
40
41#[async_trait]
42pub trait DatabaseCapability: Send + Sync {
43    /// Runs AFTER init, BEFORE REST/start.
44    async fn migrate(&self, db: &modkit_db::DbHandle) -> anyhow::Result<()>;
45}
46
47/// REST API capability: Pure wiring; must be sync. Runs AFTER DB migrations.
48pub trait RestApiCapability: Send + Sync {
49    /// Register REST routes for this module.
50    ///
51    /// # Errors
52    /// Returns an error if route registration fails.
53    fn register_rest(
54        &self,
55        ctx: &crate::context::ModuleCtx,
56        router: Router,
57        openapi: &dyn OpenApiRegistry,
58    ) -> anyhow::Result<Router>;
59}
60
61/// API Gateway capability: handles gateway hosting with prepare/finalize phases.
62/// Must be sync. Runs during REST phase, but doesn't start the server.
63#[allow(dead_code)]
64pub trait ApiGatewayCapability: Send + Sync + 'static {
65    /// Prepare a base Router (e.g., global middlewares, /healthz) and optionally touch `OpenAPI` meta.
66    /// Do NOT start the server here.
67    ///
68    /// # Errors
69    /// Returns an error if router preparation fails.
70    fn rest_prepare(
71        &self,
72        ctx: &crate::context::ModuleCtx,
73        router: Router,
74    ) -> anyhow::Result<Router>;
75
76    /// Finalize before start: attach /openapi.json, /docs, persist the Router internally if needed.
77    /// Do NOT start the server here.
78    ///
79    /// # Errors
80    /// Returns an error if router finalization fails.
81    fn rest_finalize(
82        &self,
83        ctx: &crate::context::ModuleCtx,
84        router: Router,
85    ) -> anyhow::Result<Router>;
86
87    // Return OpenAPI registry of the module, e.g., to register endpoints
88    fn as_registry(&self) -> &dyn OpenApiRegistry;
89}
90
91#[async_trait]
92pub trait RunnableCapability: Send + Sync {
93    async fn start(&self, cancel: CancellationToken) -> anyhow::Result<()>;
94    async fn stop(&self, cancel: CancellationToken) -> anyhow::Result<()>;
95}
96
97/// Represents a gRPC service registration callback used by the gRPC hub.
98///
99/// Each module that exposes gRPC services provides one or more of these.
100/// The `register` closure adds the service into the provided `RoutesBuilder`.
101#[cfg(feature = "otel")]
102pub struct RegisterGrpcServiceFn {
103    pub service_name: &'static str,
104    pub register: Box<dyn Fn(&mut tonic::service::RoutesBuilder) + Send + Sync>,
105}
106
107#[cfg(not(feature = "otel"))]
108pub struct RegisterGrpcServiceFn {
109    pub service_name: &'static str,
110}
111
112/// gRPC Service capability: modules that export gRPC services.
113///
114/// The runtime will call this during the gRPC registration phase to collect
115/// all services that should be exposed on the shared gRPC server.
116#[async_trait]
117pub trait GrpcServiceCapability: Send + Sync {
118    /// Returns all gRPC services this module wants to expose.
119    ///
120    /// Each installer adds one service to the `tonic::Server` builder.
121    async fn get_grpc_services(
122        &self,
123        ctx: &crate::context::ModuleCtx,
124    ) -> anyhow::Result<Vec<RegisterGrpcServiceFn>>;
125}
126
127/// gRPC Hub capability: hosts the gRPC server.
128///
129/// This trait is implemented by the single module responsible for hosting
130/// the `tonic::Server` instance. Only one module per process should implement this.
131pub trait GrpcHubCapability: Send + Sync {
132    /// Returns the bound endpoint after the server starts listening.
133    ///
134    /// Examples:
135    /// - TCP: `http://127.0.0.1:50652`
136    /// - Unix socket: `unix:///path/to/socket`
137    /// - Named pipe: `pipe://\\.\pipe\name`
138    ///
139    /// Returns `None` if the server hasn't started listening yet.
140    fn bound_endpoint(&self) -> Option<String>;
141}