mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-21 14:51:49 +02:00
fix: drop serde_yaml
This commit is contained in:
1 parent
382db45803
commit
d6c21de169
5 files changed
+133
-98
No files matched your search
Generated
+17
-20
@@ -252,6 +252,12 @@ dependencies = [
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arraydeque"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
@@ -1787,7 +1793,6 @@ dependencies = [
|
||||
"security-framework",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"serial_test",
|
||||
"sha2 0.11.0",
|
||||
"shadowsocks",
|
||||
@@ -1825,6 +1830,7 @@ dependencies = [
|
||||
"windows",
|
||||
"winreg",
|
||||
"wiremock",
|
||||
"yaml-rust2",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -5920,19 +5926,6 @@ dependencies = [
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.14.2",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "4.0.1"
|
||||
@@ -7614,12 +7607,6 @@ dependencies = [
|
||||
"ctutils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -8680,6 +8667,16 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust2"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57e5b818a27a4cd30884ea380857a5e56f7ec3ba24a3990a3cc0b95af3238e18"
|
||||
dependencies = [
|
||||
"arraydeque",
|
||||
"hashlink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
|
||||
@@ -113,7 +113,7 @@ async-socks5 = "0.6"
|
||||
# Wayfern CDP integration
|
||||
tokio-tungstenite = { version = "0.30", features = ["native-tls"] }
|
||||
rusqlite = { version = "0.40", features = ["bundled"] }
|
||||
serde_yaml = "0.9"
|
||||
yaml-rust2 = { version = "0.13", default-features = false }
|
||||
thiserror = "2.0"
|
||||
regex-lite = "0.1"
|
||||
tempfile = "3"
|
||||
|
||||
@@ -32,6 +32,8 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use toml_edit::{DocumentMut, Item, Table};
|
||||
use yaml_rust2::yaml::Hash as YamlMap;
|
||||
use yaml_rust2::{Yaml, YamlEmitter, YamlLoader};
|
||||
|
||||
pub const SERVER_NAME: &str = "donut-browser";
|
||||
const REMOTE_MCP_PATH: &str = "/api/mcp";
|
||||
@@ -745,14 +747,67 @@ fn toml_item_to_json(item: &Item) -> serde_json::Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_to_yaml(entry: &Entry) -> Result<serde_yaml::Value, String> {
|
||||
let mut mapping = serde_yaml::Mapping::new();
|
||||
for (key, value) in entry {
|
||||
let yaml =
|
||||
serde_yaml::to_value(value).map_err(|e| format!("Failed to convert to YAML: {e}"))?;
|
||||
mapping.insert(serde_yaml::Value::String(key.to_string()), yaml);
|
||||
fn json_to_yaml(value: &serde_json::Value) -> Yaml {
|
||||
match value {
|
||||
serde_json::Value::Null => Yaml::Null,
|
||||
serde_json::Value::Bool(b) => Yaml::Boolean(*b),
|
||||
serde_json::Value::Number(n) => match n.as_i64() {
|
||||
Some(i) => Yaml::Integer(i),
|
||||
None => Yaml::Real(n.to_string()),
|
||||
},
|
||||
serde_json::Value::String(s) => Yaml::String(s.clone()),
|
||||
serde_json::Value::Array(items) => Yaml::Array(items.iter().map(json_to_yaml).collect()),
|
||||
serde_json::Value::Object(map) => Yaml::Hash(
|
||||
map
|
||||
.iter()
|
||||
.map(|(k, v)| (Yaml::String(k.clone()), json_to_yaml(v)))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// A YAML value as plain JSON, for inspection. Keys that are not strings and
|
||||
/// values JSON cannot hold (aliases, bad values) read as null.
|
||||
fn yaml_to_json(value: &Yaml) -> serde_json::Value {
|
||||
match value {
|
||||
Yaml::Boolean(b) => serde_json::Value::Bool(*b),
|
||||
Yaml::Integer(i) => serde_json::Value::from(*i),
|
||||
Yaml::Real(text) => text
|
||||
.parse::<f64>()
|
||||
.ok()
|
||||
.and_then(serde_json::Number::from_f64)
|
||||
.map(serde_json::Value::Number)
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
Yaml::String(s) => serde_json::Value::String(s.clone()),
|
||||
Yaml::Array(items) => serde_json::Value::Array(items.iter().map(yaml_to_json).collect()),
|
||||
Yaml::Hash(map) => serde_json::Value::Object(
|
||||
map
|
||||
.iter()
|
||||
.filter_map(|(k, v)| Some((k.as_str()?.to_string(), yaml_to_json(v))))
|
||||
.collect(),
|
||||
),
|
||||
_ => serde_json::Value::Null,
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_to_yaml(entry: &Entry) -> Yaml {
|
||||
Yaml::Hash(
|
||||
entry
|
||||
.iter()
|
||||
.map(|(key, value)| (Yaml::String(key.to_string()), json_to_yaml(value)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The mapping at `key` in a YAML mapping, if it is one.
|
||||
fn yaml_child<'a>(root: &'a mut Yaml, key: &str) -> Option<&'a mut YamlMap> {
|
||||
match root {
|
||||
Yaml::Hash(map) => match map.get_mut(&Yaml::String(key.to_string())) {
|
||||
Some(Yaml::Hash(child)) => Some(child),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
Ok(serde_yaml::Value::Mapping(mapping))
|
||||
}
|
||||
|
||||
/// A parsed config file that can be edited without disturbing what the user
|
||||
@@ -760,7 +815,7 @@ fn entry_to_yaml(entry: &Entry) -> Result<serde_yaml::Value, String> {
|
||||
enum Document {
|
||||
Json(CstRootNode),
|
||||
Toml(DocumentMut),
|
||||
Yaml(serde_yaml::Value),
|
||||
Yaml(Yaml),
|
||||
}
|
||||
|
||||
impl Document {
|
||||
@@ -771,12 +826,11 @@ impl Document {
|
||||
),
|
||||
ConfigFormat::Toml => Self::Toml(content.parse::<DocumentMut>().map_err(|e| e.to_string())?),
|
||||
ConfigFormat::Yaml => {
|
||||
let value = if content.trim().is_empty() {
|
||||
serde_yaml::Value::Null
|
||||
} else {
|
||||
serde_yaml::from_str(content).map_err(|e| e.to_string())?
|
||||
};
|
||||
Self::Yaml(value)
|
||||
let mut documents = YamlLoader::load_from_str(content).map_err(|e| e.to_string())?;
|
||||
if documents.len() > 1 {
|
||||
return Err("the file holds more than one YAML document".to_string());
|
||||
}
|
||||
Self::Yaml(documents.pop().unwrap_or(Yaml::Null))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -816,20 +870,13 @@ impl Document {
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
Self::Yaml(root) => root
|
||||
.get(key)
|
||||
.and_then(serde_yaml::Value::as_mapping)
|
||||
.map(|servers| {
|
||||
servers
|
||||
.iter()
|
||||
.filter_map(|(name, value)| {
|
||||
let name = name.as_str()?.to_string();
|
||||
let value = serde_json::to_value(value).ok()?;
|
||||
Some((name, value))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
Self::Yaml(root) => match &root[key] {
|
||||
Yaml::Hash(servers) => servers
|
||||
.iter()
|
||||
.filter_map(|(name, value)| Some((name.as_str()?.to_string(), yaml_to_json(value))))
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,24 +916,21 @@ impl Document {
|
||||
}
|
||||
Self::Yaml(root) => {
|
||||
if root.is_null() {
|
||||
*root = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
|
||||
*root = Yaml::Hash(YamlMap::new());
|
||||
}
|
||||
let root_map = root
|
||||
.as_mapping_mut()
|
||||
.ok_or("the file's top level is not a YAML mapping")?;
|
||||
let Yaml::Hash(root_map) = root else {
|
||||
return Err("the file's top level is not a YAML mapping".to_string());
|
||||
};
|
||||
let servers = root_map
|
||||
.entry(serde_yaml::Value::String(key.to_string()))
|
||||
.or_insert_with(|| serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
|
||||
.entry(Yaml::String(key.to_string()))
|
||||
.or_insert_with(|| Yaml::Hash(YamlMap::new()));
|
||||
if servers.is_null() {
|
||||
*servers = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
|
||||
*servers = Yaml::Hash(YamlMap::new());
|
||||
}
|
||||
servers
|
||||
.as_mapping_mut()
|
||||
.ok_or_else(|| format!("\"{key}\" is not a YAML mapping"))?
|
||||
.insert(
|
||||
serde_yaml::Value::String(SERVER_NAME.to_string()),
|
||||
entry_to_yaml(entry)?,
|
||||
);
|
||||
let Yaml::Hash(servers) = servers else {
|
||||
return Err(format!("\"{key}\" is not a YAML mapping"));
|
||||
};
|
||||
servers.insert(Yaml::String(SERVER_NAME.to_string()), entry_to_yaml(entry));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -911,12 +955,9 @@ impl Document {
|
||||
}
|
||||
}
|
||||
Self::Yaml(root) => {
|
||||
if let Some(servers) = root
|
||||
.get_mut(key)
|
||||
.and_then(serde_yaml::Value::as_mapping_mut)
|
||||
{
|
||||
if let Some(servers) = yaml_child(root, key) {
|
||||
for name in names {
|
||||
servers.remove(name.as_str());
|
||||
servers.remove(&Yaml::String(name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -928,7 +969,13 @@ impl Document {
|
||||
Self::Json(root) => Ok(root.to_string()),
|
||||
Self::Toml(doc) => Ok(doc.to_string()),
|
||||
Self::Yaml(root) => {
|
||||
serde_yaml::to_string(root).map_err(|e| format!("Failed to serialize YAML: {e}"))
|
||||
let mut text = String::new();
|
||||
YamlEmitter::new(&mut text)
|
||||
.dump(root)
|
||||
.map_err(|e| format!("Failed to serialize YAML: {e}"))?;
|
||||
// The emitter opens with a document marker the file never had.
|
||||
let body = text.strip_prefix("---\n").unwrap_or(&text);
|
||||
Ok(format!("{body}\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
LuTriangleAlert,
|
||||
LuUser,
|
||||
} from "react-icons/lu";
|
||||
import { AccountSyncServer } from "@/components/account-sync-server";
|
||||
import {
|
||||
formatDate,
|
||||
formatHours,
|
||||
@@ -136,12 +137,11 @@ export function AccountPage({
|
||||
useState<ConnectionStatus>("unknown");
|
||||
|
||||
const hasConfig = Boolean(serverUrl && token);
|
||||
// Self-hosted and cloud are mutually exclusive — both share the same sync
|
||||
// engine and a profile can't be sync'd to two backends. The tab trigger is
|
||||
// disabled here AND the backend rejects mixed state (see `save_sync_settings`
|
||||
// / `cloud_logout`), so even if someone bypasses the UI we don't end up
|
||||
// with split-brain.
|
||||
const selfHostedDisabled = isLoggedIn || isCloudLoading;
|
||||
// Signed out, a server takes its shared token. Signed in, the account signs
|
||||
// in to its own server instead (`AccountSyncServer`); the backend refuses a
|
||||
// shared token then (`save_sync_settings`), so one device never syncs one
|
||||
// profile to two places.
|
||||
const selfHostedDisabled = isCloudLoading;
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
@@ -358,11 +358,6 @@ export function AccountPage({
|
||||
<AnimatedTabsTrigger
|
||||
value="self-hosted"
|
||||
disabled={selfHostedDisabled}
|
||||
title={
|
||||
selfHostedDisabled
|
||||
? t("account.selfHosted.disabledWhileLoggedIn")
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{t("account.tabs.selfHosted")}
|
||||
</AnimatedTabsTrigger>
|
||||
@@ -582,16 +577,12 @@ export function AccountPage({
|
||||
value="self-hosted"
|
||||
className="@container mt-4"
|
||||
>
|
||||
{selfHostedDisabled ? (
|
||||
// Defensive: the tab trigger is disabled while the user is
|
||||
// logged in, so this branch shouldn't be reachable via UI —
|
||||
// but if state flips mid-render (e.g. a cloud login finishes
|
||||
// while the tab is open), show the explanation instead of
|
||||
// a silent empty card.
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("account.selfHosted.disabledWhileLoggedIn")}
|
||||
</p>
|
||||
) : (
|
||||
{isLoggedIn && user ? (
|
||||
<AccountSyncServer
|
||||
paid={getEntitlements(user).cloudBackup}
|
||||
teamSeat={Boolean(user.teamId)}
|
||||
/>
|
||||
) : selfHostedDisabled ? null : (
|
||||
<SettingsPanel className="flex flex-col gap-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<FeatureIcon active={connectionStatus === "connected"}>
|
||||
|
||||
@@ -451,6 +451,10 @@
|
||||
"name": "aria-hidden",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "arraydeque",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "arrayref",
|
||||
"license": "BSD-2-Clause"
|
||||
@@ -2519,10 +2523,6 @@
|
||||
"name": "serde_with_macros",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "serde_yaml",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "serialize-to-javascript",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -2983,10 +2983,6 @@
|
||||
"name": "universal-hash",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "unsafe-libyaml",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "untrusted",
|
||||
"license": "ISC"
|
||||
@@ -3263,6 +3259,10 @@
|
||||
"name": "y4m",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "yaml-rust2",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "yoke",
|
||||
"license": "Unicode-3.0"
|
||||
|
||||
Reference in new issue
Block a user