refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View File

@@ -0,0 +1,30 @@
{
"name": "@fluxer/errors",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
"./*": "./*"
},
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@fluxer/config": "workspace:*",
"@fluxer/constants": "workspace:*",
"@fluxer/hono_types": "workspace:*",
"@fluxer/i18n": "workspace:*",
"@fluxer/logger": "workspace:*",
"@fluxer/sentry": "workspace:*",
"@fluxer/telemetry": "workspace:*",
"hono": "catalog:"
},
"devDependencies": {
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"vite-tsconfig-paths": "catalog:",
"vitest": "catalog:"
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CaptchaRequiredError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CAPTCHA_REQUIRED});
this.name = 'CaptchaRequiredError';
}
}
export class InvalidCaptchaError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.INVALID_CAPTCHA});
this.name = 'InvalidCaptchaError';
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {createJsonErrorResponse, createXmlErrorResponse} from '@fluxer/errors/src/error_handling/ErrorResponse';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import type {Context, ErrorHandler} from 'hono';
import {HTTPException} from 'hono/http-exception';
const HTTP_STATUS_TO_ERROR_CODE: Record<number, string> = {
400: 'BAD_REQUEST',
403: 'FORBIDDEN',
404: 'NOT_FOUND',
405: 'METHOD_NOT_ALLOWED',
409: 'CONFLICT',
410: 'GONE',
500: 'INTERNAL_SERVER_ERROR',
501: 'NOT_IMPLEMENTED',
502: 'BAD_GATEWAY',
503: 'SERVICE_UNAVAILABLE',
504: 'GATEWAY_TIMEOUT',
};
export type ResponseFormat = 'json' | 'xml';
export interface ErrorHandlerOptions {
logError?: (error: Error, context: Context) => void;
includeStack?: boolean;
responseFormat?: ResponseFormat;
customHandler?: (error: Error, context: Context) => Response | Promise<Response> | undefined;
}
export function createErrorHandler(options: ErrorHandlerOptions = {}): ErrorHandler {
const {logError, includeStack = false, responseFormat = 'json', customHandler} = options;
return async (error: Error, c: Context): Promise<Response> => {
if (logError) {
logError(error, c);
}
if (customHandler) {
const customResponse = await customHandler(error, c);
if (customResponse) {
return customResponse;
}
}
if (error instanceof FluxerError) {
return error.getResponse();
}
if (error instanceof HTTPException) {
const status = error.status;
const code = HTTP_STATUS_TO_ERROR_CODE[status] ?? 'GENERAL_ERROR';
const message = error.message || 'An error occurred';
if (responseFormat === 'xml') {
return createXmlErrorResponse(status, code, message);
}
return createJsonErrorResponse({
status,
code,
message,
data: includeStack ? {stack: error.stack} : undefined,
});
}
const status = HttpStatus.INTERNAL_SERVER_ERROR;
const message = includeStack ? error.message : 'Something went wrong. Please try again later.';
if (responseFormat === 'xml') {
return createXmlErrorResponse(status, 'INTERNAL_SERVER_ERROR', message);
}
return createJsonErrorResponse({
status,
code: 'INTERNAL_SERVER_ERROR',
message,
data: includeStack ? {stack: error.stack} : undefined,
});
};
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {HTTPException} from 'hono/http-exception';
export type FluxerErrorData = Record<string, unknown>;
export type FluxerErrorStatus = HTTPException['status'];
export interface FluxerErrorOptions {
code: string;
message?: string;
status: FluxerErrorStatus;
data?: FluxerErrorData;
headers?: Record<string, string>;
messageVariables?: Record<string, unknown>;
cause?: Error;
}
export class FluxerError extends HTTPException {
readonly code: string;
override readonly message: string;
override readonly status: FluxerErrorStatus;
readonly data?: FluxerErrorData;
readonly headers?: Record<string, string>;
readonly messageVariables?: Record<string, unknown>;
constructor(options: FluxerErrorOptions) {
const resolvedMessage = options.message ?? options.code;
super(options.status, {message: resolvedMessage, cause: options.cause});
this.code = options.code;
this.message = resolvedMessage;
this.status = options.status;
this.data = options.data;
this.headers = options.headers;
this.messageVariables = options.messageVariables;
this.name = 'FluxerError';
}
override getResponse(): Response {
return new Response(
JSON.stringify({
code: this.code,
message: this.message,
...this.data,
}),
{
status: this.status,
headers: {
'Content-Type': 'application/json',
...this.headers,
},
},
);
}
toJSON(): Record<string, unknown> {
return {
code: this.code,
message: this.message,
...this.data,
};
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {FluxerError, type FluxerErrorData} from '@fluxer/errors/src/FluxerError';
interface HttpErrorOptions {
code?: string;
message?: string;
data?: FluxerErrorData;
headers?: Record<string, string>;
cause?: Error;
}
export class BadRequestError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.BAD_REQUEST,
message: options.message ?? 'Bad Request',
status: HttpStatus.BAD_REQUEST,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'BadRequestError';
}
}
export class UnauthorizedError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.UNAUTHORIZED,
message: options.message ?? 'Unauthorized',
status: HttpStatus.UNAUTHORIZED,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'UnauthorizedError';
}
}
export class ForbiddenError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.FORBIDDEN,
message: options.message ?? 'Forbidden',
status: HttpStatus.FORBIDDEN,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'ForbiddenError';
}
}
export class NotFoundError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.NOT_FOUND,
message: options.message ?? 'Not Found',
status: HttpStatus.NOT_FOUND,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'NotFoundError';
}
}
export class MethodNotAllowedError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.METHOD_NOT_ALLOWED,
message: options.message ?? 'Method Not Allowed',
status: HttpStatus.METHOD_NOT_ALLOWED,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'MethodNotAllowedError';
}
}
export class ConflictError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.CONFLICT,
message: options.message ?? 'Conflict',
status: HttpStatus.CONFLICT,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'ConflictError';
}
}
export class GoneError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.GONE,
message: options.message ?? 'Gone',
status: HttpStatus.GONE,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'GoneError';
}
}
export class InternalServerError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.INTERNAL_SERVER_ERROR,
message: options.message ?? 'Internal Server Error',
status: HttpStatus.INTERNAL_SERVER_ERROR,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'InternalServerError';
}
}
export class NotImplementedError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.NOT_IMPLEMENTED,
message: options.message ?? 'Not Implemented',
status: HttpStatus.NOT_IMPLEMENTED,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'NotImplementedError';
}
}
export class ServiceUnavailableError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.SERVICE_UNAVAILABLE,
message: options.message ?? 'Service Unavailable',
status: HttpStatus.SERVICE_UNAVAILABLE,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'ServiceUnavailableError';
}
}
export class BadGatewayError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.BAD_GATEWAY,
message: options.message ?? 'Bad Gateway',
status: HttpStatus.BAD_GATEWAY,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'BadGatewayError';
}
}
export class GatewayTimeoutError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
code: options.code ?? APIErrorCodes.GATEWAY_TIMEOUT,
message: options.message ?? 'Gateway Timeout',
status: HttpStatus.GATEWAY_TIMEOUT,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'GatewayTimeoutError';
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
export interface FieldError {
field: string;
code: string;
message: string;
}
export interface ValidationErrorOptions {
code?: string;
message?: string;
errors: Array<FieldError>;
}
export class ValidationError extends FluxerError {
readonly errors: Array<FieldError>;
constructor(options: ValidationErrorOptions) {
super({
code: options.code ?? APIErrorCodes.VALIDATION_ERROR,
message: options.message ?? 'Validation failed',
status: HttpStatus.BAD_REQUEST,
data: {errors: options.errors},
});
this.name = 'ValidationError';
this.errors = options.errors;
}
override getResponse(): Response {
return new Response(
JSON.stringify({
code: this.code,
message: this.message,
errors: this.errors,
}),
{
status: this.status,
headers: {
'Content-Type': 'application/json',
},
},
);
}
static fromField(field: string, code: string, message: string): ValidationError {
return new ValidationError({
errors: [{field, code, message}],
});
}
static fromFields(errors: Array<FieldError>): ValidationError {
return new ValidationError({errors});
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {Locales} from '@fluxer/constants/src/Locales';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
import {AppErrorHandler} from '@fluxer/errors/src/domains/core/ErrorHandlers';
import type {BaseHonoEnv} from '@fluxer/hono_types/src/HonoTypes';
import {Hono} from 'hono';
import {describe, expect, it} from 'vitest';
interface ErrorResponse {
code: string;
message: string;
}
function createApp(): Hono<BaseHonoEnv> {
const app = new Hono<BaseHonoEnv>();
app.onError(AppErrorHandler);
return app;
}
describe('AppErrorHandler i18n fallbacks', () => {
it('localizes unexpected errors from Accept-Language when middleware locale is missing', async () => {
const app = createApp();
app.get('/test', () => {
throw new Error('boom');
});
const response = await app.request('/test', {
headers: {
'accept-language': 'fr-CA,fr;q=0.9,en;q=0.8',
},
});
expect(response.status).toBe(500);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.INTERNAL_SERVER_ERROR);
expect(body.message).toBe('Erreur interne du serveur.');
});
it('localizes FluxerError responses without errorI18nService in context', async () => {
const app = createApp();
app.get('/test', () => {
throw new BadRequestError({code: APIErrorCodes.BAD_REQUEST});
});
const response = await app.request('/test', {
headers: {
'accept-language': 'fr',
},
});
expect(response.status).toBe(400);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.BAD_REQUEST);
expect(body.message).toBe('Requête invalide.');
});
it('prefers requestLocale context over Accept-Language header', async () => {
const app = createApp();
app.use('*', async (ctx, next) => {
ctx.set('requestLocale', Locales.EN_US);
await next();
});
app.get('/test', () => {
throw new Error('boom');
});
const response = await app.request('/test', {
headers: {
'accept-language': 'fr',
},
});
expect(response.status).toBe(500);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.INTERNAL_SERVER_ERROR);
expect(body.message).toBe('Internal server error.');
});
});

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {CaptchaRequiredError, InvalidCaptchaError} from '@fluxer/errors/src/CaptchaErrors';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {ErrorCodeToI18nKey} from '@fluxer/errors/src/i18n/ErrorCodeMappings';
import {getErrorMessage} from '@fluxer/errors/src/i18n/ErrorI18n';
import type {ErrorI18nKey} from '@fluxer/errors/src/i18n/ErrorI18nTypes.generated';
import {describe, expect, it} from 'vitest';
describe('CaptchaErrors', () => {
describe('CaptchaRequiredError', () => {
it('should have correct code and name', () => {
const error = new CaptchaRequiredError();
expect(error.code).toBe('CAPTCHA_REQUIRED');
expect(error.name).toBe('CaptchaRequiredError');
});
it('should have status 400', () => {
const error = new CaptchaRequiredError();
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
});
it('should extend BadRequestError', () => {
const error = new CaptchaRequiredError();
expect(error).toBeInstanceOf(BadRequestError);
});
it('should extend FluxerError', () => {
const error = new CaptchaRequiredError();
expect(error).toBeInstanceOf(FluxerError);
});
it('should have an i18n mapping that resolves to the correct message', () => {
const error = new CaptchaRequiredError();
const i18nKey = ErrorCodeToI18nKey[error.code as keyof typeof ErrorCodeToI18nKey] as ErrorI18nKey;
expect(i18nKey).toBe('captcha.required');
expect(getErrorMessage(i18nKey, 'en-US')).toBe('Captcha is required.');
});
});
describe('InvalidCaptchaError', () => {
it('should have correct code and name', () => {
const error = new InvalidCaptchaError();
expect(error.code).toBe('INVALID_CAPTCHA');
expect(error.name).toBe('InvalidCaptchaError');
});
it('should have status 400', () => {
const error = new InvalidCaptchaError();
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
});
it('should extend BadRequestError', () => {
const error = new InvalidCaptchaError();
expect(error).toBeInstanceOf(BadRequestError);
});
it('should extend FluxerError', () => {
const error = new InvalidCaptchaError();
expect(error).toBeInstanceOf(FluxerError);
});
it('should have an i18n mapping that resolves to the correct message', () => {
const error = new InvalidCaptchaError();
const i18nKey = ErrorCodeToI18nKey[error.code as keyof typeof ErrorCodeToI18nKey] as ErrorI18nKey;
expect(i18nKey).toBe('captcha.invalid');
expect(getErrorMessage(i18nKey, 'en-US')).toBe('Invalid captcha.');
});
});
describe('error differentiation', () => {
it('should have different codes for required vs invalid', () => {
const requiredError = new CaptchaRequiredError();
const invalidError = new InvalidCaptchaError();
expect(requiredError.code).not.toBe(invalidError.code);
});
it('should have different i18n messages for required vs invalid', () => {
const requiredKey = ErrorCodeToI18nKey['CAPTCHA_REQUIRED'] as ErrorI18nKey;
const invalidKey = ErrorCodeToI18nKey['INVALID_CAPTCHA'] as ErrorI18nKey;
expect(getErrorMessage(requiredKey, 'en-US')).not.toBe(getErrorMessage(invalidKey, 'en-US'));
});
it('should have different names for required vs invalid', () => {
const requiredError = new CaptchaRequiredError();
const invalidError = new InvalidCaptchaError();
expect(requiredError.name).not.toBe(invalidError.name);
});
});
});

View File

@@ -0,0 +1,379 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {InvalidPhoneNumberError} from '@fluxer/errors/src/domains/auth/InvalidPhoneNumberError';
import {UnknownChannelError} from '@fluxer/errors/src/domains/channel/UnknownChannelError';
import {UnknownMessageError} from '@fluxer/errors/src/domains/channel/UnknownMessageError';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import {InternalServerError} from '@fluxer/errors/src/domains/core/InternalServerError';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {describe, expect, it} from 'vitest';
interface ErrorResponse {
code: string;
message: string;
[key: string]: unknown;
}
describe('Domain Errors', () => {
describe('core domain errors', () => {
describe('BadRequestError', () => {
it('should create error with required code', () => {
const error = new BadRequestError({code: APIErrorCodes.INVALID_REQUEST});
expect(error.status).toBe(400);
expect(error.code).toBe(APIErrorCodes.INVALID_REQUEST);
expect(error.message).toBe(APIErrorCodes.INVALID_REQUEST);
});
it('should allow custom message', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_FORM_BODY,
message: 'Custom bad request message',
});
expect(error.message).toBe('Custom bad request message');
});
it('should include data', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
data: {field: 'test'},
});
expect(error.data).toEqual({field: 'test'});
});
it('should include headers', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
headers: {'X-Custom': 'value'},
});
expect(error.headers).toEqual({'X-Custom': 'value'});
});
it('should include messageVariables for i18n', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
messageVariables: {count: 5},
});
expect(error.messageVariables).toEqual({count: 5});
});
it('should be instance of FluxerError', () => {
const error = new BadRequestError({code: APIErrorCodes.INVALID_REQUEST});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('NotFoundError', () => {
it('should create error with status 404', () => {
const error = new NotFoundError({code: APIErrorCodes.UNKNOWN_USER});
expect(error.status).toBe(404);
expect(error.code).toBe(APIErrorCodes.UNKNOWN_USER);
expect(error.message).toBe(APIErrorCodes.UNKNOWN_USER);
});
it('should allow messageVariables for i18n', () => {
const error = new NotFoundError({
code: APIErrorCodes.UNKNOWN_USER,
messageVariables: {userId: '12345'},
});
expect(error.messageVariables).toEqual({userId: '12345'});
});
it('should be instance of FluxerError', () => {
const error = new NotFoundError({code: APIErrorCodes.UNKNOWN_USER});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('ForbiddenError', () => {
it('should create error with status 403', () => {
const error = new ForbiddenError({code: APIErrorCodes.ACCESS_DENIED});
expect(error.status).toBe(403);
expect(error.code).toBe(APIErrorCodes.ACCESS_DENIED);
expect(error.message).toBe(APIErrorCodes.ACCESS_DENIED);
});
it('should be instance of FluxerError', () => {
const error = new ForbiddenError({code: APIErrorCodes.ACCESS_DENIED});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('InternalServerError', () => {
it('should create error with status 500', () => {
const error = new InternalServerError({code: APIErrorCodes.GENERAL_ERROR});
expect(error.status).toBe(500);
expect(error.code).toBe(APIErrorCodes.GENERAL_ERROR);
expect(error.message).toBe(APIErrorCodes.GENERAL_ERROR);
});
it('should be instance of FluxerError', () => {
const error = new InternalServerError({code: APIErrorCodes.GENERAL_ERROR});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('InputValidationError', () => {
it('should create error with validation errors', () => {
const error = new InputValidationError([{path: 'email', message: 'Invalid email format'}]);
expect(error.status).toBe(400);
expect(error.code).toBe(APIErrorCodes.INVALID_FORM_BODY);
expect(error.data).toEqual({
errors: [{path: 'email', message: 'Invalid email format'}],
});
});
it('should support localized errors', () => {
const error = new InputValidationError(
[{path: 'name', message: ValidationErrorCodes.EMAIL_IS_REQUIRED}],
[{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}],
);
expect(error.localizedErrors).toEqual([{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}]);
expect(error.getLocalizedErrors()).toEqual([{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}]);
});
it('should return null for localizedErrors when not provided', () => {
const error = new InputValidationError([{path: 'field', message: 'error'}]);
expect(error.localizedErrors).toBeNull();
expect(error.getLocalizedErrors()).toBeNull();
});
it('should create from single field using static method', () => {
const error = InputValidationError.create('username', 'Username is required');
expect(error).toBeInstanceOf(InputValidationError);
expect(error.data).toEqual({
errors: [{path: 'username', message: 'Username is required'}],
});
});
it('should create from multiple fields using static method', () => {
const error = InputValidationError.createMultiple([
{field: 'email', message: 'Invalid email'},
{field: 'password', message: 'Password too short'},
]);
expect(error.data).toEqual({
errors: [
{path: 'email', message: 'Invalid email'},
{path: 'password', message: 'Password too short'},
],
});
});
it('should create from error code using static method', () => {
const error = InputValidationError.fromCode('email', ValidationErrorCodes.EMAIL_IS_REQUIRED, {maxLength: 255});
expect(error.localizedErrors).toEqual([
{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED, variables: {maxLength: 255}},
]);
});
it('should create from multiple error codes using static method', () => {
const error = InputValidationError.fromCodes([
{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED},
{path: 'name', code: ValidationErrorCodes.STRING_LENGTH_INVALID, variables: {max: 100}},
]);
expect(error.localizedErrors).toHaveLength(2);
});
});
});
describe('auth domain errors', () => {
describe('InvalidPhoneNumberError', () => {
it('should have correct code from APIErrorCodes', () => {
const error = new InvalidPhoneNumberError();
expect(error.code).toBe(APIErrorCodes.INVALID_PHONE_NUMBER);
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
});
it('should be instance of BadRequestError', () => {
const error = new InvalidPhoneNumberError();
expect(error).toBeInstanceOf(BadRequestError);
});
it('should be instance of FluxerError', () => {
const error = new InvalidPhoneNumberError();
expect(error).toBeInstanceOf(FluxerError);
});
});
});
describe('channel domain errors', () => {
describe('UnknownChannelError', () => {
it('should have correct code from APIErrorCodes', () => {
const error = new UnknownChannelError();
expect(error.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
expect(error.status).toBe(HttpStatus.NOT_FOUND);
});
it('should be instance of NotFoundError', () => {
const error = new UnknownChannelError();
expect(error).toBeInstanceOf(NotFoundError);
});
it('should be instance of FluxerError', () => {
const error = new UnknownChannelError();
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('UnknownMessageError', () => {
it('should have correct code from APIErrorCodes', () => {
const error = new UnknownMessageError();
expect(error.code).toBe(APIErrorCodes.UNKNOWN_MESSAGE);
expect(error.status).toBe(HttpStatus.NOT_FOUND);
});
it('should be instance of NotFoundError', () => {
const error = new UnknownMessageError();
expect(error).toBeInstanceOf(NotFoundError);
});
});
});
describe('error response generation', () => {
it('should generate correct JSON response for domain errors', async () => {
const error = new UnknownChannelError();
const response = error.getResponse();
expect(response.status).toBe(404);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
});
it('should include data in response', async () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
data: {field: 'test', reason: 'invalid'},
});
const response = error.getResponse();
const body = (await response.json()) as ErrorResponse;
expect(body).toEqual({
code: APIErrorCodes.INVALID_REQUEST,
message: APIErrorCodes.INVALID_REQUEST,
field: 'test',
reason: 'invalid',
});
});
it('should include custom headers in response', async () => {
const error = new ForbiddenError({
code: APIErrorCodes.ACCESS_DENIED,
headers: {'X-Permission-Required': 'admin'},
});
const response = error.getResponse();
expect(response.headers.get('X-Permission-Required')).toBe('admin');
});
});
describe('error serialization', () => {
it('should serialize domain errors to JSON correctly', () => {
const error = new UnknownChannelError();
const json = error.toJSON();
expect(json).toEqual({
code: APIErrorCodes.UNKNOWN_CHANNEL,
message: APIErrorCodes.UNKNOWN_CHANNEL,
});
});
it('should include data in JSON serialization', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
data: {extra: 'info'},
});
const json = error.toJSON();
expect(json).toEqual({
code: APIErrorCodes.INVALID_REQUEST,
message: APIErrorCodes.INVALID_REQUEST,
extra: 'info',
});
});
});
describe('error inheritance chain', () => {
it('should maintain correct prototype chain', () => {
const error = new InvalidPhoneNumberError();
expect(error).toBeInstanceOf(InvalidPhoneNumberError);
expect(error).toBeInstanceOf(BadRequestError);
expect(error).toBeInstanceOf(FluxerError);
expect(error).toBeInstanceOf(Error);
});
it('should be catchable at any level of the chain', () => {
const error = new UnknownChannelError();
try {
throw error;
} catch (e) {
if (e instanceof NotFoundError) {
expect(e.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
}
}
try {
throw error;
} catch (e) {
if (e instanceof FluxerError) {
expect(e.status).toBe(404);
}
}
try {
throw error;
} catch (e) {
if (e instanceof Error) {
expect(e).toBeInstanceOf(UnknownChannelError);
}
}
});
});
});

View File

@@ -0,0 +1,359 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {createErrorHandler, type ErrorHandlerOptions} from '@fluxer/errors/src/ErrorHandler';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {Hono} from 'hono';
import {HTTPException} from 'hono/http-exception';
import {describe, expect, it, vi} from 'vitest';
interface ErrorResponse {
code: string;
message: string;
stack?: string;
[key: string]: unknown;
}
function createTestApp(options: ErrorHandlerOptions = {}) {
const app = new Hono();
app.onError(createErrorHandler(options));
return app;
}
describe('createErrorHandler', () => {
describe('FluxerError handling', () => {
it('should return FluxerError response directly', async () => {
const app = createTestApp();
app.get('/test', () => {
throw new FluxerError({
code: 'TEST_ERROR',
message: 'Test error message',
status: 400,
});
});
const response = await app.request('/test');
expect(response.status).toBe(400);
const body = (await response.json()) as ErrorResponse;
expect(body).toEqual({
code: 'TEST_ERROR',
message: 'Test error message',
});
});
it('should include FluxerError data in response', async () => {
const app = createTestApp();
app.get('/test', () => {
throw new FluxerError({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
status: 400,
data: {field: 'email'},
});
});
const response = await app.request('/test');
const body = (await response.json()) as ErrorResponse;
expect(body).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
field: 'email',
});
});
it('should include FluxerError custom headers', async () => {
const app = createTestApp();
app.get('/test', () => {
throw new FluxerError({
code: 'RATE_LIMITED',
status: 429,
headers: {'Retry-After': '60'},
});
});
const response = await app.request('/test');
expect(response.headers.get('Retry-After')).toBe('60');
});
});
describe('HTTPException handling', () => {
it('should handle HTTPException with JSON response', async () => {
const app = createTestApp();
app.get('/test', () => {
throw new HTTPException(403, {message: 'Access denied'});
});
const response = await app.request('/test');
expect(response.status).toBe(403);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe('FORBIDDEN');
expect(body.message).toBe('Access denied');
});
it('should use default message for HTTPException without message', async () => {
const app = createTestApp();
app.get('/test', () => {
throw new HTTPException(500);
});
const response = await app.request('/test');
expect(response.status).toBe(500);
const body = (await response.json()) as ErrorResponse;
expect(body.message).toBe('An error occurred');
});
});
describe('generic Error handling', () => {
it('should return 500 for generic errors', async () => {
const app = createTestApp();
app.get('/test', () => {
throw new Error('Something went wrong');
});
const response = await app.request('/test');
expect(response.status).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe('INTERNAL_SERVER_ERROR');
expect(body.message).toBe('Something went wrong. Please try again later.');
});
it('should not expose error message without includeStack option', async () => {
const app = createTestApp({includeStack: false});
app.get('/test', () => {
throw new Error('Sensitive error details');
});
const response = await app.request('/test');
const body = (await response.json()) as ErrorResponse;
expect(body.message).toBe('Something went wrong. Please try again later.');
expect(body.message).not.toContain('Sensitive');
});
it('should expose error message with includeStack option', async () => {
const app = createTestApp({includeStack: true});
app.get('/test', () => {
throw new Error('Error details for debugging');
});
const response = await app.request('/test');
const body = (await response.json()) as ErrorResponse;
expect(body.message).toBe('Error details for debugging');
});
it('should include stack trace with includeStack option', async () => {
const app = createTestApp({includeStack: true});
app.get('/test', () => {
throw new Error('Test error');
});
const response = await app.request('/test');
const body = (await response.json()) as ErrorResponse;
expect(body).toHaveProperty('stack');
expect(body.stack).toContain('Error: Test error');
});
});
describe('logError callback', () => {
it('should call logError with error and context', async () => {
const logError = vi.fn();
const app = createTestApp({logError});
app.get('/test', () => {
throw new Error('Logged error');
});
await app.request('/test');
expect(logError).toHaveBeenCalledTimes(1);
expect(logError.mock.calls[0][0]).toBeInstanceOf(Error);
expect((logError.mock.calls[0][0] as Error).message).toBe('Logged error');
});
it('should call logError for FluxerError', async () => {
const logError = vi.fn();
const app = createTestApp({logError});
app.get('/test', () => {
throw new FluxerError({code: 'TEST', status: 400});
});
await app.request('/test');
expect(logError).toHaveBeenCalledTimes(1);
expect(logError.mock.calls[0][0]).toBeInstanceOf(FluxerError);
});
});
describe('customHandler callback', () => {
it('should use customHandler response when provided', async () => {
const customHandler = vi.fn().mockReturnValue(
new Response(JSON.stringify({custom: true}), {
status: 418,
headers: {'Content-Type': 'application/json'},
}),
);
const app = createTestApp({customHandler});
app.get('/test', () => {
throw new Error('Custom handled');
});
const response = await app.request('/test');
expect(response.status).toBe(418);
const body = (await response.json()) as {custom: boolean};
expect(body).toEqual({custom: true});
expect(customHandler).toHaveBeenCalledTimes(1);
});
it('should fall back to default handling when customHandler returns undefined', async () => {
const customHandler = vi.fn().mockReturnValue(undefined);
const app = createTestApp({customHandler});
app.get('/test', () => {
throw new FluxerError({code: 'FALLBACK', status: 400});
});
const response = await app.request('/test');
expect(response.status).toBe(400);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe('FALLBACK');
});
it('should support async customHandler', async () => {
const customHandler = vi.fn().mockResolvedValue(
new Response(JSON.stringify({async: true}), {
status: 202,
headers: {'Content-Type': 'application/json'},
}),
);
const app = createTestApp({customHandler});
app.get('/test', () => {
throw new Error('Async handled');
});
const response = await app.request('/test');
expect(response.status).toBe(202);
const body = (await response.json()) as {async: boolean};
expect(body).toEqual({async: true});
});
});
describe('responseFormat option', () => {
it('should return JSON by default', async () => {
const app = createTestApp();
app.get('/test', () => {
throw new HTTPException(400);
});
const response = await app.request('/test');
expect(response.headers.get('Content-Type')).toBe('application/json');
});
it('should return XML when responseFormat is xml', async () => {
const app = createTestApp({responseFormat: 'xml'});
app.get('/test', () => {
throw new HTTPException(400, {message: 'Bad request'});
});
const response = await app.request('/test');
expect(response.status).toBe(400);
expect(response.headers.get('Content-Type')).toBe('application/xml');
const body = await response.text();
expect(body).toContain('<?xml version="1.0"');
expect(body).toContain('<Error>');
expect(body).toContain('<Code>BAD_REQUEST</Code>');
expect(body).toContain('<Message>Bad request</Message>');
});
it('should escape XML special characters', async () => {
const app = createTestApp({responseFormat: 'xml'});
app.get('/test', () => {
throw new HTTPException(400, {message: 'Error with <special> & "chars"'});
});
const response = await app.request('/test');
const body = await response.text();
expect(body).toContain('&lt;special&gt;');
expect(body).toContain('&amp;');
expect(body).toContain('&quot;chars&quot;');
});
it('should return XML for internal errors when responseFormat is xml', async () => {
const app = createTestApp({responseFormat: 'xml'});
app.get('/test', () => {
throw new Error('Internal error');
});
const response = await app.request('/test');
expect(response.status).toBe(500);
expect(response.headers.get('Content-Type')).toBe('application/xml');
const body = await response.text();
expect(body).toContain('<Code>INTERNAL_SERVER_ERROR</Code>');
});
});
describe('combined options', () => {
it('should support logError and includeStack together', async () => {
const logError = vi.fn();
const app = createTestApp({logError, includeStack: true});
app.get('/test', () => {
throw new Error('Combined test');
});
const response = await app.request('/test');
const body = (await response.json()) as ErrorResponse;
expect(logError).toHaveBeenCalledTimes(1);
expect(body.message).toBe('Combined test');
expect(body).toHaveProperty('stack');
});
it('should call logError before customHandler', async () => {
const callOrder: Array<string> = [];
const logError = vi.fn(() => callOrder.push('log'));
const customHandler = vi.fn(() => {
callOrder.push('custom');
return undefined;
});
const app = createTestApp({logError, customHandler});
app.get('/test', () => {
throw new Error('Order test');
});
await app.request('/test');
expect(callOrder).toEqual(['log', 'custom']);
});
});
});

View File

@@ -0,0 +1,273 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {HTTPException} from 'hono/http-exception';
import {describe, expect, it} from 'vitest';
describe('FluxerError', () => {
describe('constructor', () => {
it('should create an error with required options', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
});
expect(error.code).toBe('TEST_ERROR');
expect(error.status).toBe(400);
expect(error.message).toBe('TEST_ERROR');
expect(error.name).toBe('FluxerError');
});
it('should use provided message instead of code', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
message: 'Custom error message',
status: 400,
});
expect(error.code).toBe('TEST_ERROR');
expect(error.message).toBe('Custom error message');
});
it('should include optional data', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
data: {field: 'username', reason: 'invalid'},
});
expect(error.data).toEqual({field: 'username', reason: 'invalid'});
});
it('should include optional headers', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
headers: {'X-Custom-Header': 'value'},
});
expect(error.headers).toEqual({'X-Custom-Header': 'value'});
});
it('should include message variables for i18n', () => {
const error = new FluxerError({
code: 'RATE_LIMITED',
status: 429,
messageVariables: {retryAfter: 60},
});
expect(error.messageVariables).toEqual({retryAfter: 60});
});
it('should include cause for error chaining', () => {
const cause = new Error('Original error');
const error = new FluxerError({
code: 'WRAPPED_ERROR',
status: 500,
cause,
});
expect(error.cause).toBe(cause);
});
it('should be an instance of HTTPException', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
});
expect(error).toBeInstanceOf(HTTPException);
});
});
describe('getResponse', () => {
it('should return a JSON Response with correct status', async () => {
const error = new FluxerError({
code: 'TEST_ERROR',
message: 'Test message',
status: 400,
});
const response = error.getResponse();
expect(response.status).toBe(400);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = await response.json();
expect(body).toEqual({
code: 'TEST_ERROR',
message: 'Test message',
});
});
it('should include data in response body', async () => {
const error = new FluxerError({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
status: 400,
data: {errors: [{field: 'email', message: 'Invalid email'}]},
});
const response = error.getResponse();
const body = await response.json();
expect(body).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
errors: [{field: 'email', message: 'Invalid email'}],
});
});
it('should include custom headers in response', async () => {
const error = new FluxerError({
code: 'RATE_LIMITED',
status: 429,
headers: {'Retry-After': '60', 'X-RateLimit-Reset': '1234567890'},
});
const response = error.getResponse();
expect(response.headers.get('Retry-After')).toBe('60');
expect(response.headers.get('X-RateLimit-Reset')).toBe('1234567890');
expect(response.headers.get('Content-Type')).toBe('application/json');
});
it('should handle empty data', async () => {
const error = new FluxerError({
code: 'SIMPLE_ERROR',
status: 403,
});
const response = error.getResponse();
const body = await response.json();
expect(body).toEqual({
code: 'SIMPLE_ERROR',
message: 'SIMPLE_ERROR',
});
});
});
describe('toJSON', () => {
it('should serialize to JSON object', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
message: 'Test message',
status: 400,
});
const json = error.toJSON();
expect(json).toEqual({
code: 'TEST_ERROR',
message: 'Test message',
});
});
it('should include data in JSON output', () => {
const error = new FluxerError({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
status: 400,
data: {field: 'username'},
});
const json = error.toJSON();
expect(json).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
field: 'username',
});
});
it('should not include status or headers in JSON output', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 500,
headers: {'X-Custom': 'value'},
});
const json = error.toJSON();
expect(json).not.toHaveProperty('status');
expect(json).not.toHaveProperty('headers');
});
});
describe('status codes', () => {
it('should handle 4xx client error status codes', () => {
const testCases = [
{status: 400, name: 'Bad Request'},
{status: 401, name: 'Unauthorized'},
{status: 403, name: 'Forbidden'},
{status: 404, name: 'Not Found'},
{status: 409, name: 'Conflict'},
{status: 429, name: 'Too Many Requests'},
] as const;
for (const {status} of testCases) {
const error = new FluxerError({code: 'TEST', status});
expect(error.status).toBe(status);
}
});
it('should handle 5xx server error status codes', () => {
const testCases = [
{status: 500, name: 'Internal Server Error'},
{status: 501, name: 'Not Implemented'},
{status: 502, name: 'Bad Gateway'},
{status: 503, name: 'Service Unavailable'},
{status: 504, name: 'Gateway Timeout'},
] as const;
for (const {status} of testCases) {
const error = new FluxerError({code: 'TEST', status});
expect(error.status).toBe(status);
}
});
});
describe('error properties', () => {
it('should have correct name property', () => {
const error = new FluxerError({code: 'TEST', status: 400});
expect(error.name).toBe('FluxerError');
});
it('should be throwable', () => {
const error = new FluxerError({code: 'THROWN_ERROR', status: 400});
expect(() => {
throw error;
}).toThrow(FluxerError);
});
it('should be catchable as Error', () => {
const error = new FluxerError({code: 'CAUGHT_ERROR', status: 400});
try {
throw error;
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e).toBeInstanceOf(FluxerError);
}
});
});
});

View File

@@ -0,0 +1,342 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {
BadGatewayError,
BadRequestError,
ConflictError,
ForbiddenError,
GatewayTimeoutError,
GoneError,
InternalServerError,
MethodNotAllowedError,
NotFoundError,
NotImplementedError,
ServiceUnavailableError,
UnauthorizedError,
} from '@fluxer/errors/src/HttpErrors';
import {describe, expect, it} from 'vitest';
describe('HttpErrors', () => {
describe('BadRequestError', () => {
it('should have status 400 and default code/message', () => {
const error = new BadRequestError();
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
expect(error.code).toBe('BAD_REQUEST');
expect(error.message).toBe('Bad Request');
expect(error.name).toBe('BadRequestError');
});
it('should allow custom code', () => {
const error = new BadRequestError({code: 'INVALID_INPUT'});
expect(error.code).toBe('INVALID_INPUT');
expect(error.message).toBe('Bad Request');
});
it('should allow custom message', () => {
const error = new BadRequestError({message: 'Invalid request payload'});
expect(error.code).toBe('BAD_REQUEST');
expect(error.message).toBe('Invalid request payload');
});
it('should allow custom code and message', () => {
const error = new BadRequestError({
code: 'VALIDATION_FAILED',
message: 'Request validation failed',
});
expect(error.code).toBe('VALIDATION_FAILED');
expect(error.message).toBe('Request validation failed');
});
it('should include data', () => {
const error = new BadRequestError({
data: {field: 'email', reason: 'invalid format'},
});
expect(error.data).toEqual({field: 'email', reason: 'invalid format'});
});
it('should include headers', () => {
const error = new BadRequestError({
headers: {'X-Error-Type': 'validation'},
});
expect(error.headers).toEqual({'X-Error-Type': 'validation'});
});
it('should include cause', () => {
const cause = new Error('Original error');
const error = new BadRequestError({cause});
expect(error.cause).toBe(cause);
});
it('should be instance of FluxerError', () => {
const error = new BadRequestError();
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('UnauthorizedError', () => {
it('should have status 401 and default code/message', () => {
const error = new UnauthorizedError();
expect(error.status).toBe(HttpStatus.UNAUTHORIZED);
expect(error.code).toBe('UNAUTHORIZED');
expect(error.message).toBe('Unauthorized');
expect(error.name).toBe('UnauthorizedError');
});
it('should allow custom code', () => {
const error = new UnauthorizedError({code: 'INVALID_TOKEN'});
expect(error.code).toBe('INVALID_TOKEN');
});
it('should include WWW-Authenticate header', () => {
const error = new UnauthorizedError({
headers: {'WWW-Authenticate': 'Bearer realm="api"'},
});
expect(error.headers).toEqual({'WWW-Authenticate': 'Bearer realm="api"'});
});
});
describe('ForbiddenError', () => {
it('should have status 403 and default code/message', () => {
const error = new ForbiddenError();
expect(error.status).toBe(HttpStatus.FORBIDDEN);
expect(error.code).toBe('FORBIDDEN');
expect(error.message).toBe('Forbidden');
expect(error.name).toBe('ForbiddenError');
});
it('should allow custom code for permission errors', () => {
const error = new ForbiddenError({code: 'MISSING_PERMISSIONS'});
expect(error.code).toBe('MISSING_PERMISSIONS');
});
});
describe('NotFoundError', () => {
it('should have status 404 and default code/message', () => {
const error = new NotFoundError();
expect(error.status).toBe(HttpStatus.NOT_FOUND);
expect(error.code).toBe('NOT_FOUND');
expect(error.message).toBe('Not Found');
expect(error.name).toBe('NotFoundError');
});
it('should allow custom code for resource not found', () => {
const error = new NotFoundError({code: 'UNKNOWN_USER'});
expect(error.code).toBe('UNKNOWN_USER');
});
});
describe('MethodNotAllowedError', () => {
it('should have status 405 and default code/message', () => {
const error = new MethodNotAllowedError();
expect(error.status).toBe(HttpStatus.METHOD_NOT_ALLOWED);
expect(error.code).toBe('METHOD_NOT_ALLOWED');
expect(error.message).toBe('Method Not Allowed');
expect(error.name).toBe('MethodNotAllowedError');
});
it('should include Allow header', () => {
const error = new MethodNotAllowedError({
headers: {Allow: 'GET, POST'},
});
expect(error.headers).toEqual({Allow: 'GET, POST'});
});
});
describe('ConflictError', () => {
it('should have status 409 and default code/message', () => {
const error = new ConflictError();
expect(error.status).toBe(HttpStatus.CONFLICT);
expect(error.code).toBe('CONFLICT');
expect(error.message).toBe('Conflict');
expect(error.name).toBe('ConflictError');
});
it('should allow custom code for conflict scenarios', () => {
const error = new ConflictError({code: 'USERNAME_TAKEN'});
expect(error.code).toBe('USERNAME_TAKEN');
});
});
describe('GoneError', () => {
it('should have status 410 and default code/message', () => {
const error = new GoneError();
expect(error.status).toBe(HttpStatus.GONE);
expect(error.code).toBe('GONE');
expect(error.message).toBe('Gone');
expect(error.name).toBe('GoneError');
});
it('should allow custom code for deleted resources', () => {
const error = new GoneError({code: 'RESOURCE_DELETED'});
expect(error.code).toBe('RESOURCE_DELETED');
});
});
describe('InternalServerError', () => {
it('should have status 500 and default code/message', () => {
const error = new InternalServerError();
expect(error.status).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
expect(error.code).toBe('INTERNAL_SERVER_ERROR');
expect(error.message).toBe('Internal Server Error');
expect(error.name).toBe('InternalServerError');
});
it('should allow custom code', () => {
const error = new InternalServerError({code: 'DATABASE_ERROR'});
expect(error.code).toBe('DATABASE_ERROR');
});
it('should preserve cause for debugging', () => {
const cause = new Error('Database connection failed');
const error = new InternalServerError({cause});
expect(error.cause).toBe(cause);
});
});
describe('NotImplementedError', () => {
it('should have status 501 and default code/message', () => {
const error = new NotImplementedError();
expect(error.status).toBe(HttpStatus.NOT_IMPLEMENTED);
expect(error.code).toBe('NOT_IMPLEMENTED');
expect(error.message).toBe('Not Implemented');
expect(error.name).toBe('NotImplementedError');
});
});
describe('ServiceUnavailableError', () => {
it('should have status 503 and default code/message', () => {
const error = new ServiceUnavailableError();
expect(error.status).toBe(HttpStatus.SERVICE_UNAVAILABLE);
expect(error.code).toBe('SERVICE_UNAVAILABLE');
expect(error.message).toBe('Service Unavailable');
expect(error.name).toBe('ServiceUnavailableError');
});
it('should include Retry-After header', () => {
const error = new ServiceUnavailableError({
headers: {'Retry-After': '300'},
});
expect(error.headers).toEqual({'Retry-After': '300'});
});
});
describe('BadGatewayError', () => {
it('should have status 502 and default code/message', () => {
const error = new BadGatewayError();
expect(error.status).toBe(HttpStatus.BAD_GATEWAY);
expect(error.code).toBe('BAD_GATEWAY');
expect(error.message).toBe('Bad Gateway');
expect(error.name).toBe('BadGatewayError');
});
});
describe('GatewayTimeoutError', () => {
it('should have status 504 and default code/message', () => {
const error = new GatewayTimeoutError();
expect(error.status).toBe(HttpStatus.GATEWAY_TIMEOUT);
expect(error.code).toBe('GATEWAY_TIMEOUT');
expect(error.message).toBe('Gateway Timeout');
expect(error.name).toBe('GatewayTimeoutError');
});
});
describe('error inheritance', () => {
it('all HTTP errors should extend FluxerError', () => {
const errors = [
new BadRequestError(),
new UnauthorizedError(),
new ForbiddenError(),
new NotFoundError(),
new MethodNotAllowedError(),
new ConflictError(),
new GoneError(),
new InternalServerError(),
new NotImplementedError(),
new ServiceUnavailableError(),
new BadGatewayError(),
new GatewayTimeoutError(),
];
for (const error of errors) {
expect(error).toBeInstanceOf(FluxerError);
expect(error).toBeInstanceOf(Error);
}
});
});
describe('response generation', () => {
it('should generate correct response for each error type', async () => {
const testCases = [
{error: new BadRequestError(), expectedStatus: 400},
{error: new UnauthorizedError(), expectedStatus: 401},
{error: new ForbiddenError(), expectedStatus: 403},
{error: new NotFoundError(), expectedStatus: 404},
{error: new MethodNotAllowedError(), expectedStatus: 405},
{error: new ConflictError(), expectedStatus: 409},
{error: new GoneError(), expectedStatus: 410},
{error: new InternalServerError(), expectedStatus: 500},
{error: new NotImplementedError(), expectedStatus: 501},
{error: new BadGatewayError(), expectedStatus: 502},
{error: new ServiceUnavailableError(), expectedStatus: 503},
{error: new GatewayTimeoutError(), expectedStatus: 504},
];
for (const {error, expectedStatus} of testCases) {
const response = error.getResponse();
expect(response.status).toBe(expectedStatus);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = await response.json();
expect(body).toHaveProperty('code');
expect(body).toHaveProperty('message');
}
});
});
});

View File

@@ -0,0 +1,297 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {RateLimitError} from '@fluxer/errors/src/domains/core/RateLimitError';
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
interface RateLimitResponseBody {
code: string;
retry_after: number;
global: boolean;
}
describe('RateLimitError', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-27T12:00:00.000Z'));
});
afterEach(() => {
vi.useRealTimers();
});
describe('valid inputs', () => {
it('should create error with valid values', () => {
const error = new RateLimitError({
retryAfter: 5,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.code).toBe('RATE_LIMITED');
expect(error.status).toBe(429);
expect(error.data?.retry_after).toBe(5);
expect(error.headers?.['Retry-After']).toBe('5');
expect(error.headers?.['X-RateLimit-Limit']).toBe('10');
});
it('should use retryAfterDecimal when provided', () => {
const error = new RateLimitError({
retryAfter: 5,
retryAfterDecimal: 4.5,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.data?.retry_after).toBe(4.5);
expect(error.headers?.['Retry-After']).toBe('5');
});
it('should set global flag correctly', () => {
const globalError = new RateLimitError({
global: true,
retryAfter: 1,
limit: 50,
resetTime: new Date(Date.now() + 1000),
});
expect(globalError.data?.global).toBe(true);
expect(globalError.headers?.['X-RateLimit-Global']).toBe('true');
const bucketError = new RateLimitError({
global: false,
retryAfter: 1,
limit: 10,
resetTime: new Date(Date.now() + 1000),
});
expect(bucketError.data?.global).toBe(false);
expect(bucketError.headers?.['X-RateLimit-Global']).toBe('false');
});
});
describe('sanitization of invalid inputs', () => {
it('should handle undefined retryAfter', () => {
const error = new RateLimitError({
retryAfter: undefined as unknown as number,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.headers?.['Retry-After']).toBe('1');
expect(Number.isNaN(Number(error.headers?.['Retry-After']))).toBe(false);
});
it('should handle NaN retryAfter', () => {
const error = new RateLimitError({
retryAfter: NaN,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.headers?.['Retry-After']).toBe('1');
expect(error.data?.retry_after).toBe(1);
});
it('should handle Infinity retryAfter', () => {
const error = new RateLimitError({
retryAfter: Infinity,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.headers?.['Retry-After']).toBe('1');
});
it('should handle negative retryAfter', () => {
const error = new RateLimitError({
retryAfter: -5,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.headers?.['Retry-After']).toBe('1');
});
it('should handle zero retryAfter', () => {
const error = new RateLimitError({
retryAfter: 0,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.headers?.['Retry-After']).toBe('1');
});
it('should handle invalid resetTime', () => {
const error = new RateLimitError({
retryAfter: 5,
limit: 10,
resetTime: new Date(NaN),
});
const resetTimestamp = Number(error.headers?.['X-RateLimit-Reset']);
const nowTimestamp = Math.floor(Date.now() / 1000);
expect(Number.isFinite(resetTimestamp)).toBe(true);
expect(resetTimestamp).toBeGreaterThan(nowTimestamp);
});
it('should handle resetTime in the past', () => {
const error = new RateLimitError({
retryAfter: 5,
limit: 10,
resetTime: new Date(Date.now() - 10000),
});
const resetTimestamp = Number(error.headers?.['X-RateLimit-Reset']);
const nowTimestamp = Math.floor(Date.now() / 1000);
expect(resetTimestamp).toBeGreaterThan(nowTimestamp);
});
it('should handle invalid limit', () => {
const error = new RateLimitError({
retryAfter: 5,
limit: NaN,
resetTime: new Date(Date.now() + 5000),
});
expect(error.headers?.['X-RateLimit-Limit']).toBe('1');
});
it('should handle zero limit', () => {
const error = new RateLimitError({
retryAfter: 5,
limit: 0,
resetTime: new Date(Date.now() + 5000),
});
expect(error.headers?.['X-RateLimit-Limit']).toBe('1');
});
it('should handle NaN retryAfterDecimal', () => {
const error = new RateLimitError({
retryAfter: 5,
retryAfterDecimal: NaN,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
expect(error.data?.retry_after).toBe(5);
});
});
describe('response format', () => {
it('should produce valid JSON response', async () => {
const error = new RateLimitError({
retryAfter: 10,
limit: 50,
resetTime: new Date(Date.now() + 10000),
});
const response = error.getResponse();
const body = (await response.json()) as RateLimitResponseBody;
expect(body.code).toBe('RATE_LIMITED');
expect(body.retry_after).toBe(10);
expect(body.global).toBe(false);
expect(typeof body.retry_after).toBe('number');
});
it('should never produce null retry_after in response', async () => {
const error = new RateLimitError({
retryAfter: undefined as unknown as number,
retryAfterDecimal: undefined,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
const response = error.getResponse();
const body = (await response.json()) as RateLimitResponseBody;
expect(body.retry_after).not.toBeNull();
expect(typeof body.retry_after).toBe('number');
expect(Number.isFinite(body.retry_after)).toBe(true);
});
it('should set correct status code', () => {
const error = new RateLimitError({
retryAfter: 5,
limit: 10,
resetTime: new Date(Date.now() + 5000),
});
const response = error.getResponse();
expect(response.status).toBe(429);
});
it('should include all required headers', () => {
const error = new RateLimitError({
retryAfter: 5,
limit: 10,
resetTime: new Date(Date.now() + 5000),
global: true,
});
const response = error.getResponse();
expect(response.headers.get('Retry-After')).toBe('5');
expect(response.headers.get('X-RateLimit-Global')).toBe('true');
expect(response.headers.get('X-RateLimit-Limit')).toBe('10');
expect(response.headers.get('X-RateLimit-Remaining')).toBe('0');
expect(response.headers.get('X-RateLimit-Reset')).not.toBeNull();
});
});
describe('edge cases', () => {
it('should handle all inputs being invalid simultaneously', () => {
const error = new RateLimitError({
retryAfter: NaN,
retryAfterDecimal: Infinity,
limit: -1,
resetTime: new Date(NaN),
});
expect(error.headers?.['Retry-After']).toBe('1');
expect(error.headers?.['X-RateLimit-Limit']).toBe('1');
expect(Number.isFinite(Number(error.headers?.['X-RateLimit-Reset']))).toBe(true);
expect(Number.isFinite(error.data?.retry_after as number)).toBe(true);
});
it('should handle very large retryAfter values', () => {
const error = new RateLimitError({
retryAfter: 999999999,
limit: 10,
resetTime: new Date(Date.now() + 999999999000),
});
expect(error.headers?.['Retry-After']).toBe('999999999');
});
it('should ceil fractional retryAfter for header', () => {
const error = new RateLimitError({
retryAfter: 1.1,
limit: 10,
resetTime: new Date(Date.now() + 2000),
});
expect(error.headers?.['Retry-After']).toBe('2');
});
});
});

View File

@@ -0,0 +1,227 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {ValidationError} from '@fluxer/errors/src/ValidationError';
import {describe, expect, it} from 'vitest';
interface ValidationErrorBody {
code: string;
message: string;
errors: Array<{field: string; code: string; message: string}>;
}
describe('ValidationError', () => {
describe('constructor', () => {
it('should create error with single field error', () => {
const error = new ValidationError({
errors: [{field: 'email', code: 'INVALID_EMAIL', message: 'Invalid email format'}],
});
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.message).toBe('Validation failed');
expect(error.name).toBe('ValidationError');
expect(error.errors).toEqual([{field: 'email', code: 'INVALID_EMAIL', message: 'Invalid email format'}]);
});
it('should create error with multiple field errors', () => {
const fieldErrors = [
{field: 'email', code: 'REQUIRED', message: 'Email is required'},
{field: 'password', code: 'TOO_SHORT', message: 'Password must be at least 8 characters'},
{field: 'username', code: 'INVALID_CHARS', message: 'Username contains invalid characters'},
];
const error = new ValidationError({errors: fieldErrors});
expect(error.errors).toHaveLength(3);
expect(error.errors).toEqual(fieldErrors);
});
it('should allow custom code', () => {
const error = new ValidationError({
code: 'INVALID_FORM_BODY',
errors: [{field: 'name', code: 'REQUIRED', message: 'Name is required'}],
});
expect(error.code).toBe('INVALID_FORM_BODY');
});
it('should allow custom message', () => {
const error = new ValidationError({
message: 'Input validation failed',
errors: [{field: 'name', code: 'REQUIRED', message: 'Name is required'}],
});
expect(error.message).toBe('Input validation failed');
});
it('should be instance of FluxerError', () => {
const error = new ValidationError({
errors: [{field: 'field', code: 'CODE', message: 'message'}],
});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('getResponse', () => {
it('should return JSON response with errors array', async () => {
const error = new ValidationError({
errors: [{field: 'email', code: 'INVALID', message: 'Invalid email'}],
});
const response = error.getResponse();
expect(response.status).toBe(400);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = await response.json();
expect(body).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
errors: [{field: 'email', code: 'INVALID', message: 'Invalid email'}],
});
});
it('should include multiple errors in response', async () => {
const fieldErrors = [
{field: 'email', code: 'REQUIRED', message: 'Email is required'},
{field: 'password', code: 'TOO_SHORT', message: 'Password too short'},
];
const error = new ValidationError({errors: fieldErrors});
const response = error.getResponse();
const body = (await response.json()) as ValidationErrorBody;
expect(body.errors).toHaveLength(2);
expect(body.errors).toEqual(fieldErrors);
});
it('should include custom code and message in response', async () => {
const error = new ValidationError({
code: 'CUSTOM_VALIDATION',
message: 'Custom validation message',
errors: [{field: 'field', code: 'CODE', message: 'message'}],
});
const response = error.getResponse();
const body = (await response.json()) as ValidationErrorBody;
expect(body.code).toBe('CUSTOM_VALIDATION');
expect(body.message).toBe('Custom validation message');
});
});
describe('fromField static method', () => {
it('should create ValidationError from single field', () => {
const error = ValidationError.fromField('username', 'TAKEN', 'Username is already taken');
expect(error).toBeInstanceOf(ValidationError);
expect(error.errors).toEqual([{field: 'username', code: 'TAKEN', message: 'Username is already taken'}]);
});
it('should have default code and message', () => {
const error = ValidationError.fromField('field', 'code', 'message');
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.message).toBe('Validation failed');
});
});
describe('fromFields static method', () => {
it('should create ValidationError from multiple fields', () => {
const fieldErrors = [
{field: 'email', code: 'REQUIRED', message: 'Email is required'},
{field: 'password', code: 'WEAK', message: 'Password is too weak'},
];
const error = ValidationError.fromFields(fieldErrors);
expect(error).toBeInstanceOf(ValidationError);
expect(error.errors).toEqual(fieldErrors);
});
it('should handle empty array', () => {
const error = ValidationError.fromFields([]);
expect(error.errors).toEqual([]);
});
});
describe('error data structure', () => {
it('should store errors in data property', () => {
const fieldErrors = [{field: 'test', code: 'TEST', message: 'Test error'}];
const error = new ValidationError({errors: fieldErrors});
expect(error.data).toEqual({errors: fieldErrors});
});
it('should serialize correctly with toJSON', () => {
const error = new ValidationError({
errors: [{field: 'field', code: 'CODE', message: 'message'}],
});
const json = error.toJSON();
expect(json).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
errors: [{field: 'field', code: 'CODE', message: 'message'}],
});
});
});
describe('edge cases', () => {
it('should handle field errors with special characters', () => {
const error = new ValidationError({
errors: [{field: 'user.email', code: 'INVALID', message: "Email can't be empty"}],
});
expect(error.errors[0].field).toBe('user.email');
expect(error.errors[0].message).toBe("Email can't be empty");
});
it('should handle nested field paths', () => {
const error = new ValidationError({
errors: [
{field: 'address.street', code: 'REQUIRED', message: 'Street is required'},
{field: 'address.city', code: 'REQUIRED', message: 'City is required'},
{field: 'address.zip', code: 'INVALID_FORMAT', message: 'Invalid ZIP code format'},
],
});
expect(error.errors).toHaveLength(3);
expect(error.errors[0].field).toBe('address.street');
});
it('should handle array index field paths', () => {
const error = new ValidationError({
errors: [
{field: 'items[0].name', code: 'REQUIRED', message: 'Item name is required'},
{field: 'items[1].quantity', code: 'MIN', message: 'Quantity must be at least 1'},
],
});
expect(error.errors).toHaveLength(2);
expect(error.errors[0].field).toBe('items[0].name');
});
});
});

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
export class AdminApiKeyNotFoundError extends NotFoundError {
constructor(messageVariables?: Record<string, unknown>) {
super({
code: APIErrorCodes.ADMIN_API_KEY_NOT_FOUND,
messageVariables,
});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class NotOwnerOfAdminApiKeyError extends ForbiddenError {
constructor(messageVariables?: Record<string, unknown>) {
super({
code: APIErrorCodes.NOT_OWNER_OF_ADMIN_API_KEY,
messageVariables,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class BotUserAuthEndpointAccessDeniedError extends ForbiddenError {
constructor() {
super({
code: APIErrorCodes.BOT_USER_AUTH_ENDPOINT_ACCESS_DENIED,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class BotUserAuthSessionCreationDeniedError extends ForbiddenError {
constructor() {
super({
code: APIErrorCodes.BOT_USER_AUTH_SESSION_CREATION_DENIED,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {InternalServerError} from '@fluxer/errors/src/domains/core/InternalServerError';
export class EmailServiceNotTestableError extends InternalServerError {
constructor() {
super({code: APIErrorCodes.EMAIL_SERVICE_NOT_TESTABLE});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class EmailVerificationRequiredError extends ForbiddenError {
constructor() {
super({
code: APIErrorCodes.EMAIL_VERIFICATION_REQUIRED,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class GuildPhoneVerificationRequiredError extends ForbiddenError {
constructor() {
super({
code: APIErrorCodes.GUILD_PHONE_VERIFICATION_REQUIRED,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class HandoffCodeExpiredError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.HANDOFF_CODE_EXPIRED});
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {OAuth2Error} from '@fluxer/errors/src/domains/auth/OAuth2Error';
export class HttpGetAuthorizeNotSupportedError extends OAuth2Error {
constructor(message = 'GET /oauth2/authorize is not supported. Use POST /oauth2/authorize/consent.') {
super({error: 'invalid_request', errorDescription: message, status: 400});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {UnauthorizedError} from '@fluxer/errors/src/domains/core/UnauthorizedError';
export class InvalidGatewayAuthTokenError extends UnauthorizedError {
constructor() {
super({code: APIErrorCodes.INVALID_AUTH_TOKEN});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidHandoffCodeError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.INVALID_HANDOFF_CODE});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidPhoneNumberError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.INVALID_PHONE_NUMBER,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidPhoneVerificationCodeError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.INVALID_PHONE_VERIFICATION_CODE,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {InternalServerError} from '@fluxer/errors/src/domains/core/InternalServerError';
export class InvalidWebAuthnAuthenticationCounterError extends InternalServerError {
constructor() {
super({code: APIErrorCodes.INVALID_WEBAUTHN_AUTHENTICATION_COUNTER});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidWebAuthnCredentialCounterError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.INVALID_WEBAUTHN_CREDENTIAL_COUNTER});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidWebAuthnCredentialError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.INVALID_WEBAUTHN_CREDENTIAL});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidWebAuthnPublicKeyFormatError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.INVALID_WEBAUTHN_PUBLIC_KEY_FORMAT});
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class IpAuthorizationRequiredError extends ForbiddenError {
constructor({
ticket,
email,
resendAvailableIn,
}: {
ticket: string;
email: string;
resendAvailableIn: number;
}) {
super({
code: APIErrorCodes.IP_AUTHORIZATION_REQUIRED,
data: {
ip_authorization_required: true,
ticket,
email,
resend_available_in: resendAvailableIn,
},
});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ThrottledError} from '@fluxer/errors/src/domains/core/ThrottledError';
export class IpAuthorizationResendCooldownError extends ThrottledError {
constructor(resendAvailableIn: number) {
super({
code: APIErrorCodes.IP_AUTHORIZATION_RESEND_COOLDOWN,
data: {resend_available_in: resendAvailableIn},
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ThrottledError} from '@fluxer/errors/src/domains/core/ThrottledError';
export class IpAuthorizationResendLimitExceededError extends ThrottledError {
constructor() {
super({code: APIErrorCodes.IP_AUTHORIZATION_RESEND_LIMIT_EXCEEDED});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MfaNotDisabledError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.TWO_FA_NOT_ENABLED});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MfaNotEnabledError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.TWO_FACTOR_REQUIRED});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {UnauthorizedError} from '@fluxer/errors/src/domains/core/UnauthorizedError';
export class MissingGatewayAuthorizationError extends UnauthorizedError {
constructor() {
super({code: APIErrorCodes.MISSING_AUTHORIZATION});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MissingOAuthFieldsError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.MISSING_OAUTH_FIELDS});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class NoPasskeysRegisteredError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.NO_PASSKEYS_REGISTERED});
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {HTTPException} from 'hono/http-exception';
type ErrorStatusCode = 400 | 401 | 403;
export class OAuth2Error extends HTTPException {
error: string;
errorDescription: string;
override status: ErrorStatusCode;
constructor({
error,
errorDescription,
status = 400,
}: {
error: string;
errorDescription: string;
status?: ErrorStatusCode;
}) {
super(status, {message: errorDescription});
this.error = error;
this.errorDescription = errorDescription;
this.status = status;
this.name = 'OAuth2Error';
}
override getResponse(): Response {
return new Response(
JSON.stringify({
error: this.error,
error_description: this.errorDescription,
}),
{
status: this.status,
headers: {
'Content-Type': 'application/json',
},
},
);
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {UnauthorizedError} from '@fluxer/errors/src/domains/core/UnauthorizedError';
export class PasskeyAuthenticationFailedError extends UnauthorizedError {
constructor() {
super({code: APIErrorCodes.PASSKEY_AUTHENTICATION_FAILED});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class PhoneAlreadyUsedError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.PHONE_ALREADY_USED,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class PhoneRequiredForSmsMfaError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.PHONE_REQUIRED_FOR_SMS_MFA,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class PhoneVerificationRequiredError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.PHONE_VERIFICATION_REQUIRED,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class SessionTokenMismatchError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.SESSION_TOKEN_MISMATCH});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class SmsMfaNotEnabledError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.SMS_MFA_NOT_ENABLED,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class SmsMfaRequiresTotpError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.SMS_MFA_REQUIRES_TOTP,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class SmsVerificationUnavailableError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.SMS_VERIFICATION_UNAVAILABLE});
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class SsoRequiredError extends ForbiddenError {
constructor() {
super({code: APIErrorCodes.SSO_REQUIRED});
this.name = 'SsoRequiredError';
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class SudoModeRequiredError extends ForbiddenError {
constructor(hasMfa: boolean) {
super({
code: APIErrorCodes.SUDO_MODE_REQUIRED,
data: {
has_mfa: hasMfa,
},
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
export class UnknownWebAuthnCredentialError extends NotFoundError {
constructor() {
super({code: APIErrorCodes.UNKNOWN_WEBAUTHN_CREDENTIAL});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class WebAuthnCredentialLimitReachedError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.WEBAUTHN_CREDENTIAL_LIMIT_REACHED});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CallAlreadyExistsError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.CALL_ALREADY_EXISTS,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class CannotEditOtherUserMessageError extends ForbiddenError {
constructor() {
super({
code: APIErrorCodes.CANNOT_EDIT_OTHER_USER_MESSAGE,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CannotEditSystemMessageError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CANNOT_MODIFY_SYSTEM_WEBHOOK});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CannotReportOwnMessageError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.CANNOT_REPORT_OWN_MESSAGE,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CannotSendEmptyMessageError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CANNOT_SEND_EMPTY_MESSAGE});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CannotSendMessageToNonTextChannelError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.CANNOT_SEND_MESSAGES_IN_NON_TEXT_CHANNEL,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CannotSendMessagesToUserError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CANNOT_SEND_MESSAGES_TO_USER});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
export class ChannelIndexingError extends FluxerError {
constructor() {
super({
code: APIErrorCodes.CHANNEL_INDEXING,
status: 202,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class EmptyStreamThumbnailPayloadError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.STREAM_THUMBNAIL_PAYLOAD_EMPTY});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidChannelTypeError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.INVALID_CHANNEL_TYPE,
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidChannelTypeForCallError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.INVALID_CHANNEL_TYPE_FOR_CALL,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidStreamKeyFormatError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.INVALID_STREAM_KEY_FORMAT});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class InvalidStreamThumbnailPayloadError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.INVALID_STREAM_THUMBNAIL_PAYLOAD});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MaxCategoryChannelsError extends BadRequestError {
constructor(maxChannels: number) {
super({
code: APIErrorCodes.MAX_CATEGORY_CHANNELS,
messageVariables: {maxChannels},
});
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MaxGroupDmRecipientsError extends BadRequestError {
constructor(limit: number) {
super({
code: APIErrorCodes.MAX_GROUP_DM_RECIPIENTS,
messageVariables: {limit},
data: {
max_recipients: limit,
},
});
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MaxGroupDmsError extends BadRequestError {
constructor(limit: number) {
super({
code: APIErrorCodes.MAX_GROUP_DMS,
messageVariables: {limit},
data: {
max_group_dms: limit,
},
});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MaxReactionsPerMessageError extends BadRequestError {
constructor(limit: number) {
super({
code: APIErrorCodes.MAX_REACTIONS,
messageVariables: {limit},
});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MaxUsersPerMessageReactionError extends BadRequestError {
constructor(limit: number) {
super({
code: APIErrorCodes.MAX_REACTIONS,
messageVariables: {limit},
});
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MaxWebhooksPerChannelError extends BadRequestError {
constructor(limit: number) {
super({
code: APIErrorCodes.MAX_WEBHOOKS_PER_CHANNEL,
messageVariables: {limit},
data: {
max_webhooks_per_channel: limit,
},
});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class MessageTotalSizeTooLargeError extends BadRequestError {
constructor(maxSize: number) {
super({
code: APIErrorCodes.MESSAGE_TOTAL_ATTACHMENT_SIZE_TOO_LARGE,
messageVariables: {maxSize},
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
export class NoActiveCallError extends NotFoundError {
constructor() {
super({
code: APIErrorCodes.NO_ACTIVE_CALL,
});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class StreamKeyChannelMismatchError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.STREAM_KEY_CHANNEL_MISMATCH});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class UnclaimedAccountCannotAddReactionsError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.UNCLAIMED_ACCOUNT_CANNOT_ADD_REACTIONS});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class UnclaimedAccountCannotJoinGroupDmsError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.UNCLAIMED_ACCOUNT_CANNOT_JOIN_GROUP_DMS});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class UnclaimedAccountCannotJoinOneOnOneVoiceCallsError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.UNCLAIMED_ACCOUNT_CANNOT_JOIN_ONE_ON_ONE_VOICE_CALLS});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class UnclaimedAccountCannotJoinVoiceChannelsError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.UNCLAIMED_ACCOUNT_CANNOT_JOIN_VOICE_CHANNELS});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class UnclaimedAccountCannotSendDirectMessagesError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.UNCLAIMED_ACCOUNT_CANNOT_SEND_DIRECT_MESSAGES});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class UnclaimedAccountCannotSendMessagesError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.UNCLAIMED_ACCOUNT_CANNOT_SEND_MESSAGES});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
export class UnknownChannelError extends NotFoundError {
constructor() {
super({code: APIErrorCodes.UNKNOWN_CHANNEL});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
export class UnknownMessageError extends NotFoundError {
constructor() {
super({code: APIErrorCodes.UNKNOWN_MESSAGE});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class ConnectionAlreadyExistsError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CONNECTION_ALREADY_EXISTS});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class ConnectionInvalidIdentifierError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CONNECTION_INVALID_IDENTIFIER});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class ConnectionInvalidTypeError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CONNECTION_INVALID_TYPE});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class ConnectionLimitReachedError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CONNECTION_LIMIT_REACHED});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
export class ConnectionNotFoundError extends NotFoundError {
constructor() {
super({code: APIErrorCodes.CONNECTION_NOT_FOUND});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class ConnectionVerificationFailedError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CONNECTION_VERIFICATION_FAILED});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class ContentBlockedError extends ForbiddenError {
constructor() {
super({code: APIErrorCodes.CONTENT_BLOCKED});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
export class AccessDeniedError extends FluxerError {
constructor() {
super({code: APIErrorCodes.ACCESS_DENIED, status: 403});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class AclsMustBeNonEmptyError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.ACLS_MUST_BE_NON_EMPTY});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
export class AuditLogIndexingError extends FluxerError {
constructor() {
super({
code: APIErrorCodes.AUDIT_LOG_INDEXING,
status: 202,
});
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {APIErrorCode} from '@fluxer/constants/src/ApiErrorCodes';
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {FluxerError, type FluxerErrorData} from '@fluxer/errors/src/FluxerError';
export class BadGatewayError extends FluxerError {
constructor({
code = APIErrorCodes.BAD_GATEWAY,
message,
data,
headers,
messageVariables,
}: {
code?: APIErrorCode;
message?: string;
data?: FluxerErrorData;
headers?: Record<string, string>;
messageVariables?: Record<string, unknown>;
} = {}) {
super({code, message, status: 502, data, headers, messageVariables});
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {APIErrorCode} from '@fluxer/constants/src/ApiErrorCodes';
import {FluxerError, type FluxerErrorData} from '@fluxer/errors/src/FluxerError';
export class BadRequestError extends FluxerError {
constructor({
code,
message,
headers,
data,
messageVariables,
}: {
code: APIErrorCode;
message?: string;
data?: FluxerErrorData;
headers?: Record<string, string>;
messageVariables?: Record<string, unknown>;
}) {
super({code, message, status: 400, data, headers, messageVariables});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CannotExecuteOnDmError extends BadRequestError {
constructor() {
super({code: APIErrorCodes.CANNOT_EXECUTE_ON_DM});
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
export class CannotRemoveOtherRecipientsError extends ForbiddenError {
constructor() {
super({code: APIErrorCodes.MISSING_PERMISSIONS});
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CannotShrinkReservedSlotsError extends BadRequestError {
constructor(reservedSlotIndices: Array<number>) {
super({
code: APIErrorCodes.CANNOT_SHRINK_RESERVED_SLOTS,
messageVariables: {
count: reservedSlotIndices.length,
indices: reservedSlotIndices.join(', '),
},
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CaptchaVerificationRequiredError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.CAPTCHA_REQUIRED,
});
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {APIErrorCode} from '@fluxer/constants/src/ApiErrorCodes';
import {FluxerError, type FluxerErrorData} from '@fluxer/errors/src/FluxerError';
export class ConflictError extends FluxerError {
constructor({
code,
message,
data,
headers,
messageVariables,
}: {
code: APIErrorCode;
message?: string;
data?: FluxerErrorData;
headers?: Record<string, string>;
messageVariables?: Record<string, unknown>;
}) {
super({code, message, status: 409, data, headers, messageVariables});
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {InternalServerError} from '@fluxer/errors/src/domains/core/InternalServerError';
export class CreationFailedError extends InternalServerError {
constructor(detail?: string) {
super({
code: APIErrorCodes.CREATION_FAILED,
messageVariables: detail ? {detail} : undefined,
});
}
}

Some files were not shown because too many files have changed in this diff Show More