Files
dirigent/crates/dirigent_archivist/src/import/trait_def.rs
T
2026-05-08 01:59:04 +02:00

114 lines
3.5 KiB
Rust

//! Importer trait and config-shape types consumed by the UI (dynamic form
//! rendering) and the CLI (future). Scripts can serialise ImportConfig as JSON.
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use thiserror::Error;
use uuid::Uuid;
use crate::coordinator::Archivist;
use super::progress::ImportProgressSink;
#[async_trait]
pub trait Importer: Send + Sync {
fn source_name(&self) -> &'static str;
fn config_shape(&self) -> ImportConfigShape;
async fn discover(
&self,
cfg: &ImportConfig,
) -> Result<super::ImportDiscovery, ImportError>;
async fn import(
&self,
cfg: &ImportConfig,
archivist: &Archivist,
target: ImportTarget,
progress: ImportProgressSink,
) -> Result<super::ImportStats, ImportError>;
/// Attempt to auto-detect default configuration values.
///
/// Importers that can discover their source location automatically
/// (e.g., Claude Code's `~/.claude` directory) should override this.
/// Returns `None` when auto-detection is not supported or fails.
fn detect_defaults(&self) -> Option<ImportConfig> {
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImporterInfo {
pub source_name: String,
pub display_name: String,
pub config_shape: ImportConfigShape,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportConfigShape {
pub fields: Vec<ConfigField>,
pub example: ImportConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigField {
pub key: String,
pub label: String,
pub kind: ConfigFieldKind,
pub required: bool,
pub help: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum ConfigFieldKind {
Path { directory: bool },
File { extension: Option<String> },
String,
Bool,
Enum { variants: Vec<String> },
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportConfig {
pub source: String,
#[serde(default)]
pub params: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportTarget {
pub archive: Option<String>,
pub connector_alias: Option<String>,
pub project_id: Option<Uuid>,
/// Maps normalized project_path -> project_id (as string UUID).
/// When a session's project_path is found in this map, the corresponding
/// project_id is injected into the session metadata during import.
#[serde(default)]
pub project_map: HashMap<String, String>,
}
#[derive(Debug, Error)]
pub enum ImportError {
#[error("source not found: {0}")] SourceNotFound(String),
#[error("config: {0}")] Config(String),
#[error("discovery: {0}")] Discovery(String),
#[error("I/O: {0}")] Io(#[from] std::io::Error),
#[error("archivist: {0}")] Archivist(String),
#[error("parser: {0}")] Parser(String),
#[error("cancelled")] Cancelled,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_round_trips() {
let cfg = ImportConfig { source: "claude".into(), params: BTreeMap::new() };
let json = serde_json::to_string(&cfg).unwrap();
let back: ImportConfig = serde_json::from_str(&json).unwrap();
assert_eq!(back.source, "claude");
}
}