Skip to main content

light_fw_cli_core/
registry.rs

1use std::collections::HashMap;
2
3use serde::Deserialize;
4
5use crate::manifest::{ComponentKind, Provides};
6
7/// Встроенная таблица известных компонентов.
8///
9/// Файл `registry.toml` компилируется в бинарник через `include_str!`, поэтому
10/// CLI работает офлайн и не требует сетевого доступа.
11const REGISTRY_TOML: &str = include_str!("../registry.toml");
12
13/// Описание одного компонента из реестра.
14#[derive(Debug, Clone)]
15pub struct ComponentInfo {
16    /// Имя крейта-компонента (например, `light-fw-element-form`).
17    pub name: String,
18    /// Тип компонента.
19    pub kind: ComponentKind,
20    /// Человекочитаемое имя.
21    pub display_name: String,
22    /// Последняя известная версия.
23    pub latest_version: semver::Version,
24    /// Требование к версии ядра.
25    pub core_requirement: semver::VersionReq,
26    /// Что компонент предоставляет (теги, атрибуты, директивы).
27    pub provides: Provides,
28}
29
30impl ComponentInfo {
31    /// Строка для рекомендации CLI, например `light-fw-element-form ^0.3`.
32    pub fn requirement(&self) -> String {
33        format!("{} {}", self.name, self.core_requirement)
34    }
35}
36
37/// Реестр компонентов с индексами по элементам, атрибутам и директивам.
38pub struct ComponentRegistry {
39    components: Vec<ComponentInfo>,
40    by_name: HashMap<String, usize>,
41    by_element: HashMap<String, usize>,
42    by_attribute: HashMap<String, usize>,
43    by_directive: HashMap<String, usize>,
44}
45
46#[derive(Debug, Deserialize)]
47struct RegistryFile {
48    #[serde(default)]
49    components: Vec<RegistryEntry>,
50}
51
52#[derive(Debug, Deserialize)]
53struct RegistryEntry {
54    name: String,
55    kind: ComponentKind,
56    #[serde(rename = "display-name")]
57    display_name: String,
58    version: String,
59    core: String,
60    #[serde(default)]
61    elements: Vec<String>,
62    #[serde(default)]
63    attributes: Vec<String>,
64    #[serde(default)]
65    directives: Vec<String>,
66}
67
68impl ComponentRegistry {
69    /// Загружает встроенный реестр.
70    ///
71    /// Встроенный `registry.toml` корректен по построению; если бы он не
72    /// разбирался, это ошибка сборки, поэтому здесь допустимо ожидать
73    /// успешного разбора.
74    pub fn builtin() -> Self {
75        Self::from_toml(REGISTRY_TOML).expect("встроенный registry.toml должен разбираться")
76    }
77
78    /// Создаёт реестр из TOML-описания.
79    pub fn from_toml(text: &str) -> crate::Result<Self> {
80        let file: RegistryFile =
81            toml_edit::de::from_str(text).map_err(|e| crate::Error::Manifest(e.to_string()))?;
82        let mut registry = ComponentRegistry {
83            components: Vec::new(),
84            by_name: HashMap::new(),
85            by_element: HashMap::new(),
86            by_attribute: HashMap::new(),
87            by_directive: HashMap::new(),
88        };
89        for entry in file.components {
90            let latest_version = semver::Version::parse(&entry.version)
91                .map_err(|e| crate::Error::Manifest(format!("invalid version: {e}")))?;
92            let core_requirement = semver::VersionReq::parse(&entry.core)
93                .map_err(|e| crate::Error::Manifest(format!("invalid core range: {e}")))?;
94            let provides = Provides {
95                elements: entry.elements,
96                attributes: entry.attributes,
97                directives: entry.directives,
98            };
99            registry.insert(ComponentInfo {
100                name: entry.name,
101                kind: entry.kind,
102                display_name: entry.display_name,
103                latest_version,
104                core_requirement,
105                provides,
106            });
107        }
108        Ok(registry)
109    }
110
111    fn insert(&mut self, info: ComponentInfo) {
112        let idx = self.components.len();
113        for tag in &info.provides.elements {
114            self.by_element.insert(tag.clone(), idx);
115        }
116        for attr in &info.provides.attributes {
117            self.by_attribute.insert(attr.clone(), idx);
118        }
119        for dir in &info.provides.directives {
120            self.by_directive.insert(dir.clone(), idx);
121        }
122        self.by_name.insert(info.name.clone(), idx);
123        self.components.push(info);
124    }
125
126    /// Все компоненты, известные реестру.
127    pub fn all(&self) -> &[ComponentInfo] {
128        &self.components
129    }
130
131    /// Компонент по имени крейта.
132    pub fn by_name(&self, name: &str) -> Option<&ComponentInfo> {
133        self.by_name.get(name).map(|&i| &self.components[i])
134    }
135
136    /// Компонент, предоставляющий тег.
137    pub fn component_for_element(&self, tag: &str) -> Option<&ComponentInfo> {
138        self.by_element.get(tag).map(|&i| &self.components[i])
139    }
140
141    /// Компонент, предоставляющий атрибут.
142    pub fn component_for_attribute(&self, attr: &str) -> Option<&ComponentInfo> {
143        self.by_attribute.get(attr).map(|&i| &self.components[i])
144    }
145
146    /// Компонент, предоставляющий директиву.
147    pub fn component_for_directive(&self, dir: &str) -> Option<&ComponentInfo> {
148        self.by_directive.get(dir).map(|&i| &self.components[i])
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn builtin_registry_is_valid() {
158        let r = ComponentRegistry::builtin();
159        assert!(r.all().len() >= 2);
160        assert!(r.by_name("light-fw-element-form").is_some());
161    }
162
163    #[test]
164    fn resolves_elements() {
165        let r = ComponentRegistry::builtin();
166        let form = r.component_for_element("checkbox").expect("checkbox");
167        assert_eq!(form.name, "light-fw-element-form");
168        assert!(
169            r.component_for_element("text").is_none(),
170            "текстовые поля входят в ядро и компонента не требуют"
171        );
172    }
173
174    #[test]
175    fn resolves_attributes_and_directives() {
176        let r = ComponentRegistry::builtin();
177        assert_eq!(
178            r.component_for_attribute("required")
179                .map(|c| c.name.as_str()),
180            Some("light-fw-behavior-validation")
181        );
182        assert_eq!(
183            r.component_for_directive("@validate")
184                .map(|c| c.name.as_str()),
185            Some("light-fw-behavior-validation")
186        );
187    }
188
189    #[test]
190    fn unknown_component_is_none() {
191        let r = ComponentRegistry::builtin();
192        assert!(r.component_for_element("definitely-not-real").is_none());
193    }
194}