frontend(config): 改善配置中心测试错误展示布局并美化错误详情
- useApi.testConfig:摘要优先 error 字段;缺省时将原始响应体放入 details,确保 Pretty Printer 可用 - Config 页面:失败时仅显示“测试失败”+ 折叠详情;新增 brace-aware pretty printer,支持 MCP/Rust/reqwest 风格错误的缩进分行显示
This commit is contained in:
parent
427776b863
commit
733bf89af5
@ -6,7 +6,40 @@ export async function POST(req: NextRequest) {
|
|||||||
if (!BACKEND_BASE) {
|
if (!BACKEND_BASE) {
|
||||||
return new Response('BACKEND_INTERNAL_URL/NEXT_PUBLIC_BACKEND_URL 未配置', { status: 500 });
|
return new Response('BACKEND_INTERNAL_URL/NEXT_PUBLIC_BACKEND_URL 未配置', { status: 500 });
|
||||||
}
|
}
|
||||||
// 新后端暂无统一 /config/test;先返回未实现
|
|
||||||
const body = await req.text().catch(() => '');
|
try {
|
||||||
return Response.json({ success: false, message: 'config/test 未实现', echo: body }, { status: 501 });
|
const body = await req.json();
|
||||||
|
const { type, data } = body;
|
||||||
|
|
||||||
|
if (!type || !data) {
|
||||||
|
return new Response('请求体必须包含 type 和 data', { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将请求转发到 API Gateway
|
||||||
|
const targetUrl = `${BACKEND_BASE.replace(/\/$/, '')}/configs/test`;
|
||||||
|
|
||||||
|
const backendRes = await fetch(targetUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ type, ...data }), // 转发时将 data 字段展开
|
||||||
|
});
|
||||||
|
|
||||||
|
const backendResBody = await backendRes.text();
|
||||||
|
|
||||||
|
return new Response(backendResBody, {
|
||||||
|
status: backendRes.status,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('配置测试代理失败:', error);
|
||||||
|
return new Response(JSON.stringify({ success: false, message: error.message || '代理请求时发生未知错误' }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,143 @@ import type {
|
|||||||
} from '@/types';
|
} from '@/types';
|
||||||
import { useDataSourcesConfig, updateDataSourcesConfig, useAnalysisTemplateSets, updateAnalysisTemplateSets } from '@/hooks/useApi';
|
import { useDataSourcesConfig, updateDataSourcesConfig, useAnalysisTemplateSets, updateAnalysisTemplateSets } from '@/hooks/useApi';
|
||||||
|
|
||||||
|
// ---- Helpers: pretty print nested JSON as YAML-like (braceless, unquoted) ----
|
||||||
|
const MAX_REPARSE_DEPTH = 2;
|
||||||
|
function tryParseJson(input: string): unknown {
|
||||||
|
try {
|
||||||
|
return JSON.parse(input);
|
||||||
|
} catch {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function parsePossiblyNestedJson(raw: string): unknown {
|
||||||
|
let current: unknown = raw;
|
||||||
|
for (let i = 0; i < MAX_REPARSE_DEPTH; i++) {
|
||||||
|
if (typeof current === 'string') {
|
||||||
|
const trimmed = current.trim();
|
||||||
|
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
||||||
|
current = tryParseJson(trimmed);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
function indentLines(text: string, indent: string): string {
|
||||||
|
return text.split('\n').map((line) => indent + line).join('\n');
|
||||||
|
}
|
||||||
|
function looksLikeStructuredText(s: string): boolean {
|
||||||
|
const t = s.trim();
|
||||||
|
// 具有成对的大括号,或常见的 Rust/reqwest 错误标识,判定为可结构化的调试输出
|
||||||
|
return (t.includes('{') && t.includes('}')) || t.includes('DynamicTransportError') || t.includes('reqwest::Error');
|
||||||
|
}
|
||||||
|
function prettyFormatByBraces(input: string): string {
|
||||||
|
let out = '';
|
||||||
|
let indentLevel = 0;
|
||||||
|
const indentUnit = ' ';
|
||||||
|
let inString = false;
|
||||||
|
let stringQuote = '';
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const ch = input[i]!;
|
||||||
|
const prev = i > 0 ? input[i - 1]! : '';
|
||||||
|
if ((ch === '"' || ch === "'") && prev !== '\\') {
|
||||||
|
if (!inString) {
|
||||||
|
inString = true;
|
||||||
|
stringQuote = ch;
|
||||||
|
} else if (stringQuote === ch) {
|
||||||
|
inString = false;
|
||||||
|
}
|
||||||
|
out += ch;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inString) {
|
||||||
|
out += ch;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === '{' || ch === '[' || ch === '(') {
|
||||||
|
indentLevel += 1;
|
||||||
|
out += ch + '\n' + indentUnit.repeat(indentLevel);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === '}' || ch === ']' || ch === ')') {
|
||||||
|
indentLevel = Math.max(0, indentLevel - 1);
|
||||||
|
out += '\n' + indentUnit.repeat(indentLevel) + ch;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === ',') {
|
||||||
|
out += ch + '\n' + indentUnit.repeat(indentLevel);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === ':') {
|
||||||
|
out += ch + ' ';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out += ch;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function toPseudoYaml(value: unknown, indent: string = ''): string {
|
||||||
|
const nextIndent = indent + ' ';
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return `${indent}null`;
|
||||||
|
}
|
||||||
|
const t = typeof value;
|
||||||
|
if (t === 'string' || t === 'number' || t === 'boolean') {
|
||||||
|
const s = String(value);
|
||||||
|
const shouldPretty = looksLikeStructuredText(s) || s.includes('\n');
|
||||||
|
if (shouldPretty) {
|
||||||
|
const pretty = looksLikeStructuredText(s) ? prettyFormatByBraces(s) : s;
|
||||||
|
return `${indent}|-\n${indentLines(pretty, nextIndent)}`;
|
||||||
|
}
|
||||||
|
return `${indent}${s}`;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (value.length === 0) return `${indent}[]`;
|
||||||
|
return value.map((item) => {
|
||||||
|
const rendered = toPseudoYaml(item, nextIndent);
|
||||||
|
const lines = rendered.split('\n');
|
||||||
|
if (lines.length === 1) {
|
||||||
|
return `${indent}- ${lines[0].trimStart()}`;
|
||||||
|
}
|
||||||
|
return `${indent}- ${lines[0].trimStart()}\n${lines.slice(1).map((l) => indent + ' ' + l.trimStart()).join('\n')}`;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const obj = value as Record<string, unknown>;
|
||||||
|
const keys = Object.keys(obj);
|
||||||
|
if (keys.length === 0) return `${indent}{}`;
|
||||||
|
return keys.map((k) => {
|
||||||
|
const rendered = toPseudoYaml(obj[k], nextIndent);
|
||||||
|
const lines = rendered.split('\n');
|
||||||
|
if (lines.length === 1) {
|
||||||
|
return `${indent}${k}: ${lines[0].trimStart()}`;
|
||||||
|
}
|
||||||
|
return `${indent}${k}:\n${lines.map((l) => l).join('\n')}`;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
// fallback
|
||||||
|
return `${indent}${String(value)}`;
|
||||||
|
}
|
||||||
|
function formatDetailsToYaml(details: string): string {
|
||||||
|
const parsed = parsePossiblyNestedJson(details);
|
||||||
|
if (typeof parsed === 'string') {
|
||||||
|
// 尝试再次解析(有些场景内层 message 也是 JSON 串)
|
||||||
|
const again = parsePossiblyNestedJson(parsed);
|
||||||
|
if (typeof again === 'string') {
|
||||||
|
return toPseudoYaml(again);
|
||||||
|
}
|
||||||
|
return toPseudoYaml(again);
|
||||||
|
}
|
||||||
|
return toPseudoYaml(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultUrls: Partial<Record<DataSourceProvider, string>> = {
|
||||||
|
tushare: 'http://api.tushare.pro',
|
||||||
|
finnhub: 'https://finnhub.io/api/v1',
|
||||||
|
alphavantage: 'https://mcp.alphavantage.co/mcp',
|
||||||
|
};
|
||||||
|
|
||||||
export default function ConfigPage() {
|
export default function ConfigPage() {
|
||||||
// 从 Zustand store 获取全局状态
|
// 从 Zustand store 获取全局状态
|
||||||
const { config, loading, error, setConfig } = useConfigStore();
|
const { config, loading, error, setConfig } = useConfigStore();
|
||||||
@ -47,7 +184,7 @@ export default function ConfigPage() {
|
|||||||
// 分析配置保存状态(状态定义在下方统一维护)
|
// 分析配置保存状态(状态定义在下方统一维护)
|
||||||
|
|
||||||
// 测试结果状态
|
// 测试结果状态
|
||||||
const [testResults, setTestResults] = useState<Record<string, { success: boolean; message: string } | null>>({});
|
const [testResults, setTestResults] = useState<Record<string, { success: boolean; summary: string; details?: string } | null>>({});
|
||||||
|
|
||||||
// 保存状态
|
// 保存状态
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@ -368,8 +505,22 @@ export default function ConfigPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (initialDataSources) {
|
if (initialDataSources) {
|
||||||
await updateDataSourcesConfig(localDataSources);
|
// Create a deep copy to avoid mutating the local state directly
|
||||||
await mutateDataSources(localDataSources, false);
|
const finalDataSources = JSON.parse(JSON.stringify(localDataSources));
|
||||||
|
for (const key in finalDataSources) {
|
||||||
|
const providerKey = key as DataSourceProvider;
|
||||||
|
const source = finalDataSources[providerKey];
|
||||||
|
// If the URL is empty or null, and there is a default URL, use it
|
||||||
|
if (source && (source.api_url === null || source.api_url.trim() === '') && defaultUrls[providerKey]) {
|
||||||
|
source.api_url = defaultUrls[providerKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateDataSourcesConfig(finalDataSources);
|
||||||
|
// After saving, mutate the local SWR cache with the final data
|
||||||
|
// and also update the component's local state to reflect the change.
|
||||||
|
await mutateDataSources(finalDataSources, false);
|
||||||
|
setLocalDataSources(finalDataSources);
|
||||||
}
|
}
|
||||||
setSaveMessage('保存成功!');
|
setSaveMessage('保存成功!');
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -383,11 +534,18 @@ export default function ConfigPage() {
|
|||||||
const handleTest = async (type: string, data: any) => {
|
const handleTest = async (type: string, data: any) => {
|
||||||
try {
|
try {
|
||||||
const result = await testConfig(type, data);
|
const result = await testConfig(type, data);
|
||||||
setTestResults(prev => ({ ...prev, [type]: result }));
|
const success = !!result?.success;
|
||||||
|
const summary = typeof result?.message === 'string' && result.message.trim().length > 0
|
||||||
|
? result.message
|
||||||
|
: (success ? '测试成功' : '测试失败');
|
||||||
|
setTestResults(prev => ({ ...prev, [type]: { success, summary } }));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
// 结构化错误对象:{ summary, details? }
|
||||||
|
const summary: string = (e && typeof e === 'object' && 'summary' in e) ? String(e.summary) : (e?.message || '未知错误');
|
||||||
|
const details: string | undefined = (e && typeof e === 'object' && 'details' in e) ? (e.details ? String(e.details) : undefined) : undefined;
|
||||||
setTestResults(prev => ({
|
setTestResults(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[type]: { success: false, message: e.message }
|
[type]: { success: false, summary, details }
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -402,6 +560,11 @@ export default function ConfigPage() {
|
|||||||
handleTest('finnhub', { api_key: cfg?.api_key, api_url: cfg?.api_url, enabled: cfg?.enabled });
|
handleTest('finnhub', { api_key: cfg?.api_key, api_url: cfg?.api_url, enabled: cfg?.enabled });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTestAlphaVantage = () => {
|
||||||
|
const cfg = localDataSources['alphavantage'];
|
||||||
|
handleTest('alphavantage', { api_key: cfg?.api_key, api_url: cfg?.api_url, enabled: cfg?.enabled });
|
||||||
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (initialDataSources) setLocalDataSources(initialDataSources);
|
if (initialDataSources) setLocalDataSources(initialDataSources);
|
||||||
setTestResults({});
|
setTestResults({});
|
||||||
@ -1058,7 +1221,8 @@ export default function ConfigPage() {
|
|||||||
[providerKey]: { ...item, api_url: v, provider: providerKey },
|
[providerKey]: { ...item, api_url: v, provider: providerKey },
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
placeholder={providerKey === 'tushare' ? 'https://api.tushare.pro' : 'https://...'}
|
placeholder={defaultUrls[providerKey] ?? 'https://...'}
|
||||||
|
disabled={providerKey === 'yfinance'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1069,7 +1233,33 @@ export default function ConfigPage() {
|
|||||||
{providerKey === 'finnhub' && (
|
{providerKey === 'finnhub' && (
|
||||||
<Button variant="outline" onClick={handleTestFinnhub}>测试 Finnhub</Button>
|
<Button variant="outline" onClick={handleTestFinnhub}>测试 Finnhub</Button>
|
||||||
)}
|
)}
|
||||||
|
{providerKey === 'alphavantage' && (
|
||||||
|
<Button variant="outline" onClick={handleTestAlphaVantage}>测试 AlphaVantage</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{testResults[providerKey] ? (() => {
|
||||||
|
const r = testResults[providerKey]!;
|
||||||
|
if (r.success) {
|
||||||
|
return (
|
||||||
|
<div className="text-sm text-green-600">
|
||||||
|
{r.summary}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="text-sm text-red-600">
|
||||||
|
<div className="font-medium">测试失败</div>
|
||||||
|
{r.details ? (
|
||||||
|
<details className="mt-2">
|
||||||
|
<summary className="cursor-pointer text-red-700 underline">查看详细错误(YAML)</summary>
|
||||||
|
<pre className="mt-2 p-2 rounded bg-red-50 text-red-700 whitespace-pre-wrap break-words">
|
||||||
|
{formatDetailsToYaml(r.details)}
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})() : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -294,9 +294,41 @@ export async function testConfig(type: string, data: unknown) {
|
|||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
try {
|
try {
|
||||||
const err = JSON.parse(text);
|
const err = JSON.parse(text);
|
||||||
throw new Error(err?.message || text);
|
// 优先从标准字段中提取错误信息;同时分离 details
|
||||||
|
let message: string = err?.error || err?.message || '';
|
||||||
|
let detailsStr = '';
|
||||||
|
if (err?.details !== undefined) {
|
||||||
|
if (typeof err.details === 'string') {
|
||||||
|
detailsStr = err.details;
|
||||||
|
// details 可能是被 JSON 序列化的字符串,尝试解析一次以便还原内部结构
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(detailsStr);
|
||||||
|
if (typeof parsed === 'string') {
|
||||||
|
detailsStr = parsed;
|
||||||
|
} else if (parsed && typeof parsed === 'object' && 'message' in parsed) {
|
||||||
|
detailsStr = String((parsed as any).message);
|
||||||
|
} else {
|
||||||
|
detailsStr = JSON.stringify(parsed);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 忽略解析失败,保留原始 details 字符串
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
detailsStr = JSON.stringify(err.details);
|
||||||
|
} catch {
|
||||||
|
detailsStr = String(err.details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const summary = message || `HTTP ${res.status}`;
|
||||||
|
const detailsOut = (detailsStr && detailsStr.length > 0) ? detailsStr : (text || undefined);
|
||||||
|
// 抛出结构化错误对象,供调用方精确展示(details 始终尽量携带原始响应体,便于前端美化)
|
||||||
|
throw { summary, details: detailsOut };
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(text || `HTTP ${res.status}`);
|
// 无法解析为 JSON 时,仍然把原始文本作为 details 返回,保证前端可美化
|
||||||
|
const fallback = text || `HTTP ${res.status}`;
|
||||||
|
throw { summary: fallback, details: text || undefined };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -2,16 +2,18 @@ use std::collections::HashMap;
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::State,
|
||||||
response::Json,
|
response::Json,
|
||||||
routing::get,
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use common_contracts::observability::{HealthStatus, ServiceStatus, TaskProgress};
|
use common_contracts::observability::{HealthStatus, ServiceStatus, TaskProgress};
|
||||||
use crate::state::{AppState, ServiceOperationalStatus};
|
use crate::state::{AppState, ServiceOperationalStatus};
|
||||||
|
use crate::api_test;
|
||||||
|
|
||||||
pub fn create_router(app_state: AppState) -> Router {
|
pub fn create_router(app_state: AppState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/health", get(health_check))
|
.route("/health", get(health_check))
|
||||||
.route("/tasks", get(get_current_tasks))
|
.route("/tasks", get(get_current_tasks))
|
||||||
|
.route("/test", post(api_test::test_connection))
|
||||||
.with_state(app_state)
|
.with_state(app_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
mod api;
|
mod api;
|
||||||
|
mod api_test;
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
mod mapping;
|
mod mapping;
|
||||||
|
|||||||
@ -66,6 +66,7 @@ fn create_v1_router() -> Router<AppState> {
|
|||||||
"/configs/data_sources",
|
"/configs/data_sources",
|
||||||
get(get_data_sources_config).put(update_data_sources_config),
|
get(get_data_sources_config).put(update_data_sources_config),
|
||||||
)
|
)
|
||||||
|
.route("/configs/test", post(test_data_source_config))
|
||||||
// --- New Discover Routes ---
|
// --- New Discover Routes ---
|
||||||
.route("/discover-models/{provider_id}", get(discover_models))
|
.route("/discover-models/{provider_id}", get(discover_models))
|
||||||
.route("/discover-models", post(discover_models_preview))
|
.route("/discover-models", post(discover_models_preview))
|
||||||
@ -208,6 +209,71 @@ async fn get_task_progress(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- New Config Test Handler ---
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct TestConfigRequest {
|
||||||
|
r#type: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
data: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [POST /v1/configs/test]
|
||||||
|
/// Forwards a configuration test request to the appropriate downstream service.
|
||||||
|
async fn test_data_source_config(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(payload): Json<TestConfigRequest>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
info!("test_data_source_config: type={}", payload.r#type);
|
||||||
|
|
||||||
|
let target_service_url = match payload.r#type.as_str() {
|
||||||
|
"tushare" => state.config.provider_services.iter().find(|s| s.contains("tushare")),
|
||||||
|
"finnhub" => state.config.provider_services.iter().find(|s| s.contains("finnhub")),
|
||||||
|
"alphavantage" => state.config.provider_services.iter().find(|s| s.contains("alphavantage")),
|
||||||
|
_ => {
|
||||||
|
return Ok((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!({ "error": "Unsupported config type" })),
|
||||||
|
).into_response());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(base_url) = target_service_url {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let target_url = format!("{}/test", base_url.trim_end_matches('/'));
|
||||||
|
info!("Forwarding test request for '{}' to {}", payload.r#type, target_url);
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(&target_url)
|
||||||
|
.json(&payload.data)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
warn!("Downstream test for '{}' failed: status={} body={}", payload.r#type, status, error_text);
|
||||||
|
return Ok((
|
||||||
|
StatusCode::from_u16(status.as_u16()).unwrap_or(StatusCode::BAD_GATEWAY),
|
||||||
|
Json(serde_json::json!({
|
||||||
|
"error": "Downstream service returned an error",
|
||||||
|
"details": error_text,
|
||||||
|
})),
|
||||||
|
).into_response());
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_json: serde_json::Value = response.json().await?;
|
||||||
|
Ok((StatusCode::OK, Json(response_json)).into_response())
|
||||||
|
} else {
|
||||||
|
warn!("No downstream service found for config type: {}", payload.r#type);
|
||||||
|
Ok((
|
||||||
|
StatusCode::NOT_IMPLEMENTED,
|
||||||
|
Json(serde_json::json!({ "error": "No downstream service configured for this type" })),
|
||||||
|
).into_response())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Config API Handlers (Proxy to data-persistence-service) ---
|
// --- Config API Handlers (Proxy to data-persistence-service) ---
|
||||||
|
|
||||||
use common_contracts::config_models::{
|
use common_contracts::config_models::{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user