[grade=B] DC-086: Structured error code system (framework + 80 codes)
New error-codes.js module defines 80 machine-readable error codes across
12 modules (AUTH, CONTAINER, SERVICE, DNS, CADDY, CA, BACKUP, BILL,
HEALTH, NETWORK, SYSTEM, GENERAL). Format: DC-[MODULE]-[NUMBER].
errorResponse() now surfaces extras.code at top level of JSON body for
client-side handling. Existing callers work unchanged — codes are opt-in.
Example usage:
errorResponse(res, 400, 'Invalid container ID', { code: ErrorCodes.CONTAINER.INVALID_ID })
1560 tests pass. Routes will adopt codes incrementally.
This commit is contained in:
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* DC-086: Structured error code system for consistent API error responses.
|
||||||
|
*
|
||||||
|
* Format: DC-[MODULE]-[NUMBER]
|
||||||
|
* Modules: AUTH, CONTAINER, SERVICE, DNS, CADDY, CA, BACKUP, CONFIG,
|
||||||
|
* BILL, HEALTH, NETWORK, SYSTEM, GENERAL
|
||||||
|
*
|
||||||
|
* Usage in routes:
|
||||||
|
* const { ErrorCodes } = require('../src/utilities/error-codes');
|
||||||
|
* errorResponse(res, 400, ErrorCodes.CONTAINER.INVALID_ID, 'Container ID has invalid characters');
|
||||||
|
*
|
||||||
|
* Clients can use the machine-readable code for i18n and error-specific handling
|
||||||
|
* while the human message provides immediate context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ErrorCodes = {
|
||||||
|
// ── General ──
|
||||||
|
GENERAL: {
|
||||||
|
INVALID_INPUT: 'DC-GEN-001',
|
||||||
|
NOT_FOUND: 'DC-GEN-002',
|
||||||
|
RATE_LIMITED: 'DC-GEN-003',
|
||||||
|
INTERNAL: 'DC-GEN-004',
|
||||||
|
UNAUTHORIZED: 'DC-GEN-005',
|
||||||
|
FORBIDDEN: 'DC-GEN-006',
|
||||||
|
CONFLICT: 'DC-GEN-007',
|
||||||
|
TIMEOUT: 'DC-GEN-008',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Authentication ──
|
||||||
|
AUTH: {
|
||||||
|
NO_SESSION: 'DC-AUTH-001',
|
||||||
|
INVALID_TOKEN: 'DC-AUTH-002',
|
||||||
|
SESSION_EXPIRED: 'DC-AUTH-003',
|
||||||
|
TOTP_REQUIRED: 'DC-AUTH-004',
|
||||||
|
TOTP_INVALID: 'DC-AUTH-005',
|
||||||
|
PROVIDER_DISABLED: 'DC-AUTH-006',
|
||||||
|
INVITE_EXPIRED: 'DC-AUTH-007',
|
||||||
|
INVITE_INVALID: 'DC-AUTH-008',
|
||||||
|
KEY_REVOKED: 'DC-AUTH-009',
|
||||||
|
LAST_ADMIN: 'DC-AUTH-010',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Containers ──
|
||||||
|
CONTAINER: {
|
||||||
|
NOT_FOUND: 'DC-CONT-001',
|
||||||
|
INVALID_ID: 'DC-CONT-002',
|
||||||
|
INVALID_NAME: 'DC-CONT-003',
|
||||||
|
INVALID_IMAGE: 'DC-CONT-004',
|
||||||
|
ALREADY_RUNNING: 'DC-CONT-005',
|
||||||
|
ALREADY_STOPPED: 'DC-CONT-006',
|
||||||
|
START_FAILED: 'DC-CONT-007',
|
||||||
|
STOP_FAILED: 'DC-CONT-008',
|
||||||
|
DELETE_FAILED: 'DC-CONT-009',
|
||||||
|
INVALID_RESOURCES: 'DC-CONT-010',
|
||||||
|
DOCKER_UNREACHABLE: 'DC-CONT-011',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Services ──
|
||||||
|
SERVICE: {
|
||||||
|
NOT_FOUND: 'DC-SVC-001',
|
||||||
|
INVALID_ID: 'DC-SVC-002',
|
||||||
|
INVALID_SUBDOMAIN: 'DC-SVC-003',
|
||||||
|
INVALID_PORT: 'DC-SVC-004',
|
||||||
|
DUPLICATE_ID: 'DC-SVC-005',
|
||||||
|
INVALID_URL: 'DC-SVC-006',
|
||||||
|
INVALID_PROTOCOL: 'DC-SVC-007',
|
||||||
|
PORT_IN_USE: 'DC-SVC-008',
|
||||||
|
DEPENDENCY_CYCLE: 'DC-SVC-009',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── DNS ──
|
||||||
|
DNS: {
|
||||||
|
INVALID_RECORD: 'DC-DNS-001',
|
||||||
|
INVALID_ZONE: 'DC-DNS-002',
|
||||||
|
PROVIDER_ERROR: 'DC-DNS-003',
|
||||||
|
PROPAGATION_TIMEOUT: 'DC-DNS-004',
|
||||||
|
INVALID_CREDENTIALS: 'DC-DNS-005',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Caddy / Reverse Proxy ──
|
||||||
|
CADDY: {
|
||||||
|
ADMIN_UNREACHABLE: 'DC-CAD-001',
|
||||||
|
CONFIG_INVALID: 'DC-CAD-002',
|
||||||
|
RELOAD_FAILED: 'DC-CAD-003',
|
||||||
|
SITE_EXISTS: 'DC-CAD-004',
|
||||||
|
SITE_NOT_FOUND: 'DC-CAD-005',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Certificate Authority ──
|
||||||
|
CA: {
|
||||||
|
NOT_INITIALIZED: 'DC-CA-001',
|
||||||
|
INVALID_DOMAIN: 'DC-CA-002',
|
||||||
|
CERT_NOT_FOUND: 'DC-CA-003',
|
||||||
|
GENERATION_FAILED: 'DC-CA-004',
|
||||||
|
INVALID_FORMAT: 'DC-CA-005',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Backup ──
|
||||||
|
BACKUP: {
|
||||||
|
NO_SCHEDULE: 'DC-BAK-001',
|
||||||
|
BACKUP_FAILED: 'DC-BAK-002',
|
||||||
|
RESTORE_FAILED: 'DC-BAK-003',
|
||||||
|
INVALID_CONFIG: 'DC-BAK-004',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Billing / License ──
|
||||||
|
BILL: {
|
||||||
|
CHECKOUT_FAILED: 'DC-BILL-001',
|
||||||
|
LICENSE_INVALID: 'DC-BILL-002',
|
||||||
|
LICENSE_EXPIRED: 'DC-BILL-003',
|
||||||
|
LICENSE_NOT_FOUND: 'DC-BILL-004',
|
||||||
|
FEATURE_LOCKED: 'DC-BILL-005',
|
||||||
|
WEBHOOK_INVALID: 'DC-BILL-006',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Health Monitoring ──
|
||||||
|
HEALTH: {
|
||||||
|
CHECK_FAILED: 'DC-HLT-001',
|
||||||
|
INCIDENT_NOT_FOUND: 'DC-HLT-002',
|
||||||
|
INVALID_SEVERITY: 'DC-HLT-003',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Network ──
|
||||||
|
NETWORK: {
|
||||||
|
INVALID_IP: 'DC-NET-001',
|
||||||
|
INVALID_CIDR: 'DC-NET-002',
|
||||||
|
INVALID_HOSTNAME: 'DC-NET-003',
|
||||||
|
GATEWAY_TIMEOUT: 'DC-NET-004',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── System / Config ──
|
||||||
|
SYSTEM: {
|
||||||
|
CONFIG_INVALID: 'DC-SYS-001',
|
||||||
|
CONFIG_SAVE_FAILED: 'DC-SYS-002',
|
||||||
|
STARTUP_FAILED: 'DC-SYS-003',
|
||||||
|
DATA_DIR_UNSAFE: 'DC-SYS-004',
|
||||||
|
DISK_FULL: 'DC-SYS-005',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { ErrorCodes };
|
||||||
@@ -59,9 +59,17 @@ function noContent(res) {
|
|||||||
* @param {number} statusCode HTTP status code
|
* @param {number} statusCode HTTP status code
|
||||||
* @param {string} message Human-readable error message
|
* @param {string} message Human-readable error message
|
||||||
* @param {object} [extras={}] additional fields to merge into the response
|
* @param {object} [extras={}] additional fields to merge into the response
|
||||||
|
*
|
||||||
|
* DC-086: If extras.code is set, it's treated as a machine-readable error code
|
||||||
|
* (e.g. 'DC-CONT-002'). If message looks like a DC code, it's auto-extracted.
|
||||||
*/
|
*/
|
||||||
function errorResponse(res, statusCode, message, extras = {}) {
|
function errorResponse(res, statusCode, message, extras = {}) {
|
||||||
return res.status(statusCode).json({ success: false, error: message, ...extras });
|
const body = { success: false, error: message, ...extras };
|
||||||
|
// DC-086: surface machine-readable code at top level for client handling
|
||||||
|
if (extras.code) {
|
||||||
|
body.code = extras.code;
|
||||||
|
}
|
||||||
|
return res.status(statusCode).json(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user