use schemars::JsonSchema;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize};
use super::de::{Located, Loose, Map, OneOf, UnknownKeys};
#[derive(Deserialize, JsonSchema)]
#[schemars(title = "DRE dependencies", deny_unknown_fields)]
pub struct DependenciesFile {
#[serde(default)]
#[schemars(schema_with = "plugins")]
pub plugins: Option<PluginsSection>,
#[serde(default)]
pub extensions: Vec<ExtensionEntry>,
pub packages: Option<Located<Loose<Vec<Loose<Package>>>>>,
}
#[derive(Clone, Deserialize, Serialize, JsonSchema)]
#[schemars(extend("additionalProperties" = true, "x-doc-other" = "Settings of the companion itself, which reads and checks them; see its documentation."))]
pub struct ExtensionEntry {
pub name: String,
pub version: String,
#[serde(flatten)]
#[schemars(skip)]
pub settings: std::collections::BTreeMap<String, serde_json::Value>,
}
#[derive(Deserialize)]
pub struct PluginsKey {
pub plugins: Option<PluginsSection>,
}
pub type PluginsSection = Located<Loose<OneOf<Vec<Located<Loose<PluginItem>>>, Map<serde_json::Value>>>>;
pub type PluginItem = OneOf<PackageName, OneOf<PinnedVersion, PluginEntry>>;
#[derive(Deserialize, JsonSchema)]
#[serde(transparent)]
#[schemars(inline)]
pub struct PackageName(pub String);
#[derive(JsonSchema)]
#[schemars(inline, extend("additionalProperties" = true, "minProperties" = 1, "maxProperties" = 1))]
pub struct PinnedVersion {
#[schemars(skip)]
pub name: Located<String>,
#[schemars(skip)]
pub version: serde_json::Value,
}
impl<'de> Deserialize<'de> for PinnedVersion {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let m = Map::<serde_json::Value>::deserialize(d)?;
match <[_; 1]>::try_from(m.0) {
Ok([(name, version)]) if name.value != "name" => Ok(PinnedVersion { name, version }),
_ => Err(serde::de::Error::custom("not a single `name: version` pair")),
}
}
}
#[derive(Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields, inline)]
pub struct PluginEntry {
#[schemars(with = "String")]
pub name: Loose<String>,
#[serde(default)]
#[schemars(with = "String")]
pub version: Option<serde_json::Value>,
#[serde(default)]
#[schemars(with = "String")]
pub github: Option<serde_json::Value>,
#[serde(default)]
#[schemars(with = "String")]
pub local: Option<serde_json::Value>,
#[serde(default)]
#[schemars(with = "String")]
pub registry: Option<serde_json::Value>,
#[serde(rename = "$unknown", default)]
#[schemars(skip)]
pub unknown: UnknownKeys,
}
#[derive(Deserialize, JsonSchema)]
#[schemars(rename = "package", deny_unknown_fields)]
#[schemars(extend("oneOf" = [{"required": ["git", "revision"]}, {"required": ["local"]}]))]
pub struct Package {
pub git: Option<Loose<String>>,
pub revision: Option<Loose<String>>,
pub local: Option<Loose<String>>,
#[schemars(skip)]
pub package: Option<Loose<String>>,
}
#[derive(JsonSchema)]
#[schemars(inline, extend("additionalProperties" = true))]
#[allow(dead_code)]
struct VersionMap {}
#[derive(JsonSchema)]
#[schemars(inline, extend("x-doc-type" = "list of plugin packages: a name, `name: \"<version>\"`, or a map (see below)"))]
#[allow(dead_code)]
struct Plugins(OneOf<Vec<Item>, OneOf<VersionMap, ()>>);
#[derive(JsonSchema)]
#[schemars(inline)]
#[allow(dead_code)]
struct Item(PluginItem);
pub(super) fn plugins(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
g.subschema_for::<Plugins>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extension_entries_keep_settings_dre_does_not_own() {
let node = super::super::node::parse(
"extensions:\n - name: dre-mcp\n version: '0.1.0'\n accept_license: '2026-10'\n team: {seats: 5}\npackages:\n - local: pkg\n",
)
.unwrap();
let file: DependenciesFile = super::super::de::from_node(&node).unwrap();
let entry = &file.extensions[0];
assert_eq!(
(entry.name.as_str(), entry.version.as_str()),
("dre-mcp", "0.1.0")
);
assert_eq!(entry.settings["accept_license"], "2026-10");
assert_eq!(entry.settings["team"]["seats"], 5);
assert!(file.packages.is_some());
}
}