[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 {string} message Human-readable error message
|
||||
* @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 = {}) {
|
||||
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