Skip to main content

gen_gomod/
adapter.rs

1//! `GomodAdapter` — gen-gomod's implementation of the canonical
2//! `gen_types::Adapter` trait.
3//!
4//! `build` + `confirm` are live (M1): they drive the per-package
5//! incremental encoder ([`crate::interp::apply`]) over `go list -deps
6//! -json` against a vendored tree. `lock`/`plan`/`diff`/`sbom` remain
7//! typed-`Unsupported` until their milestones land — never a silent
8//! wrong answer.
9
10use std::path::PathBuf;
11
12use gen_types::{
13    Adapter, AdapterCtx, AdapterError, AdapterResult, ConfirmReport, DiffRef, DiffReport,
14    InvariantBreak, LockOutcome, Plan, PlanIntent, Sbom, SbomFormat,
15};
16
17use crate::build_spec::{self, TargetTuple};
18use crate::interp::{self, EncodeCtx, RealGoBuildEnv};
19
20pub struct GomodAdapter;
21
22impl GomodAdapter {
23    /// Resolve the target tuple: an explicit `AdapterCtx.target` Rust
24    /// triple (mapped to `goos/goarch`) or the build host.
25    fn tuple_for(ctx: &AdapterCtx) -> TargetTuple {
26        ctx.target
27            .as_deref()
28            .and_then(TargetTuple::from_rust_triple)
29            .unwrap_or_else(TargetTuple::host)
30    }
31
32    /// Shared encode path for `build` + `confirm`.
33    fn encode(ctx: &AdapterCtx) -> AdapterResult<build_spec::BuildSpec> {
34        let manifest = ctx.workspace_root.join("go.mod");
35        if !manifest.exists() {
36            return Err(AdapterError::ManifestNotFound(manifest));
37        }
38        let ectx = EncodeCtx {
39            root: ctx.workspace_root.clone(),
40            tuple: Self::tuple_for(ctx),
41        };
42        let env = RealGoBuildEnv::default();
43        interp::apply(&env, &ectx)
44            .map_err(|e| AdapterError::Internal(format!("gomod encode: {e}")))
45    }
46}
47
48impl Adapter for GomodAdapter {
49    fn name(&self) -> &'static str {
50        "gomod"
51    }
52    fn manifest_files(&self) -> &'static [&'static str] {
53        &["go.mod"]
54    }
55
56    fn lock(&self, _ctx: &AdapterCtx) -> AdapterResult<LockOutcome> {
57        // `go mod vendor` / `go mod tidy` — the resolver invocation
58        // (network) — lands with the lock verb in a later milestone.
59        Err(AdapterError::Unsupported("gomod lock not implemented yet".into()))
60    }
61
62    fn build(&self, ctx: &AdapterCtx) -> AdapterResult<gen_types::AdapterBuildSpec> {
63        let spec = Self::encode(ctx)?;
64        let data = serde_json::to_value(&spec)
65            .map_err(|e| AdapterError::Internal(format!("serialize gomod build-spec: {e}")))?;
66        Ok(gen_types::AdapterBuildSpec {
67            ecosystem: "gomod".to_string(),
68            schema_version: build_spec::SCHEMA_VERSION,
69            data,
70        })
71    }
72
73    fn plan(&self, _ctx: &AdapterCtx, _intent: &PlanIntent) -> AdapterResult<Plan> {
74        Err(AdapterError::Unsupported("gomod plan not implemented yet".into()))
75    }
76
77    fn confirm(&self, ctx: &AdapterCtx) -> AdapterResult<ConfirmReport> {
78        let spec = Self::encode(ctx)?;
79        let mut held: Vec<String> = Vec::new();
80        let mut broken: Vec<InvariantBreak> = Vec::new();
81
82        let violations = crate::invariants::check(&spec);
83        if violations.is_empty() {
84            held.push("invariants::check".to_string());
85        } else {
86            for v in &violations {
87                let (name, locus) = crate::invariants::violation_locus(v);
88                let message = serde_json::to_string(v).unwrap_or_else(|_| format!("{v:?}"));
89                broken.push(InvariantBreak { name: name.to_string(), message, locus });
90            }
91        }
92
93        Ok(ConfirmReport { invariants_held: held, invariants_broken: broken })
94    }
95
96    fn diff(&self, _ctx: &AdapterCtx, _against: &DiffRef) -> AdapterResult<DiffReport> {
97        Err(AdapterError::Unsupported("gomod diff not implemented yet".into()))
98    }
99
100    fn sbom(&self, _ctx: &AdapterCtx, _format: SbomFormat) -> AdapterResult<Sbom> {
101        Err(AdapterError::Unsupported("gomod sbom not implemented yet".into()))
102    }
103
104    fn quirks_registry(&self) -> Vec<gen_types::AdapterQuirkEntry> {
105        use gen_types::QuirkRegistry;
106        <crate::quirks::GomodQuirks as QuirkRegistry>::registry()
107            .into_iter()
108            .map(|(p, qs)| gen_types::AdapterQuirkEntry {
109                package: p.to_string(),
110                quirks: qs.into_iter().filter_map(|q| serde_json::to_value(&q).ok()).collect(),
111            })
112            .collect()
113    }
114
115    fn dispatcher_reflection(&self) -> Vec<gen_types::DispatcherVariant> {
116        use gen_types::TypedDispatcher;
117        <crate::quirks::GomodQuirk as TypedDispatcher>::variant_fields()
118            .into_iter()
119            .map(|(kind, fields)| gen_types::DispatcherVariant {
120                kind: kind.to_string(),
121                fields: fields.into_iter().map(str::to_string).collect(),
122            })
123            .collect()
124    }
125}
126
127pub fn ctx_for(workspace_root: PathBuf) -> AdapterCtx {
128    AdapterCtx { workspace_root, target: None }
129}
130
131// Distributed-slice registration. gen-cli discovers this adapter at link
132// time via the inventory iter — no per-adapter edit to gen-cli.
133inventory::submit! {
134    gen_types::AdapterRegistration {
135        make: || Box::new(GomodAdapter),
136        name: "gomod",
137    }
138}