use std::path::PathBuf;
use serde::{Deserialize, Serialize};
pub type AdapterResult<T> = Result<T, AdapterError>;
pub trait Adapter: Send + Sync {
fn name(&self) -> &'static str;
fn manifest_files(&self) -> &'static [&'static str];
fn lock(&self, ctx: &AdapterCtx) -> AdapterResult<LockOutcome>;
fn build(&self, ctx: &AdapterCtx) -> AdapterResult<BuildSpec>;
fn plan(&self, ctx: &AdapterCtx, intent: &PlanIntent) -> AdapterResult<Plan>;
fn confirm(&self, ctx: &AdapterCtx) -> AdapterResult<ConfirmReport>;
fn diff(&self, ctx: &AdapterCtx, against: &DiffRef) -> AdapterResult<DiffReport>;
fn sbom(&self, ctx: &AdapterCtx, format: SbomFormat) -> AdapterResult<Sbom>;
fn quirks_registry(&self) -> Vec<AdapterQuirkEntry> {
Vec::new()
}
fn dispatcher_reflection(&self) -> Vec<DispatcherVariant> {
Vec::new()
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DispatcherVariant {
pub kind: String,
pub fields: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AdapterQuirkEntry {
pub package: String,
pub quirks: Vec<serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct AdapterCtx {
pub workspace_root: PathBuf,
pub target: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockOutcome {
pub lockfile_path: PathBuf,
pub created: bool,
pub bumped: Vec<DependencyBump>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencyBump {
pub name: String,
pub from: Option<String>,
pub to: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildSpec {
pub ecosystem: String,
pub schema_version: u32,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PlanIntent {
pub bumps: Vec<String>,
pub enable_features: Vec<String>,
pub disable_features: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Plan {
pub diff: DiffReport,
pub warnings: Vec<PlanWarning>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanWarning {
pub severity: PlanWarningSeverity,
pub message: String,
pub dep: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PlanWarningSeverity {
Info,
Warn,
Block,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfirmReport {
pub invariants_held: Vec<String>,
pub invariants_broken: Vec<InvariantBreak>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvariantBreak {
pub name: String,
pub message: String,
pub locus: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum DiffRef {
Previous,
GitRev { rev: String },
Path { path: PathBuf },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiffReport {
pub added: Vec<DepEdge>,
pub removed: Vec<DepEdge>,
pub changed: Vec<DepChange>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepEdge {
pub name: String,
pub version: String,
pub kind: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepChange {
pub name: String,
pub from_version: String,
pub to_version: String,
pub kind: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SbomFormat {
CycloneDx,
Spdx,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sbom {
pub format: SbomFormat,
pub data: serde_json::Value,
}
#[derive(Debug, thiserror::Error)]
pub enum AdapterError {
#[error("manifest not found at {0}")]
ManifestNotFound(PathBuf),
#[error("lockfile not found at {0} — run `gen lock` first")]
LockfileNotFound(PathBuf),
#[error("hermetic constraint violated: {0}")]
NonHermetic(String),
#[error("invariant broken: {name}: {message}")]
InvariantBroken { name: String, message: String },
#[error("unsupported operation: {0}")]
Unsupported(String),
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("internal: {0}")]
Internal(String),
}
pub struct AdapterRegistration {
pub make: fn() -> Box<dyn Adapter>,
pub name: &'static str,
}
inventory::collect!(AdapterRegistration);
#[must_use]
pub fn registered_adapters() -> Vec<Box<dyn Adapter>> {
inventory::iter::<AdapterRegistration>()
.map(|r| (r.make)())
.collect()
}
#[must_use]
pub fn adapter_by_name(name: &str) -> Option<Box<dyn Adapter>> {
inventory::iter::<AdapterRegistration>()
.find(|r| r.name == name)
.map(|r| (r.make)())
}
#[must_use]
pub fn registered_adapter_names() -> Vec<&'static str> {
inventory::iter::<AdapterRegistration>()
.map(|r| r.name)
.collect()
}