IdDatabaseExceptions.java
/*
* Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package com.io7m.idstore.database.postgres.internal;
import com.io7m.idstore.database.api.IdDatabaseException;
import com.io7m.idstore.database.api.IdDatabaseTransactionType;
import org.jooq.exception.DataAccessException;
import org.postgresql.util.PSQLException;
import org.postgresql.util.ServerErrorMessage;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static com.io7m.idstore.error_codes.IdStandardErrorCodes.EMAIL_DUPLICATE;
import static com.io7m.idstore.error_codes.IdStandardErrorCodes.EMAIL_ONE_REQUIRED;
import static com.io7m.idstore.error_codes.IdStandardErrorCodes.OPERATION_NOT_PERMITTED;
import static com.io7m.idstore.error_codes.IdStandardErrorCodes.PROTOCOL_ERROR;
import static com.io7m.idstore.error_codes.IdStandardErrorCodes.SQL_ERROR;
import static com.io7m.idstore.error_codes.IdStandardErrorCodes.USER_DUPLICATE_ID;
import static com.io7m.idstore.error_codes.IdStandardErrorCodes.USER_DUPLICATE_ID_NAME;
/**
* Functions to handle database exceptions.
*/
public final class IdDatabaseExceptions
{
private IdDatabaseExceptions()
{
}
/**
* Handle a data access exception.
*
* @param transaction The transaction
* @param e The exception
* @param attributes The extra exception attributes
*
* @return The resulting exception
*/
public static IdDatabaseException handleDatabaseException(
final IdDatabaseTransactionType transaction,
final DataAccessException e,
final Map<String, String> attributes)
{
final var m = e.getMessage();
IdDatabaseException result =
new IdDatabaseException(
m,
e,
SQL_ERROR,
attributes,
Optional.empty()
);
if (e.getCause() instanceof final PSQLException psqlException) {
final var serverError =
Objects.requireNonNullElse(
psqlException.getServerErrorMessage(),
new ServerErrorMessage("")
);
/*
* See https://www.postgresql.org/docs/current/errcodes-appendix.html
* for all of these numeric codes.
*/
result = switch (psqlException.getSQLState()) {
/*
* foreign_key_violation
*/
case "23503" -> {
final var constraint =
Objects.requireNonNullElse(serverError.getConstraint(), "");
yield switch (constraint) {
case "emails_user_id_fkey", "emails_admin_id_fkey" -> {
yield new IdDatabaseException(
"Email already exists",
EMAIL_DUPLICATE,
attributes,
Optional.empty()
);
}
default -> {
yield new IdDatabaseException(
m,
e,
SQL_ERROR,
attributes,
Optional.empty()
);
}
};
}
/*
* unique_violation
*/
case "23505" -> {
final var constraint =
Objects.requireNonNullElse(serverError.getConstraint(), "");
yield switch (constraint) {
case "users_id_name_key" -> {
yield new IdDatabaseException(
"User ID name already exists",
USER_DUPLICATE_ID_NAME,
attributes,
Optional.empty()
);
}
case "user_ids_pkey" -> {
yield new IdDatabaseException(
"User ID already exists",
USER_DUPLICATE_ID,
attributes,
Optional.empty()
);
}
case "emails_unique_lower_email_idx" -> {
yield new IdDatabaseException(
"Email already exists",
EMAIL_DUPLICATE,
attributes,
Optional.empty()
);
}
default -> {
yield new IdDatabaseException(
m,
e,
SQL_ERROR,
attributes,
Optional.empty()
);
}
};
}
case "22021" -> {
/*
* PostgreSQL: character_not_in_repertoire
*/
yield new IdDatabaseException(
Objects.requireNonNullElse(
serverError.getMessage(),
e.getMessage()),
e,
PROTOCOL_ERROR,
attributes,
Optional.empty()
);
}
/*
* insufficient_privilege
*/
case "42501" -> {
yield new IdDatabaseException(
m,
e,
OPERATION_NOT_PERMITTED,
attributes,
Optional.empty()
);
}
/*
* Custom state code defined in a trigger.
*/
case "ID001" -> {
yield new IdDatabaseException(
m,
e,
EMAIL_ONE_REQUIRED,
attributes,
Optional.empty()
);
}
default -> {
yield new IdDatabaseException(
m,
e,
SQL_ERROR,
attributes,
Optional.empty()
);
}
};
}
try {
transaction.rollback();
} catch (final IdDatabaseException ex) {
result.addSuppressed(ex);
}
return result;
}
}