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"
|
resolver = "2"
|
||||||
# Члены растут по планам реализации. crates/core, crates/apps, crates/tools —
|
# Члены растут по планам реализации. crates/core, crates/apps, crates/tools —
|
||||||
# группировка привилегированного ядра / first-party-апов / dev-инструментов (architecture §3).
|
# группировка привилегированного ядра / first-party-апов / dev-инструментов (architecture §3).
|
||||||
members = ["crates/shturman-common", "crates/shturman-ipc"]
|
members = ["crates/shturman-common", "crates/shturman-ipc", "crates/shturman-sdk"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2021"
|
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