gen_types/ecosystem.rs
1//! Cross-ecosystem trait surface.
2//!
3//! Three traits every package-manager adapter implements to fit the
4//! pleme-io substrate's universal intake pattern. The contract is
5//! documented in `theory/ECOSYSTEM-INTAKE.md`; this module IS the
6//! Rust binding of that contract.
7//!
8//! Stack:
9//! - [`Spec`] — the typed shape of an adapter's emitted build spec
10//! - [`QuirkRegistry`] — the typed shape of known upstream-bug quirks
11//! - [`Invariants`] — the typed shape of well-formedness checks
12//!
13//! Every concrete adapter (gen-cargo today; gen-npm + gen-bundler +
14//! gen-pip + gen-helm + gen-gomod as they ship) implements all three
15//! on its own typed shapes. Substrate / cse-lint / gen confirm
16//! consume these traits without caring which ecosystem produced
17//! them.
18
19use serde::{de::DeserializeOwned, Serialize};
20
21/// The typed shape an adapter's build spec exposes to substrate +
22/// tooling consumers. Every adapter's `BuildSpec` struct implements
23/// this; the trait method surface is universal.
24///
25/// Why the associated types: each ecosystem carries its own
26/// `Args` shape (BuildRustCrateArgs for Cargo, NpmInstallArgs for
27/// npm, …) and its own `Quirk` enum. The trait doesn't constrain
28/// the shapes — substrate dispatches on the serialized JSON. What
29/// the trait DOES constrain is the universal accessor surface
30/// (schema_version / root_key / member_keys / args_for /
31/// quirks_for) so substrate's Nix dispatch is written once.
32pub trait Spec: Serialize + DeserializeOwned {
33 /// Per-package buildRustCrate-equivalent args.
34 type Args: Serialize + DeserializeOwned;
35 /// Per-package quirks variant set (typed by the adapter).
36 type Quirk: Serialize + DeserializeOwned;
37
38 /// Spec schema version. Substrate's lockfile-builder asserts
39 /// this >= a known floor; cse-lint flags stale specs.
40 fn schema_version(&self) -> u32;
41
42 /// The workspace's primary buildable package key.
43 fn root_key(&self) -> &str;
44
45 /// Every package the workspace tracks (root + members).
46 fn member_keys(&self) -> Vec<&str>;
47
48 /// Pre-shaped build args for one package, or None if the package
49 /// isn't in the spec.
50 fn args_for(&self, key: &str) -> Option<&Self::Args>;
51
52 /// Quirks registered for one package — empty when no quirks
53 /// apply, never None.
54 fn quirks_for(&self, key: &str) -> &[Self::Quirk];
55}
56
57/// The typed shape of an adapter's quirk registry. The registry is
58/// the canonical knowledge of "which upstream packages need which
59/// build-time workarounds." Each adapter ships its own typed quirk
60/// enum + a const REGISTRY table.
61///
62/// Implementations should be implementable from a TOML side-table
63/// (declarative) or a Rust constructor function — both are valid.
64/// The `QuirkRegistry` derive macro auto-implements from TOML.
65pub trait QuirkRegistry {
66 /// The ecosystem's typed quirk variant set.
67 type Quirk: Serialize + DeserializeOwned + Clone;
68
69 /// Full registry — every (package_name, quirks) pair the
70 /// adapter knows about. Sorted by package name for stable
71 /// diffs.
72 fn registry() -> Vec<(&'static str, Vec<Self::Quirk>)>;
73
74 /// Lookup quirks for a single package by name. Returns empty
75 /// Vec when no quirks registered (never None).
76 fn for_package(name: &str) -> Vec<Self::Quirk> {
77 for (k, v) in Self::registry() {
78 if k == name {
79 return v;
80 }
81 }
82 Vec::new()
83 }
84
85 /// Every package name with at least one registered quirk. Used
86 /// by invariants to detect drift between the registry and the
87 /// spec's emission.
88 fn registered_names() -> Vec<&'static str> {
89 Self::registry().into_iter().map(|(k, _)| k).collect()
90 }
91}
92
93/// The typed shape of an adapter's invariants pass. Every adapter
94/// ships a `check(spec) -> Vec<Violation>` function that verifies
95/// the spec is well-formed against its own typed rules.
96///
97/// `gen confirm` invokes this per-adapter. cse-lint can invoke it
98/// fleet-wide.
99pub trait Invariants {
100 /// The adapter's spec type — pinned to the same `Spec` impl as
101 /// the adapter's `build()` returns.
102 type Spec: Spec;
103 /// The adapter's typed violation enum. Always serializable so
104 /// `gen confirm` / cse-lint / IDE tooling can consume the
105 /// payload uniformly.
106 type Violation: Serialize + DeserializeOwned + Clone;
107
108 /// Run every invariant against the spec. Returns the violation
109 /// list (empty when the spec is valid). Pure function, no I/O,
110 /// deterministic output.
111 fn check(spec: &Self::Spec) -> Vec<Self::Violation>;
112}