Files
shturman/crates/shturman-sdk/src/manifest.rs
T
kk0t9 69beaad596 fix(review): manifest deny_unknown_fields + каталог сигналов в sdk::vehicle
- deny_unknown_fields: опечатка в ключе capability (граница доверия F §3) не проглатывается.
- VEHICLE_SIGNALS → shturman_sdk::vehicle (single source; валидатор берёт оттуда).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Alexander <akotenev2003@gmail.com>
2026-06-24 14:36:50 +03:00

164 lines
4.6 KiB
Rust

//! Схема манифеста плагина (plugin-sdk §2). На старте — простая форма списка интентов
//! (i18n-ready, не i18n-now: per-locale секции — позже). Валидатор (домен F, План 5) добавляет
//! правила поверх схемы (квоты, каталог сигналов, закрытый `ru.shturman.*`).
use serde::Deserialize;
/// Корень манифеста (`manifest.yaml`).
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Manifest {
pub plugin: Plugin,
#[serde(default)]
pub capabilities: Capabilities,
#[serde(default)]
pub extension_points: ExtensionPoints,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Plugin {
/// reverse-DNS id; `ru.shturman.*` закрыт за first-party (проверяет валидатор, F §3).
pub id: String,
pub name: String,
pub version: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub author: String,
/// Мажорная версия API (совместимость, plugin-sdk §7).
pub shturman_api: String,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Capabilities {
/// Только сигналы из каталога data-model (проверяет валидатор).
#[serde(default)]
pub vehicle_read: Vec<String>,
#[serde(default)]
pub assistant_intents: Vec<String>,
#[serde(default)]
pub ui_tiles: u32,
#[serde(default)]
pub ui_screens: u32,
#[serde(default)]
pub storage: bool,
#[serde(default)]
pub network: Option<Network>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Network {
/// host-allowlist (намерение; строгая гарантия — forced-proxy, plugin-sdk §3).
#[serde(default)]
pub hosts: Vec<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExtensionPoints {
#[serde(default)]
pub tiles: Vec<Tile>,
#[serde(default)]
pub intents: Option<Intents>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Tile {
pub id: String,
#[serde(default)]
pub title: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Intents {
/// Путь объекта `IntentHandler` (все фразы → сюда, plugin-sdk §2).
pub handler: String,
}
impl Manifest {
/// Распарсить манифест из YAML (schema-уровень; правила — у валидатора).
pub fn from_yaml(s: &str) -> Result<Self, serde_yaml::Error> {
serde_yaml::from_str(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
const GOOD: &str = r#"
plugin:
id: dev.example.fuel-tracker
name: "Учёт расхода"
version: 0.1.0
description: "Пробег и средний расход"
author: "Имя"
shturman_api: "1"
capabilities:
vehicle_read: [speed, maf, fuel_level, fuel_rate]
assistant_intents:
- "сколько я проехал"
- "средний расход"
ui_tiles: 1
ui_screens: 1
storage: true
extension_points:
tiles:
- id: consumption
title: "Расход"
intents:
handler: /dev/example/fuel_tracker/intents
"#;
#[test]
fn parses_valid_manifest() {
let m = Manifest::from_yaml(GOOD).unwrap();
assert_eq!(m.plugin.id, "dev.example.fuel-tracker");
assert_eq!(m.plugin.shturman_api, "1");
assert_eq!(
m.capabilities.vehicle_read,
vec!["speed", "maf", "fuel_level", "fuel_rate"]
);
assert_eq!(m.capabilities.ui_tiles, 1);
assert!(m.capabilities.storage);
assert_eq!(m.extension_points.tiles.len(), 1);
assert_eq!(m.extension_points.tiles[0].id, "consumption");
}
#[test]
fn rejects_missing_required_id() {
let bad = r#"
plugin:
name: "x"
version: 0.1.0
shturman_api: "1"
"#;
assert!(Manifest::from_yaml(bad).is_err());
}
#[test]
fn rejects_malformed_yaml() {
assert!(Manifest::from_yaml(": : not yaml : :").is_err());
}
#[test]
fn rejects_unknown_field() {
// опечатка в ключе capability на границе доверия (F §3) не должна молча проглатываться
let bad = r#"
plugin:
id: dev.example.x
name: "x"
version: 0.1.0
shturman_api: "1"
capabilities:
vehicl_read: [speed]
"#;
assert!(Manifest::from_yaml(bad).is_err());
}
}