This commit is contained in:
75
src/editor/shared/editorApiClient.ts
Normal file
75
src/editor/shared/editorApiClient.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { fetchJson, parseApiErrorMessage, saveJsonObject } from './jsonClient';
|
||||
|
||||
export const EDITOR_API_BASE_PATH = '/api/editor';
|
||||
export const ASSETS_API_BASE_PATH = '/api/assets';
|
||||
|
||||
export const EDITOR_JSON_RESOURCE_IDS = {
|
||||
itemOverrides: 'item-overrides',
|
||||
npcVisualOverrides: 'npc-visual-overrides',
|
||||
npcLayoutConfig: 'npc-layout-config',
|
||||
characterOverrides: 'character-overrides',
|
||||
monsterOverrides: 'monster-overrides',
|
||||
sceneOverrides: 'scene-overrides',
|
||||
sceneNpcOverrides: 'scene-npc-overrides',
|
||||
stateFunctionOverrides: 'state-function-overrides',
|
||||
} as const;
|
||||
|
||||
export type EditorJsonResourceId =
|
||||
(typeof EDITOR_JSON_RESOURCE_IDS)[keyof typeof EDITOR_JSON_RESOURCE_IDS];
|
||||
|
||||
export const ASSET_API_PATHS = {
|
||||
characterWorkflowCache: `${ASSETS_API_BASE_PATH}/character-workflow-cache`,
|
||||
characterVisualGenerate: `${ASSETS_API_BASE_PATH}/character-visual/generate`,
|
||||
characterVisualPublish: `${ASSETS_API_BASE_PATH}/character-visual/publish`,
|
||||
characterVisualJobs: `${ASSETS_API_BASE_PATH}/character-visual/jobs`,
|
||||
characterAnimationGenerate: `${ASSETS_API_BASE_PATH}/character-animation/generate`,
|
||||
characterAnimationPublish: `${ASSETS_API_BASE_PATH}/character-animation/publish`,
|
||||
characterAnimationJobs: `${ASSETS_API_BASE_PATH}/character-animation/jobs`,
|
||||
characterAnimationImportVideo: `${ASSETS_API_BASE_PATH}/character-animation/import-video`,
|
||||
characterAnimationTemplates: `${ASSETS_API_BASE_PATH}/character-animation/templates`,
|
||||
} as const;
|
||||
|
||||
export const EDITOR_ITEM_CATALOG_API_PATH =
|
||||
`${EDITOR_API_BASE_PATH}/catalog/items`;
|
||||
|
||||
export function buildEditorJsonApiPath(resourceId: EditorJsonResourceId) {
|
||||
return `${EDITOR_API_BASE_PATH}/json/${resourceId}`;
|
||||
}
|
||||
|
||||
export function fetchEditorJsonResource<T>(
|
||||
resourceId: EditorJsonResourceId,
|
||||
fallbackMessage = '读取失败',
|
||||
) {
|
||||
return fetchJson<T>(buildEditorJsonApiPath(resourceId), fallbackMessage);
|
||||
}
|
||||
|
||||
export function saveEditorJsonResource(
|
||||
resourceId: EditorJsonResourceId,
|
||||
payload: Record<string, unknown>,
|
||||
fallbackMessage = '保存失败',
|
||||
) {
|
||||
return saveJsonObject(
|
||||
buildEditorJsonApiPath(resourceId),
|
||||
payload,
|
||||
fallbackMessage,
|
||||
);
|
||||
}
|
||||
|
||||
export async function postApiJson<T>(
|
||||
url: string,
|
||||
payload: Record<string, unknown>,
|
||||
fallbackMessage: string,
|
||||
) {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
|
||||
}
|
||||
|
||||
return responseText ? (JSON.parse(responseText) as T) : ({} as T);
|
||||
}
|
||||
49
src/editor/shared/jsonClient.test.ts
Normal file
49
src/editor/shared/jsonClient.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
import {parseApiErrorMessage} from './jsonClient';
|
||||
|
||||
describe('parseApiErrorMessage', () => {
|
||||
it('prefers api error detail messages for business failures', () => {
|
||||
expect(
|
||||
parseApiErrorMessage(
|
||||
JSON.stringify({
|
||||
ok: false,
|
||||
data: null,
|
||||
error: {
|
||||
code: 'BAD_REQUEST',
|
||||
message: '请求参数不合法',
|
||||
details: {
|
||||
message: 'big_fish 发布校验未通过:还缺少 16 个基础动作',
|
||||
provider: 'spacetimedb',
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
apiVersion: '2026-04-08',
|
||||
},
|
||||
}),
|
||||
'Fallback failure',
|
||||
),
|
||||
).toBe('big_fish 发布校验未通过:还缺少 16 个基础动作');
|
||||
});
|
||||
|
||||
it('prefers nested api error messages', () => {
|
||||
expect(
|
||||
parseApiErrorMessage(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
message: 'Detailed failure',
|
||||
},
|
||||
}),
|
||||
'Fallback failure',
|
||||
),
|
||||
).toBe('Detailed failure');
|
||||
});
|
||||
|
||||
it('falls back to the raw response text when the payload is not json', () => {
|
||||
expect(parseApiErrorMessage('Plain text failure', 'Fallback failure')).toBe('Plain text failure');
|
||||
});
|
||||
|
||||
it('uses the fallback when the response body is empty', () => {
|
||||
expect(parseApiErrorMessage('', 'Fallback failure')).toBe('Fallback failure');
|
||||
});
|
||||
});
|
||||
48
src/editor/shared/jsonClient.ts
Normal file
48
src/editor/shared/jsonClient.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
API_RESPONSE_ENVELOPE_HEADER,
|
||||
API_RESPONSE_ENVELOPE_VERSION,
|
||||
parseApiErrorMessage,
|
||||
unwrapApiResponse,
|
||||
} from '../../../packages/shared/src/http';
|
||||
|
||||
export async function fetchJson<T>(
|
||||
url: string,
|
||||
fallbackMessage = '请求失败',
|
||||
): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
[API_RESPONSE_ENVELOPE_HEADER]: API_RESPONSE_ENVELOPE_VERSION,
|
||||
},
|
||||
});
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(parseApiErrorMessage(responseText, `${fallbackMessage}: ${response.status}`));
|
||||
}
|
||||
|
||||
return responseText
|
||||
? unwrapApiResponse<T>(JSON.parse(responseText) as T)
|
||||
: ({} as T);
|
||||
}
|
||||
|
||||
export async function saveJsonObject(
|
||||
url: string,
|
||||
payload: Record<string, unknown>,
|
||||
fallbackMessage = '保存失败',
|
||||
) {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[API_RESPONSE_ENVELOPE_HEADER]: API_RESPONSE_ENVELOPE_VERSION,
|
||||
},
|
||||
body: JSON.stringify(payload, null, 2),
|
||||
});
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
|
||||
}
|
||||
}
|
||||
|
||||
export { parseApiErrorMessage };
|
||||
Reference in New Issue
Block a user