dre_core/config/
dependencies.rs1use schemars::JsonSchema;
5use serde::de::Deserializer;
6use serde::{Deserialize, Serialize};
7
8use super::de::{Located, Loose, Map, OneOf, UnknownKeys};
9
10#[derive(Deserialize, JsonSchema)]
12#[schemars(title = "DRE dependencies", deny_unknown_fields)]
13pub struct DependenciesFile {
14 #[serde(default)]
15 #[schemars(schema_with = "plugins")]
16 pub plugins: Option<PluginsSection>,
17 #[serde(default)]
19 pub extensions: Vec<ExtensionEntry>,
20 pub packages: Option<Located<Loose<Vec<Loose<Package>>>>>,
22}
23
24#[derive(Clone, Deserialize, Serialize, JsonSchema)]
26#[schemars(extend("additionalProperties" = true, "x-doc-other" = "Settings of the companion itself, which reads and checks them; see its documentation."))]
27pub struct ExtensionEntry {
28 pub name: String,
30 pub version: String,
32 #[serde(flatten)]
34 #[schemars(skip)]
35 pub settings: std::collections::BTreeMap<String, serde_json::Value>,
36}
37
38#[derive(Deserialize)]
40pub struct PluginsKey {
41 pub plugins: Option<PluginsSection>,
42}
43
44pub type PluginsSection = Located<Loose<OneOf<Vec<Located<Loose<PluginItem>>>, Map<serde_json::Value>>>>;
46
47pub type PluginItem = OneOf<PackageName, OneOf<PinnedVersion, PluginEntry>>;
49
50#[derive(Deserialize, JsonSchema)]
52#[serde(transparent)]
53#[schemars(inline)]
54pub struct PackageName(pub String);
55
56#[derive(JsonSchema)]
58#[schemars(inline, extend("additionalProperties" = true, "minProperties" = 1, "maxProperties" = 1))]
59pub struct PinnedVersion {
60 #[schemars(skip)]
61 pub name: Located<String>,
62 #[schemars(skip)]
63 pub version: serde_json::Value,
64}
65
66impl<'de> Deserialize<'de> for PinnedVersion {
67 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
69 let m = Map::<serde_json::Value>::deserialize(d)?;
70 match <[_; 1]>::try_from(m.0) {
71 Ok([(name, version)]) if name.value != "name" => Ok(PinnedVersion { name, version }),
72 _ => Err(serde::de::Error::custom("not a single `name: version` pair")),
73 }
74 }
75}
76
77#[derive(Deserialize, JsonSchema)]
79#[schemars(deny_unknown_fields, inline)]
80pub struct PluginEntry {
81 #[schemars(with = "String")]
83 pub name: Loose<String>,
84 #[serde(default)]
86 #[schemars(with = "String")]
87 pub version: Option<serde_json::Value>,
88 #[serde(default)]
90 #[schemars(with = "String")]
91 pub github: Option<serde_json::Value>,
92 #[serde(default)]
94 #[schemars(with = "String")]
95 pub local: Option<serde_json::Value>,
96 #[serde(default)]
98 #[schemars(with = "String")]
99 pub registry: Option<serde_json::Value>,
100 #[serde(rename = "$unknown", default)]
101 #[schemars(skip)]
102 pub unknown: UnknownKeys,
103}
104
105#[derive(Deserialize, JsonSchema)]
107#[schemars(rename = "package", deny_unknown_fields)]
108#[schemars(extend("oneOf" = [{"required": ["git", "revision"]}, {"required": ["local"]}]))]
109pub struct Package {
110 pub git: Option<Loose<String>>,
112 pub revision: Option<Loose<String>>,
114 pub local: Option<Loose<String>>,
116 #[schemars(skip)]
118 pub package: Option<Loose<String>>,
119}
120
121#[derive(JsonSchema)]
123#[schemars(inline, extend("additionalProperties" = true))]
124#[allow(dead_code)]
125struct VersionMap {}
126
127#[derive(JsonSchema)]
129#[schemars(inline, extend("x-doc-type" = "list of plugin packages: a name, `name: \"<version>\"`, or a map (see below)"))]
130#[allow(dead_code)]
131struct Plugins(OneOf<Vec<Item>, OneOf<VersionMap, ()>>);
132
133#[derive(JsonSchema)]
135#[schemars(inline)]
136#[allow(dead_code)]
137struct Item(PluginItem);
138
139pub(super) fn plugins(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
141 g.subschema_for::<Plugins>()
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn extension_entries_keep_settings_dre_does_not_own() {
150 let node = super::super::node::parse(
151 "extensions:\n - name: dre-mcp\n version: '0.1.0'\n accept_license: '2026-10'\n team: {seats: 5}\npackages:\n - local: pkg\n",
152 )
153 .unwrap();
154 let file: DependenciesFile = super::super::de::from_node(&node).unwrap();
155 let entry = &file.extensions[0];
156 assert_eq!(
157 (entry.name.as_str(), entry.version.as_str()),
158 ("dre-mcp", "0.1.0")
159 );
160 assert_eq!(entry.settings["accept_license"], "2026-10");
161 assert_eq!(entry.settings["team"]["seats"], 5);
162 assert!(file.packages.is_some());
163 }
164}