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}