errors.js (4674B)
1 // src/lib/errors.js - errors that describe themselves over HTTP 2 // 3 // The class name is the identifier on the wire, and code is the HTTP status: 4 // serialize-error-cjs carries both, where a property of our own would be lost. 5 // Statics rather than fields, since a subclass field would clobber an override. 6 7 export class ApiError extends Error { 8 static code = 500; 9 static defaultMessage = 'The request could not be completed'; 10 11 constructor(message, { code, details } = {}) { 12 super(message ?? new.target.defaultMessage); 13 this.name = new.target.name; 14 this.code = code ?? new.target.code; 15 if (details !== undefined) this.details = details; 16 } 17 } 18 19 // Carries details.request_id, tying a report of "it broke" to the log line. 20 export class InternalError extends ApiError { 21 static code = 500; 22 } 23 24 // For a 4xx nobody threw deliberately, such as a body Fastify itself refused. 25 export class BadRequest extends ApiError { 26 static code = 400; 27 static defaultMessage = 'The request could not be understood'; 28 } 29 30 // --- not there, or not yours: both answer 404, so neither can be probed --- 31 32 export class UnknownProject extends ApiError { 33 static code = 404; 34 static defaultMessage = 'No such project'; 35 } 36 37 export class UnknownJob extends ApiError { 38 static code = 404; 39 static defaultMessage = 'No such job'; 40 } 41 42 export class UnknownTask extends ApiError { 43 static code = 404; 44 static defaultMessage = 'No such task'; 45 } 46 47 export class UnknownArtifact extends ApiError { 48 static code = 404; 49 static defaultMessage = 'No such artifact'; 50 } 51 52 export class ArtifactNotStored extends ApiError { 53 static code = 404; 54 static defaultMessage = 'That artifact is no longer stored'; 55 } 56 57 export class NotFound extends ApiError { 58 static code = 404; 59 static defaultMessage = 'No such endpoint'; 60 } 61 62 // --- refused --- 63 64 export class InvalidSignature extends ApiError { 65 static code = 401; 66 static defaultMessage = 'The request signature is missing or wrong'; 67 } 68 69 export class InvalidWorkerToken extends ApiError { 70 static code = 401; 71 static defaultMessage = 'The worker token is missing or wrong'; 72 } 73 74 export class NotAuthenticated extends ApiError { 75 static code = 401; 76 static defaultMessage = 'This endpoint needs an access token'; 77 } 78 79 // Only for something the caller is allowed to know exists. Anything else is 80 // a 404, so identifiers cannot be probed. 81 export class NotPermitted extends ApiError { 82 static code = 403; 83 static defaultMessage = 'That is not yours to do'; 84 } 85 86 export class Conflict extends ApiError { 87 static code = 409; 88 static defaultMessage = 'That conflicts with something that already exists'; 89 } 90 91 // 403 reaching for a task it never held, 409 losing one mid-report. 92 export class TaskNotHeld extends ApiError { 93 static code = 403; 94 static defaultMessage = 'That task is not held by this worker'; 95 } 96 97 // --- not actionable as it stands --- 98 99 export class ProjectDisabled extends ApiError { 100 static code = 409; 101 static defaultMessage = 'That project is disabled'; 102 } 103 104 export class TaskNotRunning extends ApiError { 105 static code = 409; 106 static defaultMessage = 'That task is not running'; 107 } 108 109 // Carries details.expected_offset, which is what makes a resume safe. 110 export class LogOffsetConflict extends ApiError { 111 static code = 409; 112 static defaultMessage = 'The log chunk does not continue from the end of the log'; 113 } 114 115 // --- malformed --- 116 117 export class MalformedJson extends ApiError { 118 static code = 400; 119 static defaultMessage = 'The request body is not valid JSON'; 120 } 121 122 export class NoCommitInPayload extends ApiError { 123 static code = 400; 124 static defaultMessage = 125 'Could not read a commit from the payload; expected sha, or a GitHub, Gitea or GitLab push event'; 126 } 127 128 export class InvalidSha extends ApiError { 129 static code = 400; 130 static defaultMessage = 'That is not a full commit id'; 131 } 132 133 export class ShaRequired extends ApiError { 134 static code = 400; 135 static defaultMessage = 'sha is required'; 136 } 137 138 export class InvalidLogOffset extends ApiError { 139 static code = 400; 140 static defaultMessage = 'x-log-offset must be an integer'; 141 } 142 143 export class InvalidArtifactPath extends ApiError { 144 static code = 400; 145 static defaultMessage = 'x-artifact-path is missing or unusable'; 146 } 147 148 export class ArtifactSizeMismatch extends ApiError { 149 static code = 400; 150 static defaultMessage = 'The artifact body does not match its declared length'; 151 } 152 153 export class ContentLengthRequired extends ApiError { 154 static code = 411; 155 static defaultMessage = 'content-length is required for artifact uploads'; 156 } 157 158 export class LogChunkTooLarge extends ApiError { 159 static code = 413; 160 static defaultMessage = 'That log chunk is too large'; 161 }