use std::{path::Path, sync::Arc};
use sqry_core::graph::CodeGraph;
use crate::error::DaemonError;
pub trait WorkspaceBuilder: Send + Sync + std::fmt::Debug {
fn build(&self, workspace_root: &Path) -> Result<CodeGraph, DaemonError>;
}
impl<T: WorkspaceBuilder + ?Sized> WorkspaceBuilder for Arc<T> {
fn build(&self, workspace_root: &Path) -> Result<CodeGraph, DaemonError> {
(**self).build(workspace_root)
}
}
#[doc(hidden)]
#[derive(Debug, Default, Clone, Copy)]
pub struct EmptyGraphBuilder;
impl WorkspaceBuilder for EmptyGraphBuilder {
fn build(&self, _workspace_root: &Path) -> Result<CodeGraph, DaemonError> {
Ok(CodeGraph::new())
}
}
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct FailingGraphBuilder {
pub reason: String,
}
impl FailingGraphBuilder {
#[must_use]
pub fn new(reason: impl Into<String>) -> Self {
Self {
reason: reason.into(),
}
}
}
impl WorkspaceBuilder for FailingGraphBuilder {
fn build(&self, workspace_root: &Path) -> Result<CodeGraph, DaemonError> {
Err(DaemonError::WorkspaceBuildFailed {
root: workspace_root.to_path_buf(),
reason: self.reason.clone(),
})
}
}
pub struct RealWorkspaceBuilder {
plugins: Arc<sqry_core::plugin::PluginManager>,
build_config: sqry_core::graph::unified::build::BuildConfig,
}
impl std::fmt::Debug for RealWorkspaceBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RealWorkspaceBuilder")
.field(
"plugins",
&format_args!("<PluginManager@{:p}>", Arc::as_ptr(&self.plugins)),
)
.field("build_config", &self.build_config)
.finish()
}
}
impl RealWorkspaceBuilder {
#[must_use]
pub fn new(plugins: Arc<sqry_core::plugin::PluginManager>) -> Self {
Self {
plugins,
build_config: sqry_core::graph::unified::build::BuildConfig::default(),
}
}
#[must_use]
pub fn with_build_config(
plugins: Arc<sqry_core::plugin::PluginManager>,
build_config: sqry_core::graph::unified::build::BuildConfig,
) -> Self {
Self {
plugins,
build_config,
}
}
}
impl WorkspaceBuilder for RealWorkspaceBuilder {
fn build(&self, workspace_root: &Path) -> Result<CodeGraph, DaemonError> {
sqry_core::graph::unified::build::build_unified_graph(
workspace_root,
&self.plugins,
&self.build_config,
)
.map_err(|e| DaemonError::WorkspaceBuildFailed {
root: workspace_root.to_path_buf(),
reason: e.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_builder_returns_fresh_graph() {
let b = EmptyGraphBuilder;
let g = b.build(Path::new("/repos/example")).expect("always ok");
assert_eq!(g.node_count(), 0);
}
#[test]
fn failing_builder_surfaces_reason_and_root() {
let b = FailingGraphBuilder::new("plugin panic");
let err = b
.build(Path::new("/repos/example"))
.expect_err("always fails");
match err {
DaemonError::WorkspaceBuildFailed { root, reason } => {
assert_eq!(root, Path::new("/repos/example"));
assert_eq!(reason, "plugin panic");
}
other => panic!("wrong variant: {other:?}"),
}
}
#[test]
fn arc_builder_passes_through_to_inner() {
let inner: Arc<dyn WorkspaceBuilder> = Arc::new(EmptyGraphBuilder);
let g = inner
.build(Path::new("/repos/example"))
.expect("arc-wrapped builder delegates");
assert_eq!(g.node_count(), 0);
}
}