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