Mercurial > gemma
view pkg/pgxutils/errors.go @ 5095:e21cbb9768a2
Prevent duplicate fairway areas
In principal, there can be only one or no fairway area at each point
on the map. Since polygons from real data will often be topologically
inexact, just disallow equal geometries. This will also help to
avoid importing duplicates with concurrent imports, once the history
of fairway dimensions will be preserved.
author | Tom Gottfried <tom@intevation.de> |
---|---|
date | Wed, 25 Mar 2020 18:10:02 +0100 |
parents | 8c590ef35280 |
children | 73563c4bba5b |
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 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 }