UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

139 lines (138 loc) 6.2 kB
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 }); } }