Skip to main content

algocline_app/service/
dist.rs

1//! `AppService::hub_dist` — facade that chains `hub_reindex` and
2//! `hub_gendoc` in a single call.
3//!
4//! This is the thin composition layer expected by downstream hub
5//! repositories that want to regenerate `hub_index.json` and the
6//! public documentation artifacts in one shot. It performs no
7//! filesystem work of its own — it calls the two underlying
8//! services in sequence and assembles their JSON responses into a
9//! `{ "reindex": ..., "gendoc": ... }` envelope.
10//!
11//! Error propagation (per `CLAUDE.md §Service 層 Error 伝播規律`):
12//!
13//! - If `hub_reindex` fails, this function returns immediately with
14//!   `Err("dist: reindex failed: {inner}")` and `hub_gendoc` is not
15//!   invoked. No `warn!`, no silent drop.
16//! - If `hub_gendoc` fails after a successful reindex, the returned
17//!   `Err` text embeds the (already-succeeded) reindex JSON so the
18//!   caller can observe both outcomes in a single response:
19//!   `Err("dist: gendoc failed: {inner}\nreindex result (succeeded): {json}")`.
20//!   The reindex-side side effect (the written `hub_index.json`) is
21//!   not rolled back — callers must treat it as authoritative after
22//!   a failed gendoc.
23//! - Any JSON parse failure on the underlying responses is also
24//!   propagated with a `dist:` prefix.
25
26use super::hub_dist_preset::{preset_meta_value, resolve_hub_dist_preset};
27use super::AppService;
28
29impl AppService {
30    /// Run `hub_reindex` followed by `hub_gendoc` as a single call.
31    ///
32    /// See the module-level doc comment for error semantics. `source_dir`
33    /// is forwarded to both steps; `output_path` is the reindex
34    /// `hub_index.json` destination (callers typically point this at
35    /// `{source_dir}/hub_index.json`); the remaining arguments are
36    /// forwarded to `hub_gendoc` unchanged.
37    #[allow(clippy::too_many_arguments)]
38    pub async fn hub_dist(
39        &self,
40        source_dir: &str,
41        output_path: Option<&str>,
42        out_dir: Option<&str>,
43        preset: Option<&str>,
44        project_root: Option<&str>,
45        projections: Option<&[String]>,
46        config_path: Option<&str>,
47        lint_strict: Option<bool>,
48    ) -> Result<String, String> {
49        // Honour the activated session pin (P > S > E > W) before
50        // delegating to the free helper, which only knows the legacy
51        // 3-layer chain.
52        let resolved_root = self
53            .resolve_root(project_root)
54            .map(|p| p.to_string_lossy().to_string());
55        let preset_resolution = resolve_hub_dist_preset(
56            preset,
57            resolved_root.as_deref(),
58            source_dir,
59            projections,
60            config_path,
61            lint_strict,
62        )
63        .map_err(|e| format!("dist: preset resolve failed: {e}"))?;
64
65        let eff_projections = preset_resolution.projections.as_deref();
66        let eff_config_path = preset_resolution.config_path.as_deref();
67        let eff_lint_strict = preset_resolution.lint_strict;
68
69        // Step 1: reindex. Propagate failure immediately — gendoc is
70        // not invoked when reindex cannot produce a fresh index.
71        let reindex_json = self
72            .hub_reindex(output_path, Some(source_dir))
73            .await
74            .map_err(|e| format!("dist: reindex failed: {e}"))?;
75
76        // Step 2: gendoc. On failure, surface the reindex JSON so the
77        // caller sees both the succeeded-half and the failed-half.
78        let gendoc_json = match self.hub_gendoc(
79            source_dir,
80            out_dir,
81            eff_projections,
82            eff_config_path,
83            eff_lint_strict,
84        ) {
85            Ok(json) => json,
86            Err(e) => {
87                return Err(format!(
88                    "dist: gendoc failed: {e}\nreindex result (succeeded): {reindex_json}"
89                ));
90            }
91        };
92
93        // Step 3: compose `{ reindex, gendoc }`.
94        let reindex_val: serde_json::Value = serde_json::from_str(&reindex_json)
95            .map_err(|e| format!("dist: reindex response parse: {e}"))?;
96        let gendoc_val: serde_json::Value = serde_json::from_str(&gendoc_json)
97            .map_err(|e| format!("dist: gendoc response parse: {e}"))?;
98
99        let mut composed = serde_json::json!({
100            "reindex": reindex_val,
101            "gendoc": gendoc_val,
102            "preset_catalog_version": preset_resolution.catalog_version,
103        });
104        if preset_resolution.preset_name.is_some() {
105            composed["preset"] = preset_meta_value(&preset_resolution);
106        }
107        Ok(composed.to_string())
108    }
109}