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 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        let preset_resolution = resolve_hub_dist_preset(
50            preset,
51            project_root,
52            source_dir,
53            projections,
54            config_path,
55            lint_strict,
56        )
57        .map_err(|e| format!("dist: preset resolve failed: {e}"))?;
58
59        let eff_projections = preset_resolution.projections.as_deref();
60        let eff_config_path = preset_resolution.config_path.as_deref();
61        let eff_lint_strict = preset_resolution.lint_strict;
62
63        // Step 1: reindex. Propagate failure immediately — gendoc is
64        // not invoked when reindex cannot produce a fresh index.
65        let reindex_json = self
66            .hub_reindex(output_path, Some(source_dir))
67            .map_err(|e| format!("dist: reindex failed: {e}"))?;
68
69        // Step 2: gendoc. On failure, surface the reindex JSON so the
70        // caller sees both the succeeded-half and the failed-half.
71        let gendoc_json = match self.hub_gendoc(
72            source_dir,
73            out_dir,
74            eff_projections,
75            eff_config_path,
76            eff_lint_strict,
77        ) {
78            Ok(json) => json,
79            Err(e) => {
80                return Err(format!(
81                    "dist: gendoc failed: {e}\nreindex result (succeeded): {reindex_json}"
82                ));
83            }
84        };
85
86        // Step 3: compose `{ reindex, gendoc }`.
87        let reindex_val: serde_json::Value = serde_json::from_str(&reindex_json)
88            .map_err(|e| format!("dist: reindex response parse: {e}"))?;
89        let gendoc_val: serde_json::Value = serde_json::from_str(&gendoc_json)
90            .map_err(|e| format!("dist: gendoc response parse: {e}"))?;
91
92        let mut composed = serde_json::json!({
93            "reindex": reindex_val,
94            "gendoc": gendoc_val,
95            "preset_catalog_version": preset_resolution.catalog_version,
96        });
97        if preset_resolution.preset_name.is_some() {
98            composed["preset"] = preset_meta_value(&preset_resolution);
99        }
100        Ok(composed.to_string())
101    }
102}