Skip to main content

spring_lsp/
schema.rs

1//! 配置 Schema 管理模块
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// 配置 Schema
7///
8/// 包含所有插件的配置定义
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ConfigSchema {
11    /// 插件配置映射,键为配置前缀
12    pub plugins: HashMap<String, PluginSchema>,
13}
14
15/// 插件 Schema
16///
17/// 单个插件的配置定义
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct PluginSchema {
20    /// 配置前缀(如 "web"、"redis" 等)
21    pub prefix: String,
22    /// 配置属性映射,键为属性名
23    pub properties: HashMap<String, PropertySchema>,
24}
25
26/// 配置属性 Schema
27///
28/// 单个配置属性的定义
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct PropertySchema {
31    /// 属性名称
32    pub name: String,
33    /// 类型信息
34    pub type_info: TypeInfo,
35    /// 属性描述
36    pub description: String,
37    /// 默认值(可选)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub default: Option<Value>,
40    /// 是否必需
41    #[serde(default)]
42    pub required: bool,
43    /// 废弃信息(可选)
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub deprecated: Option<String>,
46    /// 示例代码(可选)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub example: Option<String>,
49}
50
51/// 类型信息
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(tag = "type", rename_all = "lowercase")]
54pub enum TypeInfo {
55    /// 字符串类型
56    String {
57        /// 枚举值(可选)
58        #[serde(skip_serializing_if = "Option::is_none")]
59        enum_values: Option<Vec<String>>,
60        /// 最小长度(可选)
61        #[serde(skip_serializing_if = "Option::is_none")]
62        min_length: Option<usize>,
63        /// 最大长度(可选)
64        #[serde(skip_serializing_if = "Option::is_none")]
65        max_length: Option<usize>,
66    },
67    /// 整数类型
68    Integer {
69        /// 最小值(可选)
70        #[serde(skip_serializing_if = "Option::is_none")]
71        min: Option<i64>,
72        /// 最大值(可选)
73        #[serde(skip_serializing_if = "Option::is_none")]
74        max: Option<i64>,
75    },
76    /// 浮点数类型
77    Float {
78        /// 最小值(可选)
79        #[serde(skip_serializing_if = "Option::is_none")]
80        min: Option<f64>,
81        /// 最大值(可选)
82        #[serde(skip_serializing_if = "Option::is_none")]
83        max: Option<f64>,
84    },
85    /// 布尔类型
86    Boolean,
87    /// 数组类型
88    Array {
89        /// 元素类型
90        item_type: Box<TypeInfo>,
91    },
92    /// 对象类型(嵌套配置)
93    Object {
94        /// 嵌套属性
95        properties: HashMap<String, PropertySchema>,
96    },
97}
98
99/// 配置值
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
101#[serde(untagged)]
102pub enum Value {
103    /// 字符串值
104    String(String),
105    /// 整数值
106    Integer(i64),
107    /// 浮点数值
108    Float(f64),
109    /// 布尔值
110    Boolean(bool),
111    /// 数组值
112    Array(Vec<Value>),
113    /// 对象值
114    Table(HashMap<String, Value>),
115}
116
117/// Schema 提供者
118///
119/// 管理配置 Schema,提供配置项元数据查询和缓存
120#[derive(Clone)]
121pub struct SchemaProvider {
122    /// Schema 数据(加载后不会改变,直接拥有即可)
123    schema: ConfigSchema,
124    /// 查询缓存(使用 DashMap 提供并发安全的缓存)
125    cache: dashmap::DashMap<String, PluginSchema>,
126}
127
128impl SchemaProvider {
129    /// Schema URL
130    const SCHEMA_URL: &'static str = "https://spring-rs.github.io/config-schema.json";
131
132    /// 创建新的 Schema 提供者(使用空 Schema)
133    pub fn new() -> Self {
134        Self {
135            schema: ConfigSchema {
136                plugins: HashMap::new(),
137            },
138            cache: dashmap::DashMap::new(),
139        }
140    }
141
142    /// 从 URL 加载 Schema
143    ///
144    /// 如果加载失败,使用内置的备用 Schema
145    pub async fn load() -> anyhow::Result<Self> {
146        // 尝试从 URL 加载 Schema
147        match Self::load_from_url(Self::SCHEMA_URL).await {
148            Ok(schema) => {
149                tracing::info!("Successfully loaded schema from {}", Self::SCHEMA_URL);
150                Ok(Self {
151                    schema,
152                    cache: dashmap::DashMap::new(),
153                })
154            }
155            Err(e) => {
156                tracing::warn!("Failed to load schema from URL: {}, using fallback", e);
157                // 使用内置备用 Schema
158                Ok(Self::with_fallback_schema())
159            }
160        }
161    }
162
163    /// 从指定 URL 加载 Schema
164    async fn load_from_url(url: &str) -> anyhow::Result<ConfigSchema> {
165        let response = reqwest::get(url).await?;
166        let schema = response.json::<ConfigSchema>().await?;
167        Ok(schema)
168    }
169
170    /// 使用内置备用 Schema
171    fn with_fallback_schema() -> Self {
172        let fallback_schema = Self::create_fallback_schema();
173        Self {
174            schema: fallback_schema,
175            cache: dashmap::DashMap::new(),
176        }
177    }
178
179    /// 创建内置备用 Schema
180    ///
181    /// 包含常见的 spring-rs 插件配置
182    fn create_fallback_schema() -> ConfigSchema {
183        let mut plugins = HashMap::new();
184
185        // Web 插件配置
186        let mut web_properties = HashMap::new();
187        web_properties.insert(
188            "host".to_string(),
189            PropertySchema {
190                name: "host".to_string(),
191                type_info: TypeInfo::String {
192                    enum_values: None,
193                    min_length: None,
194                    max_length: None,
195                },
196                description: "Web server host address".to_string(),
197                default: Some(Value::String("0.0.0.0".to_string())),
198                required: false,
199                deprecated: None,
200                example: Some("host = \"0.0.0.0\"".to_string()),
201            },
202        );
203        web_properties.insert(
204            "port".to_string(),
205            PropertySchema {
206                name: "port".to_string(),
207                type_info: TypeInfo::Integer {
208                    min: Some(1),
209                    max: Some(65535),
210                },
211                description: "Web server port".to_string(),
212                default: Some(Value::Integer(8080)),
213                required: false,
214                deprecated: None,
215                example: Some("port = 8080".to_string()),
216            },
217        );
218
219        plugins.insert(
220            "web".to_string(),
221            PluginSchema {
222                prefix: "web".to_string(),
223                properties: web_properties,
224            },
225        );
226
227        // Redis 插件配置
228        let mut redis_properties = HashMap::new();
229        redis_properties.insert(
230            "url".to_string(),
231            PropertySchema {
232                name: "url".to_string(),
233                type_info: TypeInfo::String {
234                    enum_values: None,
235                    min_length: None,
236                    max_length: None,
237                },
238                description: "Redis connection URL".to_string(),
239                default: Some(Value::String("redis://localhost:6379".to_string())),
240                required: false,
241                deprecated: None,
242                example: Some("url = \"redis://localhost:6379\"".to_string()),
243            },
244        );
245
246        plugins.insert(
247            "redis".to_string(),
248            PluginSchema {
249                prefix: "redis".to_string(),
250                properties: redis_properties,
251            },
252        );
253
254        ConfigSchema { plugins }
255    }
256
257    /// 获取插件 Schema
258    ///
259    /// 使用缓存提高性能,返回克隆以避免锁竞争
260    pub fn get_plugin_schema(&self, prefix: &str) -> Option<PluginSchema> {
261        // 先查缓存(DashMap 并发安全)
262        if let Some(cached) = self.cache.get(prefix) {
263            return Some(cached.clone());
264        }
265
266        // 缓存未命中,从 schema 中查找并缓存
267        if let Some(schema) = self.schema.plugins.get(prefix) {
268            let cloned = schema.clone();
269            self.cache.insert(prefix.to_string(), cloned.clone());
270            Some(cloned)
271        } else {
272            None
273        }
274    }
275
276    /// 获取属性 Schema
277    ///
278    /// 查询指定插件的指定属性
279    pub fn get_property_schema(&self, prefix: &str, property: &str) -> Option<PropertySchema> {
280        let plugin_schema = self.get_plugin_schema(prefix)?;
281        plugin_schema.properties.get(property).cloned()
282    }
283
284    /// 获取所有配置前缀
285    ///
286    /// 返回所有已注册插件的配置前缀列表
287    pub fn get_all_prefixes(&self) -> Vec<String> {
288        self.schema.plugins.keys().cloned().collect()
289    }
290}
291
292impl Default for SchemaProvider {
293    fn default() -> Self {
294        Self::with_fallback_schema()
295    }
296}
297
298impl SchemaProvider {
299    /// 从给定的 ConfigSchema 创建 SchemaProvider(用于测试)
300    ///
301    /// 这个方法主要用于属性测试,允许使用自定义的 Schema 创建提供者
302    pub fn from_schema(schema: ConfigSchema) -> Self {
303        Self {
304            schema,
305            cache: dashmap::DashMap::new(),
306        }
307    }
308}