sync from monorepo @ 2452e92e
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
//! 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user