Squashed commit of the following:
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 40s

commit 579f0878e5240dc17db69be1e0b0c0f5af7ef9fe
Author: Arno Kaimbacher <arno.kaimbacher@geosphere.at>
Date:   Tue Jun 9 09:25:44 2026 +0200

    feat: Refactor error handling in Dataset Edit form and improve validation messages

    - Updated error handling in the Dataset Edit form to use a centralized formatError function for displaying validation messages.
    - Enhanced user feedback by ensuring that error messages are displayed consistently across various fields.
    - Modified the validation rule for arrayContainsTypes to provide clearer error messages for missing main and translated titles/abstracts.
    - Introduced a new ValidationService to manage manual construction of validation errors.
    - Updated Vite configuration to streamline asset loading and improve performance.
    - Adjusted Inertia setup to utilize dynamic imports for page-specific assets.
    - Cleaned up unnecessary comments and code in various files for better readability.

commit 5efddc2a58c0e164fef585cc7344c06155dbc2c1
Author: Arno Kaimbacher <arno.kaimbacher@geosphere.at>
Date:   Mon Jan 12 17:02:47 2026 +0100

    feat: add dataset change detection and form submission composables

    - Implemented `useDatasetChangeDetection` for tracking unsaved changes in dataset forms, including comparisons for licenses, basic properties, files, coverage, and more.
    - Added `useDatasetFormSubmission` for handling dataset form submissions with validation, success/error handling, and auto-save functionality.
This commit is contained in:
Kaimbacher 2026-06-09 09:35:15 +02:00
commit 9368a0dd8d
38 changed files with 5588 additions and 6181 deletions

View file

@ -42,7 +42,7 @@ export default class RoleController {
can: {
create: await auth.user?.can(['user-create']),
edit: await auth.user?.can(['user-edit']),
delete: await auth.user?.can(['user-delete']),
delete: false, //await auth.user?.can(['user-delete']),
},
});
}
@ -124,7 +124,7 @@ export default class RoleController {
// password is optional
const input = request.only(['name', 'description']);
const input = request.only(['name', 'display_name', 'description']);
await role.merge(input).save();
// await user.save();

View file

@ -821,6 +821,10 @@ export default class DatasetsController {
referenceIdentifierTypes: Object.entries(ReferenceIdentifierTypes).map(([key, value]) => ({ value: key, label: value })),
relationTypes: Object.entries(RelationTypes).map(([key, value]) => ({ value: key, label: value })),
doctypes: DatasetTypes,
can: {
edit: await auth.user?.can(['dataset-editor-update']),
// delete: await auth.user?.can(['dataset-delete']),
},
});
}

View file

@ -14,9 +14,7 @@ import Person from '#models/person';
import db from '@adonisjs/lucid/services/db';
import { TransactionClientContract } from '@adonisjs/lucid/types/database';
import Subject from '#models/subject';
// import CreateDatasetValidator from '#validators/create_dataset_validator';
import { createDatasetValidator, updateDatasetValidator } from '#validators/dataset';
// import UpdateDatasetValidator from '#validators/update_dataset_validator';
import {
TitleTypes,
DescriptionTypes,
@ -40,7 +38,8 @@ import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
import type { Multipart } from '@adonisjs/bodyparser';
import * as fs from 'fs';
import { parseBytesSize, getConfigFor, getTmpPath, formatBytes } from '#app/utils/utility-functions';
import { parseBytesSize, getConfigFor, getTmpPath, formatBytes, errorMessage } from '#app/utils/utility-functions';
import validation from '#services/validation_service';
interface Dictionary {
[index: string]: string;
@ -207,6 +206,7 @@ export default class DatasetController {
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
.minLength(1) // Ensure at least the main title exists
// .minLength(2)
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
descriptions: vine
@ -426,37 +426,31 @@ export default class DatasetController {
}
public async store({ auth, request, response, session }: HttpContext) {
// At the top of the store() method, declare an array to hold temporary file paths
const uploadedTmpFiles: string[] = [];
// Aggregated limit example (adjust as needed)
const multipartConfig = getConfigFor('multipart');
const aggregatedLimit = multipartConfig.limit ? parseBytesSize(multipartConfig.limit) : 100 * 1024 * 1024;
// const aggregatedLimit = 200 * 1024 * 1024;
let totalUploadedSize = 0;
// // Helper function to format bytes as human-readable text
// function formatBytes(bytes: number): string {
// if (bytes === 0) return '0 Bytes';
// const k = 1024;
// const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
// const i = Math.floor(Math.log(bytes) / Math.log(k));
// return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
// }
// const enabledExtensions = await this.getEnabledExtensions();
const multipart: Multipart = request.multipart;
// NULL GUARD: kein multipart-Body → sauberer Validierungsfehler statt Crash
if (!multipart) {
validation.throw('files', 'Please select at least one file.', 'required');
}
// Configuration for limits
const multipartConfig = getConfigFor('multipart');
const aggregatedLimit = multipartConfig.limit ? parseBytesSize(multipartConfig.limit) : 100 * 1024 * 1024;
let totalUploadedSize = 0;
let filesCount = 0; // Tracker: ensure at least one file is processed
multipart.onFile('files', { deferValidations: true }, async (part) => {
// Attach an individual file size accumulator if needed
filesCount++;
let fileUploadedSize = 0;
// Simply accumulate the size in on('data') without performing the expensive check per chunk
// Accumulate the size per chunk (cheap), defer the limit check to 'end'
part.on('data', (chunk) => {
// reporter(chunk);
// Increase counters using the chunk length
fileUploadedSize += chunk.length;
});
// After the file is completely read, update the global counter and check aggregated limit
// After the file is fully read, update the global counter and check the aggregated limit
part.on('end', () => {
totalUploadedSize += fileUploadedSize;
part.file.size = fileUploadedSize;
@ -465,6 +459,7 @@ export default class DatasetController {
uploadedTmpFiles.push(part.file.tmpPath);
}
// AGGREGATED LIMIT CHECK: abort immediately if too big
if (totalUploadedSize > aggregatedLimit) {
// Clean up all temporary files if aggregate limit is exceeded
uploadedTmpFiles.forEach((tmpPath) => {
@ -474,48 +469,44 @@ export default class DatasetController {
console.error('Error cleaning up temporary file:', cleanupError);
}
});
const error = new errors.E_VALIDATION_ERROR({
'upload error': `Aggregated upload limit of ${formatBytes(aggregatedLimit)} exceeded. The total size of files being uploaded would exceed the limit.`,
});
request.multipart.abort(error);
request.multipart.abort(
validation.make('files', `Upload limit of ${formatBytes(aggregatedLimit)} exceeded.`, 'limit')
);
}
});
part.on('error', (error) => {
// fileUploadError = error;
request.multipart.abort(error);
});
// await pipeline(part, createWriteStream(filePath));
// return { filePath };
// Process file with error handling
try {
// Extract extension from the client file name, e.g. "Tethys 5 - Ampflwang_dataset.zip"
const ext = path.extname(part.file.clientName).replace('.', '');
// Attach the extracted extension to the file object for later use
part.file.extname = ext;
// part.file.sortOrder = part.file.sortOrder;
const tmpPath = getTmpPath(multipartConfig);
(part.file as any).tmpPath = tmpPath;
const writeStream = createWriteStream(tmpPath);
await pipeline(part, writeStream);
} catch (error) {
request.multipart.abort(new errors.E_VALIDATION_ERROR({ 'upload error': error.message }));
request.multipart.abort(validation.make('files', error.message, 'upload'));
}
});
try {
await multipart.process();
// // Instead of letting an error abort the controller, check if any error occurred
// EMPTY FIELD CHECK: triggered if process finishes but onFile never ran
if (filesCount === 0) {
validation.throw('files', 'Please select at least one file.', 'required');
}
} catch (error) {
// This is where you'd expect to catch any errors.
session.flash('errors', error.messages);
return response.redirect().back();
// If it's already a validation error, let it bubble up unchanged
if (error instanceof errors.E_VALIDATION_ERROR) throw error;
// Wrap any other stream/size errors
validation.throw('files', errorMessage(error) || 'Upload failed.');
}
// Proceed to transaction and createDatasetAndAssociations
let trx: TransactionClientContract | null = null;
try {
await request.validateUsing(createDatasetValidator);
@ -539,13 +530,11 @@ export default class DatasetController {
await trx.rollback();
}
console.error('Failed to create dataset and related models:', error);
// throw new ValidationException(true, { 'upload error': `failed to create dataset and related models. ${error}` });
throw error;
}
session.flash('message', 'Dataset has been created successfully');
return response.redirect().toRoute('dataset.list');
// return response.redirect().back();
}
private async createDatasetAndAssociations(
user: User,
@ -1106,7 +1095,7 @@ export default class DatasetController {
}
});
const error = new errors.E_VALIDATION_ERROR({
'upload error': `Aggregated upload limit of ${formatBytes(aggregatedLimit)} exceeded. The total size of files being uploaded would exceed the limit.`,
'files': `Aggregated upload limit of ${formatBytes(aggregatedLimit)} exceeded. The total size of files being uploaded would exceed the limit.`,
});
request.multipart.abort(error);
}
@ -1132,7 +1121,7 @@ export default class DatasetController {
try {
await multipart.process();
} catch (error) {
session.flash('errors', error.messages);
// session.flash('errors', error.messages);
return response.redirect().back();
}
}

View file

@ -1,8 +1,8 @@
import ResumptionToken from './ResumptionToken.js';
import { createClient, RedisClientType } from 'redis';
import InternalServerErrorException from '#app/exceptions/InternalServerException';
import { sprintf } from 'sprintf-js';
import dayjs from 'dayjs';
// import { sprintf } from 'sprintf-js';
// import dayjs from 'dayjs';
import TokenWorkerContract from './TokenWorkerContract.js';
export default class TokenWorkerService implements TokenWorkerContract {
@ -98,21 +98,21 @@ export default class TokenWorkerService implements TokenWorkerContract {
// return uniqueName;
// }
private async generateUniqueName(): Promise<string> {
let fc = 0;
const uniqueId = dayjs().unix().toString();
let uniqueName: string;
let cacheKeyExists: boolean;
do {
// format values
// %s - String
// %d - Signed decimal number (negative, zero or positive)
// [0-9] (Specifies the minimum width held of to the variable value)
uniqueName = sprintf('%s%05d', uniqueId, fc++);
cacheKeyExists = await this.has(uniqueName);
} while (cacheKeyExists);
return uniqueName;
}
// private async generateUniqueName(): Promise<string> {
// let fc = 0;
// const uniqueId = dayjs().unix().toString();
// let uniqueName: string;
// let cacheKeyExists: boolean;
// do {
// // format values
// // %s - String
// // %d - Signed decimal number (negative, zero or positive)
// // [0-9] (Specifies the minimum width held of to the variable value)
// uniqueName = sprintf('%s%05d', uniqueId, fc++);
// cacheKeyExists = await this.has(uniqueName);
// } while (cacheKeyExists);
// return uniqueName;
// }
public async get(key: string): Promise<ResumptionToken | null> {
if (!this.cache) {

View file

@ -1,214 +1,53 @@
/*
|--------------------------------------------------------------------------
| Http Exception Handler
|--------------------------------------------------------------------------
|
| AdonisJs will forward all exceptions occurred during an HTTP request to
| the following class. You can learn more about exception handling by
| reading docs.
|
| The exception handler extends a base `HttpExceptionHandler` which is not
| mandatory, however it can do lot of heavy lifting to handle the errors
| properly.
|
*/
import app from '@adonisjs/core/services/app';
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http';
// import logger from '@adonisjs/core/services/logger';
import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http';
import app from '@adonisjs/core/services/app'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http'
export default class HttpExceptionHandler extends ExceptionHandler {
/**
* In debug mode, the exception handler will display verbose errors
* with pretty printed stack traces.
*/
protected debug = !app.inProduction;
protected debug = !app.inProduction
protected renderStatusPages = true
/**
* Status pages are used to display a custom HTML pages for certain error
* codes. You might want to enable them in production only, but feel
* free to enable them in development as well.
*/
protected renderStatusPages = true; //app.inProduction;
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
'404': (error, ctx) =>
ctx.inertia
? ctx.inertia.render('Errors/ServerError', { error: error.message, code: error.status })
: ctx.response.status(error.status).send(error.message),
'401..403': (error, ctx) => {
if (ctx.inertia) {
return ctx.inertia.render('Errors/ServerError', { error: error.message, code: error.status });
}
return ctx.response.status(error.status).send(error.message);
},
'500..599': (error, ctx) => {
const isDbError =
error.code === 'ECONNREFUSED' &&
(error.errors?.some((e: any) => e.port === 5432) ?? error.message?.includes('5432'));
/**
* Status pages is a collection of error code range and a callback
* to return the HTML contents to send as a response.
*/
// protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
// '401..403': (error, { view }) => {
// return view.render('./errors/unauthorized', { error });
// },
// '404': (error, { view }) => {
// return view.render('./errors/not-found', { error });
// },
// '500..599': (error, { view }) => {
// return view.render('./errors/server-error', { error });
// },
// };
if (isDbError && ctx.inertia) {
return ctx.inertia.render('Errors/postgres_error', {
status: 'error',
message: 'PostgreSQL database connection failed.',
details: {
code: error.code,
type: error.status
// Entferne das .map() auf error.errors, da es oft undefined ist
}
});
}
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
'404': (error, { inertia }) => {
return inertia.render('Errors/ServerError', {
error: error.message,
code: error.status,
});
},
'401..403': async (error, { inertia }) => {
// session.flash('errors', error.message);
return inertia.render('Errors/ServerError', {
error: error.message,
code: error.status,
});
},
// '500': (error, { inertia }) => {
// return inertia.render('Errors/postgres_error', {
// status: 'error',
// message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
// details: {
// code: error.code,
// type: error.status,
// ports: error.errors.map((err: any) => ({
// port: err.port,
// address: err.address,
// })),
// },
// });
// },
'500..599': (error, { inertia }) => {
if (error.code === 'ECONNREFUSED') {
const dbErrors = error.errors.some((err: any) => err.code === 'ECONNREFUSED' && err.port === 5432);
if (dbErrors) {
return inertia.render('Errors/postgres_error', {
status: 'error',
message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
details: {
code: error.code,
type: error.status,
ports: error.errors.map((err: any) => ({
port: err.port,
address: err.address,
})),
},
});
}
} else {
return inertia.render('Errors/ServerError', {
error: error.message,
code: error.status,
});
}
},
};
// constructor() {
// super(logger);
// }
public async handle(error: any, ctx: HttpContext) {
const { response, request, session, inertia } = ctx;
/**
* Handle failed authentication attempt
*/
// if (['E_INVALID_AUTH_PASSWORD', 'E_INVALID_AUTH_UID'].includes(error.code)) {
// session.flash('errors', { login: error.message });
// return response.redirect('/login');
// }
// if ([401].includes(error.status)) {
// session.flash('errors', { login: error.message });
// return response.redirect('/dashboard');
// }
// Handle Axios errors
if (error.code === 'ECONNREFUSED') {
const dbErrors = error.errors.some((err: any) => err.code === 'ECONNREFUSED' && err.port === 5432);
if (dbErrors) {
// return ctx.response.status(503).json({
// status: 'error',
// message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
// details: {
// code: error.code,
// type: error.status,
// ports: error.errors.map((err: any) => ({
// port: err.port,
// address: err.address,
// })),
// },
// });
// return inertia.render('Errors/postgres_error', {
// status: 'error',
// message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
// details: {
// code: error.code,
// type: error.status,
// ports: error.errors.map((err: any) => ({
// port: err.port,
// address: err.address,
// })),
// },
// });
}
}
// Handle simple ECONNREFUSED errors
// if (error.code === 'ECONNREFUSED') {
// return ctx.response.status(503).json({
// status: 'error',
// message: 'Database connection failed. Please ensure PostgreSQL is running.',
// code: error.code,
// });
// }
// https://github.com/inertiajs/inertia-laravel/issues/56
// let test = response.getStatus(); //200
// let header = request.header('X-Inertia'); // true
// if (request.header('X-Inertia') && [500, 503, 404, 403, 401, 200].includes(response.getStatus())) {
if (request.header('X-Inertia') && [422].includes(error.status)) {
// session.flash('errors', error.messages.errors);
session.flash('errors', error.messages);
return response.redirect().back();
// return inertia.render('errors/server_error', {
// return inertia.render('errors/server_error', {
// // status: response.getStatus(),
// error: error,
// });
// ->toResponse($request)
// ->setStatusCode($response->status());
}
// Handle simple ECONNREFUSED errors
// if (error.code === 'ECONNREFUSED') {
// return ctx.response.status(503).json({
// status: 'error',
// message: 'Database connection failed. Please ensure PostgreSQL is running.',
// code: error.code,
// });
// }
// Dynamically change the error templates based on the absence of X-Inertia header
// if (!ctx.request.header('X-Inertia')) {
// this.statusPages = {
// '401..403': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
// '404': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
// '500..599': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
// };
// }
/**
* Forward rest of the exceptions to the parent class
*/
return super.handle(error, ctx);
if (ctx.inertia) {
return ctx.inertia.render('Errors/ServerError', { error: error.message, code: 500 });
}
return ctx.response.status(500).send(error.message);
}
};
public async handle(error: any, ctx: HttpContext) {
/**
* The method is used to report error to the logging service or
* the a third party error monitoring service.
*
* @note You should not attempt to send a response from this method.
* WICHTIG: Validierungsfehler (422) NICHT manuell abfangen!
* AdonisJS 6 + VineJS + Inertia machen das automatisch.
* Wenn du es hier manuell machst, überschreibst du den Standard-Flow.
*/
async report(error: unknown, ctx: HttpContext) {
return super.report(error, ctx);
}
}
return super.handle(error, ctx)
}
}

View file

@ -0,0 +1,54 @@
import app from '@adonisjs/core/services/app'
import { errors } from '@vinejs/vine'
/**
* The ValidationService handles manual construction of validation errors
* that are compatible with the VanillaErrorReporter and AdonisJS Session.
*/
export class ValidationService {
/**
* Builds a validation error in the array-of-objects format without throwing it.
* Use this when you need the error object itself, e.g. multipart.abort(error).
*/
make(field: string, message: string, rule: string = 'manual') {
return new errors.E_VALIDATION_ERROR([
{
field,
message,
rule,
},
])
}
/**
* Throws a manual validation error in the array-of-objects format
* which prevents the ".reduce is not a function" error in the session.
*/
throw(field: string, message: string, rule: string = 'manual') {
throw this.make(field, message, rule)
}
/**
* Throws multiple manual validation errors at once.
*/
throwMany(errorObjects: Array<{ field: string; message: string; rule?: string }>) {
throw new errors.E_VALIDATION_ERROR(
errorObjects.map((err) => ({
field: err.field,
message: err.message,
rule: err.rule || 'manual',
}))
)
}
}
/**
* Initialize and export the singleton instance
*/
let validation: ValidationService
await app.booted(async () => {
validation = await app.container.make(ValidationService)
})
export { validation as default }

View file

@ -122,3 +122,8 @@ function extractPivotAttributes(person: any) {
}
return pivotAttributes;
}
// in #app/utils/utility-functions
export function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}

View file

@ -1,203 +1,50 @@
// import { ValidationError } from '../errors/validation_error.js';
import { errors } from '@vinejs/vine';
import type { ErrorReporterContract, FieldContext } from '@vinejs/vine/types';
import string from '@poppinss/utils/string';
import { errors } from '@vinejs/vine'
import type { ErrorReporterContract, FieldContext } from '@vinejs/vine/types'
/**
* Shape of the Vanilla error node
*/
export type VanillaErrorNode = {
[field: string]: string[];
};
export interface MessagesBagContract {
get(pointer: string, rule: string, message: string, arrayExpressionPointer?: string, args?: any): string;
}
/**
* Message bag exposes the API to pull the most appropriate message for a
* given validation failure.
*/
export class MessagesBag implements MessagesBagContract {
messages: Message;
wildCardCallback;
constructor(messages: string[]) {
this.messages = messages;
this.wildCardCallback = typeof this.messages['*'] === 'function' ? this.messages['*'] : undefined;
}
/**
* Transform message by replace placeholders with runtime values
*/
transform(message: any, rule: string, pointer: string, args: any) {
/**
* No interpolation required
*/
if (!message.includes('{{')) {
return message;
}
return string.interpolate(message, { rule, field: pointer, options: args || {} });
}
/**
* Returns the most appropriate message for the validation failure.
*/
get(pointer: string, rule: string, message: string, arrayExpressionPointer: string, args: any) {
let validationMessage = this.messages[`${pointer}.${rule}`];
/**
* Fetch message for the array expression pointer if it exists
*/
if (!validationMessage && arrayExpressionPointer) {
validationMessage = this.messages[`${arrayExpressionPointer}.${rule}`];
}
/**
* Fallback to the message for the rule
*/
if (!validationMessage) {
validationMessage = this.messages[rule];
}
/**
* Transform and return message. The wildcard callback is invoked when custom message
* is not defined
*/
return validationMessage
? this.transform(validationMessage, rule, pointer, args)
: this.wildCardCallback
? this.wildCardCallback(pointer, rule, arrayExpressionPointer, args)
: message;
}
}
/**
* Shape of the error message collected by the SimpleErrorReporter
*/
type SimpleError = {
message: string;
field: string;
rule: string;
index?: number;
meta?: Record<string, any>;
};
export interface Message {
[key: string]: any;
}
/**
* Simple error reporter collects error messages as an array of object.
* Each object has following properties.
*
* - message: string
* - field: string
* - rule: string
* - index?: number (in case of an array member)
* - args?: Record<string, any>
* Der VanillaErrorReporter sammelt Validierungsfehler im Standardformat,
* damit die AdonisJS Session-Middleware sie korrekt verarbeiten (reducen) kann.
*/
export class VanillaErrorReporter implements ErrorReporterContract {
// private messages;
// private bail;
/**
* Boolean, um zu prüfen, ob Fehler vorliegen
*/
hasErrors: boolean = false
/**
* Sammlung der Fehler als Array (erforderlich für AdonisJS 6 Session)
*/
errors: any[] = []
/**
* Diese Methode wird von VineJS für jeden Validierungsfehler aufgerufen
*/
report(
message: string,
rule: string,
field: FieldContext,
meta?: Record<string, any>
): void {
this.hasErrors = true
/**
* Boolean to know one or more errors have been reported
*/
hasErrors: boolean = false;
/**
* Collection of errors
*/
// errors: SimpleError[] = [];
errors: Message = {};
/**
* Report an error.
* Wir pushen das Objekt in das Array.
* Das Feld 'field' erhält den vollständigen Pfad (z.B. "user.email").
*/
this.errors.push({
message,
rule,
field: field.getFieldPath(),
...meta,
});
}
// constructor(messages: MessagesBagContract) {
// this.messages = messages;
// }
report(message: string, rule: string, field: FieldContext, meta?: Record<string, any> | undefined): void {
// const error: SimpleError = {
// message,
// rule,
// field: field.getFieldPath()
// };
// if (meta) {
// error.meta = meta;
// }
// if (field.isArrayMember) {
// error.index = field.name as number;
// }
// this.errors.push(error);
this.hasErrors = true;
// if (this.errors[field.getFieldPath()]) {
// this.errors[field.getFieldPath()]?.push(message);
// } else {
// this.errors[field.getFieldPath()] = [message];
// }
const error: SimpleError = {
message,
rule,
field: field.getFieldPath(), // ?field.wildCardPath.split('.')[0] : field.getFieldPath(),
};
// field: 'titles.0.value'
// message: 'Main Title is required'
// rule: 'required' "required"
if (meta) {
error.meta = meta;
}
// if (field.isArrayMember) {
// error.index = field.name;
// }
this.hasErrors = true;
// var test = field.getFieldPath();
// this.errors.push(error);
// if (this.errors[error.field]) {
// this.errors[error.field]?.push(message);
// }
if (field.isArrayMember) {
// Check if the field has wildCardPath and if the error field already exists
if (this.errors[error.field]) {
// Do nothing, as we don't want to push further messages
} else {
// If the error field already exists, push the message
if (this.errors[error.field]) {
this.errors[error.field].push(message);
} else {
this.errors[error.field] = [message];
}
}
} else {
if (this.errors[error.field]) {
this.errors[error.field]?.push(message);
} else {
this.errors[error.field] = [message];
}
}
// } else {
// // normal field
// this.errors[field.field] = [message];
// }
/**
* Collecting errors as per the JSONAPI spec
*/
// this.errors.push({
// code: rule,
// detail: message,
// source: {
// pointer: field.wildCardPath,
// },
// ...(meta ? { meta } : {}),
// });
// let pointer: string = field.wildCardPath as string; //'display_name'
// // if (field.isArrayMember) {
// // this.errors[pointer] = field.name;
// // }
// this.errors[pointer] = this.errors[pointer] || [];
// // this.errors[pointer].push(message);
// this.errors[pointer].push(this.messages.get(pointer, rule, message, arrayExpressionPointer, args));
}
/**
* Returns an instance of the validation error
*/
createError() {
return new errors.E_VALIDATION_ERROR(this.errors);
}
}
export {};
/**
* Erstellt die eigentliche Exception.
* Da 'this.errors' nun ein Array ist, funktioniert .reduce()
* in der Session-Middleware reibungslos.
*/
createError() {
return new errors.E_VALIDATION_ERROR(this.errors);
}
}