fix: improve JSON validation with unified validation function

- Extract common validateJsonConfig function for reuse
- Apply unified validation to both main config and common config snippets
- Add real-time JSON validation to JsonEditor component using CodeMirror linter
- Simplify error handling without over-engineering error position extraction
This commit is contained in:
Jason
2025-09-18 08:35:09 +08:00
parent 19dcc84c83
commit efff780eea
6 changed files with 195 additions and 59 deletions

View File

@@ -10,6 +10,34 @@ use crate::config::{get_claude_settings_path, ConfigStatus};
use crate::provider::Provider; use crate::provider::Provider;
use crate::store::AppState; use crate::store::AppState;
fn validate_provider_settings(app_type: &AppType, provider: &Provider) -> Result<(), String> {
match app_type {
AppType::Claude => {
if !provider.settings_config.is_object() {
return Err("Claude 配置必须是 JSON 对象".to_string());
}
}
AppType::Codex => {
let settings = provider
.settings_config
.as_object()
.ok_or_else(|| "Codex 配置必须是 JSON 对象".to_string())?;
let auth = settings
.get("auth")
.ok_or_else(|| "Codex 配置缺少 auth 字段".to_string())?;
if !auth.is_object() {
return Err("Codex auth 配置必须是 JSON 对象".to_string());
}
if let Some(config_value) = settings.get("config") {
if !(config_value.is_string() || config_value.is_null()) {
return Err("Codex config 字段必须是字符串".to_string());
}
}
}
}
Ok(())
}
/// 获取所有供应商 /// 获取所有供应商
#[tauri::command] #[tauri::command]
pub async fn get_providers( pub async fn get_providers(
@@ -74,6 +102,8 @@ pub async fn add_provider(
.or_else(|| appType.as_deref().map(|s| s.into())) .or_else(|| appType.as_deref().map(|s| s.into()))
.unwrap_or(AppType::Claude); .unwrap_or(AppType::Claude);
validate_provider_settings(&app_type, &provider)?;
// 读取当前是否是激活供应商(短锁) // 读取当前是否是激活供应商(短锁)
let is_current = { let is_current = {
let config = state let config = state
@@ -139,6 +169,8 @@ pub async fn update_provider(
.or_else(|| appType.as_deref().map(|s| s.into())) .or_else(|| appType.as_deref().map(|s| s.into()))
.unwrap_or(AppType::Claude); .unwrap_or(AppType::Claude);
validate_provider_settings(&app_type, &provider)?;
// 读取校验 & 是否当前(短锁) // 读取校验 & 是否当前(短锁)
let (exists, is_current) = { let (exists, is_current) = {
let config = state let config = state

View File

@@ -1,9 +1,10 @@
import React, { useRef, useEffect } from "react"; import React, { useRef, useEffect, useMemo } from "react";
import { EditorView, basicSetup } from "codemirror"; import { EditorView, basicSetup } from "codemirror";
import { json } from "@codemirror/lang-json"; import { json } from "@codemirror/lang-json";
import { oneDark } from "@codemirror/theme-one-dark"; import { oneDark } from "@codemirror/theme-one-dark";
import { EditorState } from "@codemirror/state"; import { EditorState } from "@codemirror/state";
import { placeholder } from "@codemirror/view"; import { placeholder } from "@codemirror/view";
import { linter, Diagnostic } from "@codemirror/lint";
interface JsonEditorProps { interface JsonEditorProps {
value: string; value: string;
@@ -11,6 +12,7 @@ interface JsonEditorProps {
placeholder?: string; placeholder?: string;
darkMode?: boolean; darkMode?: boolean;
rows?: number; rows?: number;
showValidation?: boolean;
} }
const JsonEditor: React.FC<JsonEditorProps> = ({ const JsonEditor: React.FC<JsonEditorProps> = ({
@@ -19,10 +21,50 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
placeholder: placeholderText = "", placeholder: placeholderText = "",
darkMode = false, darkMode = false,
rows = 12, rows = 12,
showValidation = true,
}) => { }) => {
const editorRef = useRef<HTMLDivElement>(null); const editorRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null); const viewRef = useRef<EditorView | null>(null);
// JSON linter 函数
const jsonLinter = useMemo(
() =>
linter((view) => {
const diagnostics: Diagnostic[] = [];
if (!showValidation) return diagnostics;
const doc = view.state.doc.toString();
if (!doc.trim()) return diagnostics;
try {
const parsed = JSON.parse(doc);
// 检查是否是JSON对象
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
// 格式正确
} else {
diagnostics.push({
from: 0,
to: doc.length,
severity: "error",
message: "配置必须是JSON对象不能是数组或其他类型",
});
}
} catch (e) {
// 简单处理JSON解析错误
const message = e instanceof SyntaxError ? e.message : "JSON格式错误";
diagnostics.push({
from: 0,
to: doc.length,
severity: "error",
message,
});
}
return diagnostics;
}),
[showValidation]
);
useEffect(() => { useEffect(() => {
if (!editorRef.current) return; if (!editorRef.current) return;
@@ -43,6 +85,7 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
json(), json(),
placeholder(placeholderText || ""), placeholder(placeholderText || ""),
sizingTheme, sizingTheme,
jsonLinter,
EditorView.updateListener.of((update) => { EditorView.updateListener.of((update) => {
if (update.docChanged) { if (update.docChanged) {
const newValue = update.state.doc.toString(); const newValue = update.state.doc.toString();
@@ -75,7 +118,7 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
view.destroy(); view.destroy();
viewRef.current = null; viewRef.current = null;
}; };
}, [darkMode, rows]); // 依赖项中不包含 onChange 和 placeholder避免不必要的重建 }, [darkMode, rows, jsonLinter]); // 依赖项中不包含 onChange 和 placeholder避免不必要的重建
// 当 value 从外部改变时更新编辑器内容 // 当 value 从外部改变时更新编辑器内容
useEffect(() => { useEffect(() => {

View File

@@ -9,6 +9,7 @@ import {
setApiKeyInConfig, setApiKeyInConfig,
updateTomlCommonConfigSnippet, updateTomlCommonConfigSnippet,
hasTomlCommonConfigSnippet, hasTomlCommonConfigSnippet,
validateJsonConfig,
} from "../utils/providerConfigUtils"; } from "../utils/providerConfigUtils";
import { providerPresets } from "../config/providerPresets"; import { providerPresets } from "../config/providerPresets";
import { codexProviderPresets } from "../config/codexProviderPresets"; import { codexProviderPresets } from "../config/codexProviderPresets";
@@ -77,6 +78,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
const setCodexAuth = (value: string) => { const setCodexAuth = (value: string) => {
setCodexAuthState(value); setCodexAuthState(value);
setCodexAuthError(validateCodexAuth(value));
}; };
const setCodexConfig = (value: string) => { const setCodexConfig = (value: string) => {
@@ -123,6 +125,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
return DEFAULT_COMMON_CONFIG_SNIPPET; return DEFAULT_COMMON_CONFIG_SNIPPET;
}); });
const [commonConfigError, setCommonConfigError] = useState(""); const [commonConfigError, setCommonConfigError] = useState("");
const [settingsConfigError, setSettingsConfigError] = useState("");
// 用于跟踪是否正在通过通用配置更新 // 用于跟踪是否正在通过通用配置更新
const isUpdatingFromCommonConfig = useRef(false); const isUpdatingFromCommonConfig = useRef(false);
@@ -151,12 +154,40 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
showPresets ? -1 : null, showPresets ? -1 : null,
); );
const [apiKey, setApiKey] = useState(""); const [apiKey, setApiKey] = useState("");
const [codexAuthError, setCodexAuthError] = useState("");
// Kimi 模型选择状态 // Kimi 模型选择状态
const [kimiAnthropicModel, setKimiAnthropicModel] = useState(""); const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] = const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] =
useState(""); useState("");
const validateSettingsConfig = (value: string): string => {
return validateJsonConfig(value, "配置内容");
};
const validateCodexAuth = (value: string): string => {
if (!value.trim()) {
return "";
}
try {
const parsed = JSON.parse(value);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return "auth.json 必须是 JSON 对象";
}
return "";
} catch {
return "auth.json 格式错误请检查JSON语法";
}
};
const updateSettingsConfigValue = (value: string) => {
setFormData((prev) => ({
...prev,
settingsConfig: value,
}));
setSettingsConfigError(validateSettingsConfig(value));
};
// 初始化自定义模式的默认配置 // 初始化自定义模式的默认配置
useEffect(() => { useEffect(() => {
if ( if (
@@ -175,11 +206,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name" // ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
}, },
}; };
const templateString = JSON.stringify(customTemplate, null, 2);
setFormData((prev) => ({ updateSettingsConfigValue(templateString);
...prev,
settingsConfig: JSON.stringify(customTemplate, null, 2),
}));
setApiKey(""); setApiKey("");
} }
}, []); // 只在组件挂载时执行一次 }, []); // 只在组件挂载时执行一次
@@ -194,6 +223,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
commonConfigSnippet, commonConfigSnippet,
); );
setUseCommonConfig(hasCommon); setUseCommonConfig(hasCommon);
setSettingsConfigError(validateSettingsConfig(configString));
// 初始化模型配置(编辑模式) // 初始化模型配置(编辑模式)
if ( if (
@@ -279,6 +309,12 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
let settingsConfig: Record<string, any>; let settingsConfig: Record<string, any>;
if (isCodex) { if (isCodex) {
const currentAuthError = validateCodexAuth(codexAuth);
setCodexAuthError(currentAuthError);
if (currentAuthError) {
setError(currentAuthError);
return;
}
// Codex: 仅要求 auth.json 必填config.toml 可为空 // Codex: 仅要求 auth.json 必填config.toml 可为空
if (!codexAuth.trim()) { if (!codexAuth.trim()) {
setError("请填写 auth.json 配置"); setError("请填写 auth.json 配置");
@@ -313,6 +349,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
return; return;
} }
} else { } else {
const currentSettingsError = validateSettingsConfig(
formData.settingsConfig,
);
setSettingsConfigError(currentSettingsError);
if (currentSettingsError) {
setError(currentSettingsError);
return;
}
// Claude: 原有逻辑 // Claude: 原有逻辑
if (!formData.settingsConfig.trim()) { if (!formData.settingsConfig.trim()) {
setError("请填写配置内容"); setError("请填写配置内容");
@@ -353,10 +397,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setApiKey(parsedKey); setApiKey(parsedKey);
// 不再从 JSON 自动提取或覆盖官网地址,只更新配置内容 // 不再从 JSON 自动提取或覆盖官网地址,只更新配置内容
setFormData((prev) => ({ updateSettingsConfigValue(value);
...prev,
[name]: value,
}));
} else { } else {
setFormData({ setFormData({
...formData, ...formData,
@@ -375,6 +416,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
if (snippetError) { if (snippetError) {
setCommonConfigError(snippetError); setCommonConfigError(snippetError);
if (snippetError.includes("配置 JSON 解析失败")) {
setSettingsConfigError("配置JSON格式错误请检查语法");
}
setUseCommonConfig(false); setUseCommonConfig(false);
return; return;
} }
@@ -383,10 +427,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setUseCommonConfig(checked); setUseCommonConfig(checked);
// 标记正在通过通用配置更新 // 标记正在通过通用配置更新
isUpdatingFromCommonConfig.current = true; isUpdatingFromCommonConfig.current = true;
setFormData((prev) => ({ updateSettingsConfigValue(updatedConfig);
...prev,
settingsConfig: updatedConfig,
}));
// 在下一个事件循环中重置标记 // 在下一个事件循环中重置标记
setTimeout(() => { setTimeout(() => {
isUpdatingFromCommonConfig.current = false; isUpdatingFromCommonConfig.current = false;
@@ -406,32 +447,34 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
false, false,
); );
// 直接更新 formData不通过 handleChange // 直接更新 formData不通过 handleChange
setFormData((prev) => ({ updateSettingsConfigValue(updatedConfig);
...prev,
settingsConfig: updatedConfig,
}));
setUseCommonConfig(false); setUseCommonConfig(false);
} }
return; return;
} }
// 验证JSON格式 // 验证JSON格式
let isValidJson = false; const validationError = validateJsonConfig(value, "通用配置片段");
try { if (validationError) {
JSON.parse(value); setCommonConfigError(validationError);
isValidJson = true; } else {
setCommonConfigError(""); setCommonConfigError("");
} catch (err) {
setCommonConfigError("通用配置片段格式错误,需为合法 JSON");
} }
// 若当前启用通用配置且格式正确,需要替换为最新片段 // 若当前启用通用配置且格式正确,需要替换为最新片段
if (useCommonConfig && isValidJson) { if (useCommonConfig && !validationError) {
const removeResult = updateCommonConfigSnippet( const removeResult = updateCommonConfigSnippet(
formData.settingsConfig, formData.settingsConfig,
previousSnippet, previousSnippet,
false, false,
); );
if (removeResult.error) {
setCommonConfigError(removeResult.error);
if (removeResult.error.includes("配置 JSON 解析失败")) {
setSettingsConfigError("配置JSON格式错误请检查语法");
}
return;
}
const addResult = updateCommonConfigSnippet( const addResult = updateCommonConfigSnippet(
removeResult.updatedConfig, removeResult.updatedConfig,
value, value,
@@ -440,15 +483,15 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
if (addResult.error) { if (addResult.error) {
setCommonConfigError(addResult.error); setCommonConfigError(addResult.error);
if (addResult.error.includes("配置 JSON 解析失败")) {
setSettingsConfigError("配置JSON格式错误请检查语法");
}
return; return;
} }
// 标记正在通过通用配置更新,避免触发状态检查 // 标记正在通过通用配置更新,避免触发状态检查
isUpdatingFromCommonConfig.current = true; isUpdatingFromCommonConfig.current = true;
setFormData((prev) => ({ updateSettingsConfigValue(addResult.updatedConfig);
...prev,
settingsConfig: addResult.updatedConfig,
}));
// 在下一个事件循环中重置标记 // 在下一个事件循环中重置标记
setTimeout(() => { setTimeout(() => {
isUpdatingFromCommonConfig.current = false; isUpdatingFromCommonConfig.current = false;
@@ -456,7 +499,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
} }
// 保存通用配置到 localStorage // 保存通用配置到 localStorage
if (isValidJson && typeof window !== "undefined") { if (!validationError && typeof window !== "undefined") {
try { try {
window.localStorage.setItem(COMMON_CONFIG_STORAGE_KEY, value); window.localStorage.setItem(COMMON_CONFIG_STORAGE_KEY, value);
} catch { } catch {
@@ -473,6 +516,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
websiteUrl: preset.websiteUrl, websiteUrl: preset.websiteUrl,
settingsConfig: configString, settingsConfig: configString,
}); });
setSettingsConfigError(validateSettingsConfig(configString));
setCategory( setCategory(
preset.category || (preset.isOfficial ? "official" : undefined), preset.category || (preset.isOfficial ? "official" : undefined),
); );
@@ -527,12 +571,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name" // ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
}, },
}; };
const templateString = JSON.stringify(customTemplate, null, 2);
setFormData({ setFormData({
name: "", name: "",
websiteUrl: "", websiteUrl: "",
settingsConfig: JSON.stringify(customTemplate, null, 2), settingsConfig: templateString,
}); });
setSettingsConfigError(validateSettingsConfig(templateString));
setApiKey(""); setApiKey("");
setBaseUrl("https://your-api-endpoint.com"); // 设置默认的基础 URL setBaseUrl("https://your-api-endpoint.com"); // 设置默认的基础 URL
setUseCommonConfig(false); setUseCommonConfig(false);
@@ -576,6 +622,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
websiteUrl: "", websiteUrl: "",
settingsConfig: "", settingsConfig: "",
}); });
setSettingsConfigError(validateSettingsConfig(""));
setCodexAuth(""); setCodexAuth("");
setCodexConfig(""); setCodexConfig("");
setCodexApiKey(""); setCodexApiKey("");
@@ -593,10 +640,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
); );
// 更新表单配置 // 更新表单配置
setFormData((prev) => ({ updateSettingsConfigValue(configString);
...prev,
settingsConfig: configString,
}));
// 同步通用配置开关 // 同步通用配置开关
const hasCommon = hasCommonConfigSnippet( const hasCommon = hasCommonConfigSnippet(
@@ -617,10 +661,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
} }
config.env.ANTHROPIC_BASE_URL = url.trim(); config.env.ANTHROPIC_BASE_URL = url.trim();
setFormData((prev) => ({ updateSettingsConfigValue(JSON.stringify(config, null, 2));
...prev,
settingsConfig: JSON.stringify(config, null, 2),
}));
} catch { } catch {
// ignore // ignore
} }
@@ -861,10 +902,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
delete currentConfig.env[field]; delete currentConfig.env[field];
} }
setFormData((prev) => ({ updateSettingsConfigValue(JSON.stringify(currentConfig, null, 2));
...prev,
settingsConfig: JSON.stringify(currentConfig, null, 2),
}));
} catch (err) { } catch (err) {
// 如果 JSON 解析失败,不做处理 // 如果 JSON 解析失败,不做处理
} }
@@ -888,10 +926,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
currentConfig.env[field] = value; currentConfig.env[field] = value;
const updatedConfigString = JSON.stringify(currentConfig, null, 2); const updatedConfigString = JSON.stringify(currentConfig, null, 2);
setFormData((prev) => ({ updateSettingsConfigValue(updatedConfigString);
...prev,
settingsConfig: updatedConfigString,
}));
} catch (err) { } catch (err) {
console.error("更新 Kimi 模型配置失败:", err); console.error("更新 Kimi 模型配置失败:", err);
} }
@@ -1144,6 +1179,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
commonConfigSnippet={codexCommonConfigSnippet} commonConfigSnippet={codexCommonConfigSnippet}
onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange} onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange}
commonConfigError={codexCommonConfigError} commonConfigError={codexCommonConfigError}
authError={codexAuthError}
/> />
) : ( ) : (
<> <>
@@ -1215,6 +1251,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
commonConfigSnippet={commonConfigSnippet} commonConfigSnippet={commonConfigSnippet}
onCommonConfigSnippetChange={handleCommonConfigSnippetChange} onCommonConfigSnippetChange={handleCommonConfigSnippetChange}
commonConfigError={commonConfigError} commonConfigError={commonConfigError}
configError={settingsConfigError}
/> />
</> </>
)} )}

View File

@@ -10,6 +10,7 @@ interface ClaudeConfigEditorProps {
commonConfigSnippet: string; commonConfigSnippet: string;
onCommonConfigSnippetChange: (value: string) => void; onCommonConfigSnippetChange: (value: string) => void;
commonConfigError: string; commonConfigError: string;
configError: string;
} }
const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
@@ -20,6 +21,7 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
commonConfigSnippet, commonConfigSnippet,
onCommonConfigSnippetChange, onCommonConfigSnippetChange,
commonConfigError, commonConfigError,
configError,
}) => { }) => {
const [isDarkMode, setIsDarkMode] = useState(false); const [isDarkMode, setIsDarkMode] = useState(false);
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
@@ -117,6 +119,11 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
}`} }`}
rows={12} rows={12}
/> />
{configError && (
<p className="text-xs text-red-500 dark:text-red-400">
{configError}
</p>
)}
<p className="text-xs text-gray-500 dark:text-gray-400"> <p className="text-xs text-gray-500 dark:text-gray-400">
Claude Code settings.json Claude Code settings.json
</p> </p>

View File

@@ -12,6 +12,7 @@ interface CodexConfigEditorProps {
commonConfigSnippet: string; commonConfigSnippet: string;
onCommonConfigSnippetChange: (value: string) => void; onCommonConfigSnippetChange: (value: string) => void;
commonConfigError: string; commonConfigError: string;
authError: string;
} }
const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
@@ -25,6 +26,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
commonConfigSnippet, commonConfigSnippet,
onCommonConfigSnippetChange, onCommonConfigSnippetChange,
commonConfigError, commonConfigError,
authError,
}) => { }) => {
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
@@ -94,6 +96,11 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
data-gramm_editor="false" data-gramm_editor="false"
data-enable-grammarly="false" data-enable-grammarly="false"
/> />
{authError && (
<p className="text-xs text-red-500 dark:text-red-400">
{authError}
</p>
)}
<p className="text-xs text-gray-500 dark:text-gray-400"> <p className="text-xs text-gray-500 dark:text-gray-400">
Codex auth.json Codex auth.json
</p> </p>

View File

@@ -77,6 +77,22 @@ export interface UpdateCommonConfigResult {
error?: string; error?: string;
} }
// 验证JSON配置格式
export const validateJsonConfig = (value: string, fieldName: string = "配置"): string => {
if (!value.trim()) {
return "";
}
try {
const parsed = JSON.parse(value);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return `${fieldName}必须是 JSON 对象`;
}
return "";
} catch {
return `${fieldName}JSON格式错误请检查语法`;
}
};
// 将通用配置片段写入/移除 settingsConfig // 将通用配置片段写入/移除 settingsConfig
export const updateCommonConfigSnippet = ( export const updateCommonConfigSnippet = (
jsonString: string, jsonString: string,
@@ -99,23 +115,17 @@ export const updateCommonConfigSnippet = (
}; };
} }
let snippet: Record<string, any>; // 使用统一的验证函数
try { const snippetError = validateJsonConfig(snippetString, "通用配置片段");
const parsed = JSON.parse(snippetString); if (snippetError) {
if (!isPlainObject(parsed)) {
return { return {
updatedConfig: JSON.stringify(config, null, 2), updatedConfig: JSON.stringify(config, null, 2),
error: "通用配置片段必须是 JSON 对象", error: snippetError,
};
}
snippet = parsed;
} catch (err) {
return {
updatedConfig: JSON.stringify(config, null, 2),
error: "通用配置片段格式错误,需为合法 JSON",
}; };
} }
const snippet = JSON.parse(snippetString) as Record<string, any>;
if (enabled) { if (enabled) {
const merged = deepMerge(deepClone(config), snippet); const merged = deepMerge(deepClone(config), snippet);
return { return {