Mercurial > gemma
view pkg/pgxutils/errors.go @ 5493:0cd4ff1066fe logging
Signal config readiness after logging is configured.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 20 Sep 2021 18:14:02 +0200 |
parents | 73563c4bba5b |
children | 2dd155cc95ec |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Tom Gottfried <tom.gottfried@intevation.de> // * Sascha L. Teichmann <sascha.teichmann@intevation.de> package pgxutils import ( "net/http" "strings" "github.com/jackc/pgx" ) const ( // camel cased condition name = error code // from appendix A of PostgreSQL documentation notNullViolation = "23502" foreignKeyViolation = "23503" uniqueViolation = "23505" checkViolation = "23514" exclusionViolation = "23P01" insufficientPrivilege = "42501" duplicateObject = "42710" noDataFound = "P0002" ) // ReadableError wraps a given error Err and // permits extraction of more user-friendly // error messages from it in case it is an error // from the PostgreSQL backend. type ReadableError struct { Err error } func (re ReadableError) Error() string { m, _ := re.MessageAndCode() return m } // MessageAndCode returns a user-readable message // and a matching HTTP status code. // If its not a pgx.PgError it defaults to // calling the parent Error method and returns its // result together with http.StatusInternalServerError. func (re ReadableError) MessageAndCode() (string, int) { if e, ok := re.Err.(pgx.PgError); ok { return messageAndCode(e) } return re.Err.Error(), http.StatusInternalServerError } func messageAndCode(err pgx.PgError) (m string, c int) { c = http.StatusInternalServerError // Most recent line from stacktrace contains failed statement recent := strings.SplitN(err.Where, "\n", 1)[0] switch err.Code { case notNullViolation: switch err.SchemaName { case "waterway": switch err.TableName { case "gauges": switch err.ColumnName { case "objname": m = "Missing objname" return case "geom": m = "Missing lat/lon" return case "zero_point": m = "Missing zeropoint" return } } } case foreignKeyViolation: switch err.SchemaName { case "waterway": switch err.TableName { case "gauge_measurements", "gauge_predictions", "bottlenecks": switch err.ConstraintName { case "waterway_bottlenecks_reference_gauge", "waterway_gauge_measurements_reference_gauge", "waterway_gauge_predictions_reference_gauge": m = "Referenced gauge with matching temporal validity not available" return } } switch err.TableName { case "fairway_marks_bcnlat_dirimps", "fairway_marks_daymar_dirimps", "fairway_marks_notmrk_dirimps": switch err.ConstraintName { case "fairway_marks_bcnlat_dirimps_dirimp_fkey", "fairway_marks_daymar_dirimps_dirimp_fkey", "fairway_marks_notmrk_dirimps_dirimp_fkey": m = "Invalid value for dirimp" return } } } case uniqueViolation: switch err.SchemaName { case "users": switch err.TableName { case "stretches": switch err.ConstraintName { case "stretches_name_staging_done_key": m = "A stretch with that name already exists" c = http.StatusConflict return } } case "waterway": switch err.TableName { case "sections": switch err.ConstraintName { case "sections_name_staging_done_key": m = "A section with that name already exists" c = http.StatusConflict return } case "fairway_dimensions": switch err.ConstraintName { case "fairway_dimensions_area_unique": m = "Duplicate fairway dimension area" c = http.StatusConflict return } } } case exclusionViolation: switch err.SchemaName { case "waterway": switch err.TableName { case "sections": switch err.ConstraintName { case "sections_name_country_excl": m = "A section with that name already exists for another country" c = http.StatusConflict return } } } case checkViolation: switch err.SchemaName { case "waterway": switch err.TableName { case "sounding_results": switch err.ConstraintName { case "b_sounding_results_in_bn_area": m = "Dataset does not intersect with given bottleneck" c = http.StatusConflict return } case "fairway_dimensions": switch err.ConstraintName { case "fairway_dimensions_area_check": m = "Geometry could not be stored as valid, non-empty polygon" return } } case "internal": switch err.TableName { case "user_profiles": switch err.ConstraintName { case "user_profiles_username_check": m = "User name too long" c = http.StatusBadRequest return } } } case duplicateObject: switch { case strings.Contains(recent, "CREATE ROLE"): m = "A user with that name already exists" c = http.StatusConflict return } case noDataFound: switch { case strings.Contains(recent, "isrsrange_points"): m = "No distance mark found for at least one given ISRS Location Code" return case strings.Contains(recent, "isrsrange_axis"): m = "No contiguous axis found between given ISRS Location Codes" return case strings.Contains(recent, "isrsrange_area"): m = "No area around axis between given ISRS Location Codes" return } case insufficientPrivilege: m = "Could not save: Data outside the area of responsibility." return } m = "Unexpected database error: " + err.Message return }