use crate::config::{Config, ConfigError};
use crate::events::{AnyError, EventBus, EventHandler, EventName};
use crate::ports::{Port, Ports};
use crate::surface::Surface;
use crate::template::TemplateRegistry;
use crate::venture::Venture;
use std::sync::Arc;
pub use futures_core::future::BoxFuture;
pub const HARNESS_API: u32 = 1;
#[must_use]
pub fn harness_api_mismatch(module: &dyn Module) -> String {
format!(
"module `{name}` v{version} targets harness API {api}, but {core} v{core_version} \
provides harness API {harness_api}: rebuild `{name}` against this core — the supported \
ranges are in docs/COMPATIBILITY.md",
name = module.name(),
version = module.version(),
api = module.harness_api(),
core = env!("CARGO_PKG_NAME"),
core_version = env!("CARGO_PKG_VERSION"),
harness_api = HARNESS_API,
)
}
#[derive(Debug, Clone)]
pub struct SqlMigration {
pub id: &'static str,
pub name: &'static str,
pub sql: &'static str,
}
#[must_use]
pub fn migration_checksum(sql: &str) -> String {
use sha2::{Digest, Sha256};
let digest = Sha256::digest(sql.as_bytes());
let mut hex = String::with_capacity(64);
for byte in digest {
use std::fmt::Write as _;
let _ = write!(hex, "{byte:02x}");
}
hex
}
#[must_use]
pub fn migration_edited(key: &str, recorded: &str, found: &str) -> String {
format!(
"migration `{key}` was already applied to this database with different SQL \
(recorded sha256 {}…, embedded {}…). Applied migrations are never edited: \
revert the change and write a new migration instead \
(docs/MODULE-AUTHORING.md, step 3)",
&recorded[..recorded.len().min(12)],
&found[..found.len().min(12)]
)
}
#[derive(Debug, Clone)]
pub struct Migrations {
pub sqlite: &'static [SqlMigration],
pub postgres: &'static [SqlMigration],
}
impl Migrations {
pub const EMPTY: Migrations = Migrations {
sqlite: &[],
postgres: &[],
};
pub const fn sqlite(migrations: &'static [SqlMigration]) -> Self {
Migrations {
sqlite: migrations,
postgres: &[],
}
}
}
impl Default for Migrations {
fn default() -> Self {
Self::EMPTY
}
}
pub struct ModuleContext {
pub ports: Ports,
pub config: Arc<dyn Config>,
pub events: EventBus,
pub templates: Arc<TemplateRegistry>,
pub venture: Arc<Venture>,
pub ui_mounted: bool,
}
pub trait Module: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn version(&self) -> &'static str;
fn harness_api(&self) -> u32 {
HARNESS_API
}
fn requires(&self) -> &'static [Port];
fn optional(&self) -> &'static [Port] {
&[]
}
fn tables(&self) -> &'static [&'static str] {
&[]
}
fn emits(&self) -> &'static [&'static str] {
&[]
}
fn public_writes(&self) -> bool {
false
}
fn migrations(&self) -> Migrations;
fn validate_config(&self, cfg: &dyn Config) -> Result<(), ConfigError>;
fn router(&self, ctx: ModuleContext) -> axum::Router;
fn well_known(&self) -> Option<axum::Router> {
None
}
fn surface(&self) -> Surface {
Surface::none()
}
fn events(&self) -> Vec<(EventName, EventHandler)> {
Vec::new()
}
fn scheduled<'a>(
&'a self,
ctx: &'a ModuleContext,
cron: &'a str,
) -> BoxFuture<'a, Result<(), AnyError>> {
let _ = (ctx, cron);
Box::pin(async { Ok(()) })
}
}