@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
139 lines (138 loc) • 6.2 kB
JavaScript
import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '@directus/errors';
var MySQLErrorCodes;
(function (MySQLErrorCodes) {
MySQLErrorCodes["UNIQUE_VIOLATION"] = "ER_DUP_ENTRY";
MySQLErrorCodes["NUMERIC_VALUE_OUT_OF_RANGE"] = "ER_WARN_DATA_OUT_OF_RANGE";
MySQLErrorCodes["ER_DATA_TOO_LONG"] = "ER_DATA_TOO_LONG";
MySQLErrorCodes["NOT_NULL_VIOLATION"] = "ER_BAD_NULL_ERROR";
MySQLErrorCodes["FOREIGN_KEY_VIOLATION"] = "ER_NO_REFERENCED_ROW_2";
MySQLErrorCodes["ER_INVALID_USE_OF_NULL"] = "ER_INVALID_USE_OF_NULL";
MySQLErrorCodes["WARN_DATA_TRUNCATED"] = "WARN_DATA_TRUNCATED";
})(MySQLErrorCodes || (MySQLErrorCodes = {}));
export function extractError(error, data) {
switch (error.code) {
case MySQLErrorCodes.UNIQUE_VIOLATION:
return uniqueViolation();
case MySQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
return numericValueOutOfRange();
case MySQLErrorCodes.ER_DATA_TOO_LONG:
return valueLimitViolation();
case MySQLErrorCodes.NOT_NULL_VIOLATION:
return notNullViolation();
case MySQLErrorCodes.FOREIGN_KEY_VIOLATION:
return foreignKeyViolation();
// Note: MariaDB throws data truncated for null value error
case MySQLErrorCodes.ER_INVALID_USE_OF_NULL:
case MySQLErrorCodes.WARN_DATA_TRUNCATED:
return containsNullValues();
}
return error;
function uniqueViolation() {
const betweenQuotes = /'([^']+)'/g;
const matches = error.sqlMessage.match(betweenQuotes);
if (!matches)
return error;
/**
* MySQL's error doesn't return the field name in the error. In case the field is created through
* Directus (/ Knex), the key name will be `<collection>_<field>_unique` in which case we can pull
* the field name from the key name.
* If the field is the primary key it instead will be `<collection>_PRIMARY` for MySQL 8+
* and `PRIMARY` for MySQL 5.7 and MariaDB.
*/
let collection;
let indexName;
let field = null;
if (matches[1].includes('.')) {
// MySQL 8+ style error message
// In case of primary key matches[1] is `'<collection>.PRIMARY'`
// In case of other field matches[1] is `'<collection>.<collection>_<field>_unique'`
[collection, indexName] = matches[1].slice(1, -1).split('.');
}
else {
// MySQL 5.7 and MariaDB style error message
// In case of primary key matches[1] is `'PRIMARY'`
// In case of other field matches[1] is `'<collection>_<field>_unique'`
indexName = matches[1].slice(1, -1);
collection = indexName.includes('_') ? indexName.split('_')[0] : null;
}
if (collection !== null && indexName.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
field = indexName?.slice(collection.length + 1, -7);
}
return new RecordNotUniqueError({
collection,
field,
value: field ? data[field] : null,
primaryKey: indexName === 'PRIMARY', // propagate information about primary key violation
});
}
function numericValueOutOfRange() {
const betweenTicks = /`([^`]+)`/g;
const betweenQuotes = /'([^']+)'/g;
const tickMatches = error.sql.match(betweenTicks);
const quoteMatches = error.sqlMessage.match(betweenQuotes);
if (!tickMatches || !quoteMatches)
return error;
const collection = tickMatches[0]?.slice(1, -1);
const field = quoteMatches[0]?.slice(1, -1);
return new ValueOutOfRangeError({
collection,
field,
value: field ? data[field] : null,
});
}
function valueLimitViolation() {
const betweenTicks = /`([^`]+)`/g;
const betweenQuotes = /'([^']+)'/g;
const tickMatches = error.sql.match(betweenTicks);
const quoteMatches = error.sqlMessage.match(betweenQuotes);
if (!tickMatches || !quoteMatches)
return error;
const collection = tickMatches[0]?.slice(1, -1);
const field = quoteMatches[0]?.slice(1, -1);
return new ValueTooLongError({
collection,
field,
value: field ? data[field] : null,
});
}
function notNullViolation() {
const betweenTicks = /`([^`]+)`/g;
const betweenQuotes = /'([^']+)'/g;
const tickMatches = error.sql.match(betweenTicks);
const quoteMatches = error.sqlMessage.match(betweenQuotes);
if (!tickMatches || !quoteMatches)
return error;
const collection = tickMatches[0]?.slice(1, -1);
const field = quoteMatches[0]?.slice(1, -1);
return new NotNullViolationError({
collection,
field,
});
}
function foreignKeyViolation() {
const betweenTicks = /`([^`]+)`/g;
const betweenParens = /\(([^)]+)\)/g;
const tickMatches = error.sqlMessage.match(betweenTicks);
const parenMatches = error.sql.match(betweenParens);
if (!tickMatches || !parenMatches)
return error;
const collection = tickMatches[1].slice(1, -1);
const field = tickMatches[3].slice(1, -1);
return new InvalidForeignKeyError({
collection,
field,
value: field ? data[field] : null,
});
}
function containsNullValues() {
const betweenTicks = /`([^`]+)`/g;
// Normally, we shouldn't read from the executed SQL. In this case, we're altering a single
// column, so we shouldn't have the problem where multiple columns are altered at the same time
const tickMatches = error.sql.match(betweenTicks);
if (!tickMatches)
return error;
const collection = tickMatches[0].slice(1, -1);
const field = tickMatches[1].slice(1, -1);
return new ContainsNullValuesError({ collection, field });
}
}