error.go

  1package graphhopper
  2
  3import (
  4	"encoding/json"
  5	"errors"
  6	"fmt"
  7	"strings"
  8)
  9
 10// RouteTooFarError indicates that the routing points are too far apart for the API
 11type RouteTooFarError struct {
 12	Message string
 13}
 14
 15func (e *RouteTooFarError) Error() string {
 16	return fmt.Sprintf("route too far: %s", e.Message)
 17}
 18
 19// Is allows errors.Is to detect RouteTooFarError through error wrapping
 20func (e *RouteTooFarError) Is(target error) bool {
 21	_, ok := target.(*RouteTooFarError)
 22	return ok
 23}
 24
 25// PointNotFoundError indicates that a point could not be snapped to the road network
 26type PointNotFoundError struct {
 27	Message string
 28}
 29
 30func (e *PointNotFoundError) Error() string {
 31	return fmt.Sprintf("point not found: %s", e.Message)
 32}
 33
 34// Is allows errors.Is to detect PointNotFoundError through error wrapping
 35func (e *PointNotFoundError) Is(target error) bool {
 36	_, ok := target.(*PointNotFoundError)
 37	return ok
 38}
 39
 40// NonRetryableError interface for errors that should not be retried
 41type NonRetryableError interface {
 42	error
 43	NonRetryable()
 44}
 45
 46// NonRetryable markers for error types
 47func (e *RouteTooFarError) NonRetryable()   {}
 48func (e *PointNotFoundError) NonRetryable() {}
 49
 50// IsNonRetryable checks if an error is non-retryable (either through interface or type checking)
 51func IsNonRetryable(err error) bool {
 52	if err == nil {
 53		return false
 54	}
 55	// Check if error implements NonRetryableError interface
 56	if _, ok := err.(NonRetryableError); ok {
 57		return true
 58	}
 59	// Also check via errors.Is for wrapped errors
 60	if errors.Is(err, &RouteTooFarError{}) || errors.Is(err, &PointNotFoundError{}) {
 61		return true
 62	}
 63	return false
 64}
 65
 66// APIError represents the JSON error response from Graphhopper
 67type APIError struct {
 68	Message string `json:"message"`
 69}
 70
 71// ParseAPIError parses a Graphhopper API error response and returns appropriate error type
 72// Returns non-retryable errors for 4XX status codes (client errors)
 73func ParseAPIError(statusCode int, body string) error {
 74	var apiErr APIError
 75	if err := json.Unmarshal([]byte(body), &apiErr); err != nil {
 76		return fmt.Errorf("routing API error (status %d): %s", statusCode, body)
 77	}
 78
 79	message := strings.ToLower(apiErr.Message)
 80
 81	// Check for specific error types in 4XX responses
 82	if statusCode >= 400 && statusCode < 500 {
 83		if strings.Contains(message, "too far") {
 84			return &RouteTooFarError{Message: apiErr.Message}
 85		}
 86		if strings.Contains(message, "cannot find point") {
 87			return &PointNotFoundError{Message: apiErr.Message}
 88		}
 89		// Generic client error - wrap with non-retryable marker
 90		return &clientError{
 91			StatusCode: statusCode,
 92			Message:    apiErr.Message,
 93		}
 94	}
 95
 96	return fmt.Errorf("routing API error (status %d): %s", statusCode, apiErr.Message)
 97}
 98
 99// clientError represents a generic 4XX client error that should not be retried
100type clientError struct {
101	StatusCode int
102	Message    string
103}
104
105func (e *clientError) Error() string {
106	return fmt.Sprintf("routing API client error (status %d): %s", e.StatusCode, e.Message)
107}
108
109func (e *clientError) NonRetryable() {}
110
111// Is allows errors.Is to detect clientError through error wrapping
112func (e *clientError) Is(target error) bool {
113	_, ok := target.(*clientError)
114	return ok
115}