diff --git a/cli/src/errors/format.test.ts b/cli/src/errors/format.test.ts new file mode 100644 index 0000000000..bebbfb0c73 --- /dev/null +++ b/cli/src/errors/format.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, it } from 'vitest' +import { HttpClientError } from './base' +import { ErrorCode } from './codes' +import { formatErrorForCli } from './format' + +function validationError(): HttpClientError { + return new HttpClientError({ + code: ErrorCode.Server4xxOther, + message: 'Request validation failed', + httpStatus: 422, + serverError: { + code: 'invalid_param', + message: 'Request validation failed', + status: 422, + hint: 'check the page parameter', + details: [ + { type: 'int_parsing', loc: ['page'], msg: 'must be >= 1' }, + { type: 'missing', loc: ['inputs', 'query'], msg: 'field required' }, + ], + }, + }) +} + +describe('formatErrorForCli — human', () => { + it('prints server code, message, and details without verbose', () => { + const out = formatErrorForCli(validationError(), { isErrTTY: false }) + + expect(out).toContain('invalid_param: Request validation failed') + expect(out).toContain('- page: must be >= 1 (int_parsing)') + expect(out).toContain('- inputs.query: field required (missing)') + expect(out).toContain('check the page parameter') + expect(out).not.toContain('raw_response') + }) + + it('falls back to cli code when no server code', () => { + const err = new HttpClientError({ code: ErrorCode.Server5xx, message: 'server error (HTTP 502)', httpStatus: 502 }) + + const out = formatErrorForCli(err, { isErrTTY: false }) + + expect(out).toContain('server_5xx: server error (HTTP 502)') + }) + + it('server hint wins over cli hint; cli hint fills when server sent none', () => { + // validationError has server hint "check the page parameter"; cli hint is ignored + const withCliHint = new HttpClientError({ + code: ErrorCode.Server4xxOther, + message: 'Request validation failed', + httpStatus: 422, + hint: 'cli fallback hint', + serverError: { + code: 'invalid_param', + message: 'Request validation failed', + status: 422, + hint: 'check the page parameter', + details: [], + }, + }) + expect(formatErrorForCli(withCliHint, { isErrTTY: false })).toContain('check the page parameter') + expect(formatErrorForCli(withCliHint, { isErrTTY: false })).not.toContain('cli fallback hint') + + // no server hint → cli hint shown + const noServerHint = new HttpClientError({ + code: ErrorCode.AuthExpired, + message: 'session expired', + hint: 'run difyctl auth login', + }) + expect(formatErrorForCli(noServerHint, { isErrTTY: false })).toContain('run difyctl auth login') + }) + + it('renders request and http_status lines', () => { + const err = new HttpClientError({ + code: ErrorCode.Server5xx, + message: 'upstream boom', + httpStatus: 502, + method: 'GET', + url: 'https://api.dify.ai/v1/me', + }) + const out = formatErrorForCli(err, { isErrTTY: false }) + expect(out).toContain('request: GET https://api.dify.ai/v1/me') + expect(out).toContain('http_status: 502') + }) +}) + +describe('formatErrorForCli — json', () => { + it('envelope nests the whole server error', () => { + const out = JSON.parse(formatErrorForCli(validationError(), { format: 'json' })) + + expect(out.error.server.code).toBe('invalid_param') + expect(out.error.server.details).toHaveLength(2) + expect(out.error.code).toBe('server_4xx_other') + }) +}) diff --git a/cli/src/errors/format.ts b/cli/src/errors/format.ts index 97bc527e7d..63a0a0b28b 100644 --- a/cli/src/errors/format.ts +++ b/cli/src/errors/format.ts @@ -47,9 +47,14 @@ function renderEnvelope(env: ErrorEnvelope): string { function renderHuman(env: ErrorEnvelope, isErrTTY: boolean): string { const cs = colorScheme(colorEnabled(isErrTTY)) const e = env.error - const lines: string[] = [`${e.code}: ${e.message}`] - if (e.hint !== undefined) - lines.push(`${cs.magenta('hint:')} ${cs.cyan(e.hint)}`) + const server = e.server + const headerCode = server?.code ?? e.code + const lines: string[] = [`${headerCode}: ${e.message}`] + for (const d of server?.details ?? []) + lines.push(` - ${(d.loc ?? []).join('.')}: ${d.msg} (${d.type})`) + const hint = server?.hint ?? e.hint + if (hint !== undefined && hint !== null) + lines.push(`${cs.magenta('hint:')} ${cs.cyan(hint)}`) if (e.method !== undefined && e.url !== undefined) lines.push(`request: ${e.method} ${e.url}`) if (e.http_status !== undefined)