view pkg/pgxutils/errors.go @ 4611:b5aa1eb83bb0 geoserver_sql_views

Add possibility to configure SRS for GeoServer SQL view Automatic detection of spatial reference system for SQL views in GeoServer does not always find the correct SRS.
author Tom Gottfried <tom@intevation.de>
date Fri, 06 Sep 2019 11:58:03 +0200
parents bd97dc2dceea
children 940b53bdf871
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 (
	notNullViolation         = "23502"
	foreignKeyViolation      = "23503"
	uniqueViolation          = "23505"
	checkViolation           = "23514"
	violatesRowLevelSecurity = "42501"
	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

	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 "gauge_key", "waterway_bottlenecks_reference_gauge":
					m = "Referenced gauge with matching temporal validity not available"
					return
				}
			}
		}
	case uniqueViolation:
		switch err.SchemaName {
		case "internal":
			switch err.TableName {
			case "user_profiles":
				switch err.ConstraintName {
				case "user_profiles_pkey":
					m = "A user with that name already exists"
					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 noDataFound:
		// Most recent line from stacktrace contains name of failed function
		recent := strings.SplitN(err.Where, "\n", 1)[0]
		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 violatesRowLevelSecurity:
		m = "Could not save: Data outside the area of responsibility."
		return
	}
	m = "Unexpected database error: " + err.Message
	return
}