feat(sdk): схема манифеста (plugin-sdk §2)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
resolver = "2"
|
||||
# Члены растут по планам реализации. crates/core, crates/apps, crates/tools —
|
||||
# группировка привилегированного ядра / first-party-апов / dev-инструментов (architecture §3).
|
||||
members = ["crates/shturman-common", "crates/shturman-ipc"]
|
||||
members = ["crates/shturman-common", "crates/shturman-ipc", "crates/shturman-sdk"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "shturman-sdk"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
zbus.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
tokio.workspace = true
|
||||
shturman-ipc = { path = "../shturman-ipc" }
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Публичный SDK Штурмана (F01): клиентские обёртки над `ipc`-proxy + схема манифеста.
|
||||
//! First-party апы и сторонние плагины строятся на нём (architecture §1, principles #9, plugin-sdk).
|
||||
|
||||
pub mod manifest;
|
||||
|
||||
pub use manifest::Manifest;
|
||||
@@ -0,0 +1,141 @@
|
||||
//! Схема манифеста плагина (plugin-sdk §2). На старте — простая форма списка интентов
|
||||
//! (i18n-ready, не i18n-now: per-locale секции — позже). Валидатор (домен F, План 5) добавляет
|
||||
//! правила поверх схемы (квоты, каталог сигналов, закрытый `ru.shturman.*`).
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Корень манифеста (`manifest.yaml`).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Manifest {
|
||||
pub plugin: Plugin,
|
||||
#[serde(default)]
|
||||
pub capabilities: Capabilities,
|
||||
#[serde(default)]
|
||||
pub extension_points: ExtensionPoints,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
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)]
|
||||
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)]
|
||||
pub struct Network {
|
||||
/// host-allowlist (намерение; строгая гарантия — forced-proxy, plugin-sdk §3).
|
||||
#[serde(default)]
|
||||
pub hosts: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
pub struct ExtensionPoints {
|
||||
#[serde(default)]
|
||||
pub tiles: Vec<Tile>,
|
||||
#[serde(default)]
|
||||
pub intents: Option<Intents>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Tile {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user