feat: GPUI 版补齐 MQTTX 基础功能对标(分组/SSL证书/订阅增强/消息流控制/设置与日志)
This commit is contained in:
+102
-3
@@ -49,9 +49,9 @@ impl Storage {
|
||||
}
|
||||
let mut text = std::fs::read_to_string(&p)
|
||||
.with_context(|| format!("读取 {} 失败", p.display()))?;
|
||||
// 容忍 Windows 编辑器/PowerShell 写入的 UTF-8 BOM
|
||||
if text.starts_with('\u{feff}') {
|
||||
text.drain(..3);
|
||||
// 容忍 Windows 编辑器/PowerShell 写入的 UTF-8 BOM(BOM 只有 1 个字符,逐字符剥离)
|
||||
if let Some(rest) = text.strip_prefix('\u{feff}') {
|
||||
text = rest.to_string();
|
||||
}
|
||||
if text.trim().is_empty() {
|
||||
return Ok(None);
|
||||
@@ -164,3 +164,102 @@ impl Default for Storage {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// ── 连接导入 / 导出(Phase 2 UI 调用) ──────────────────────────────────────
|
||||
|
||||
/// 导出连接为 JSON 数组(与 connections.json 同构:缩进格式化、UTF-8 无 BOM)。
|
||||
pub fn export_connections_to(path: &Path, conns: &[ConnectionConfig]) -> Result<(), String> {
|
||||
let text = serde_json::to_string_pretty(conns)
|
||||
.map_err(|e| format!("序列化连接失败: {e}"))?;
|
||||
std::fs::write(path, text).map_err(|e| format!("写入 {} 失败: {e}", path.display()))
|
||||
}
|
||||
|
||||
/// 从 JSON 文件导入连接。
|
||||
///
|
||||
/// 容错规则:容忍 UTF-8 BOM;顶层允许是数组,也可能是 `{ "connections": [...] }` 对象;
|
||||
/// 任一项不是合法连接配置时返回带序号的错误信息。
|
||||
pub fn import_connections_from(path: &Path) -> Result<Vec<ConnectionConfig>, String> {
|
||||
let bytes = std::fs::read(path).map_err(|e| format!("读取 {} 失败: {e}", path.display()))?;
|
||||
let mut text =
|
||||
String::from_utf8(bytes).map_err(|_| format!("{} 不是有效的 UTF-8 文件", path.display()))?;
|
||||
if let Some(rest) = text.strip_prefix('\u{feff}') {
|
||||
text = rest.to_string();
|
||||
}
|
||||
let value: serde_json::Value =
|
||||
serde_json::from_str(text.trim()).map_err(|e| format!("解析 JSON 失败: {e}"))?;
|
||||
let items = match value {
|
||||
serde_json::Value::Array(items) => items,
|
||||
serde_json::Value::Object(mut map) => match map.remove("connections") {
|
||||
Some(serde_json::Value::Array(items)) => items,
|
||||
_ => {
|
||||
return Err(
|
||||
"JSON 对象中缺少 \"connections\" 数组字段".to_string(),
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => return Err("JSON 顶层应为连接数组".to_string()),
|
||||
};
|
||||
let mut conns = Vec::with_capacity(items.len());
|
||||
for (i, item) in items.into_iter().enumerate() {
|
||||
match serde_json::from_value::<ConnectionConfig>(item) {
|
||||
Ok(c) => conns.push(c),
|
||||
Err(e) => return Err(format!("第 {} 项不是合法的连接配置: {e}", i + 1)),
|
||||
}
|
||||
}
|
||||
Ok(conns)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn temp_path(name: &str) -> PathBuf {
|
||||
std::env::temp_dir().join(format!(
|
||||
"mqttx-store-test-{}-{name}",
|
||||
uuid::Uuid::new_v4().simple()
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_import_roundtrip() {
|
||||
let path = temp_path("roundtrip.json");
|
||||
let mut c = ConnectionConfig::new();
|
||||
c.group = Some("测试组".into());
|
||||
c.connection_timeout_secs = 25;
|
||||
c.max_reconnect_times = 5;
|
||||
export_connections_to(&path, std::slice::from_ref(&c)).expect("导出应成功");
|
||||
let back = import_connections_from(&path).expect("导入应成功");
|
||||
assert_eq!(back.len(), 1);
|
||||
assert_eq!(back[0].id, c.id);
|
||||
assert_eq!(back[0].group.as_deref(), Some("测试组"));
|
||||
assert_eq!(back[0].connection_timeout_secs, 25);
|
||||
assert_eq!(back[0].max_reconnect_times, 5);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_tolerates_bom_and_object_form() {
|
||||
let path = temp_path("bom-object.json");
|
||||
let c = ConnectionConfig::new();
|
||||
let body = serde_json::to_string(&serde_json::json!({
|
||||
"connections": [serde_json::to_value(&c).unwrap()]
|
||||
}))
|
||||
.unwrap();
|
||||
// 写入 BOM 前缀,验证容错
|
||||
std::fs::write(&path, format!("\u{feff}{body}")).expect("写入测试文件");
|
||||
let back = import_connections_from(&path).expect("BOM + 对象形式应可导入");
|
||||
assert_eq!(back.len(), 1);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_reports_invalid_item_index() {
|
||||
let path = temp_path("invalid.json");
|
||||
let good = serde_json::to_value(ConnectionConfig::new()).unwrap();
|
||||
let body = serde_json::json!([good, {"id": "bad"}]).to_string();
|
||||
std::fs::write(&path, body).expect("写入测试文件");
|
||||
let err = import_connections_from(&path).expect_err("非法项应报错");
|
||||
assert!(err.contains("第 2 项"), "错误应定位到序号: {err}");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user