114 lines
3.5 KiB
Rust
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");
|
|
}
|
|
}
|