MapForgejoError previously classified HTTP results by substring
matching. Two failure modes: URIs/error bodies legitimately
containing "404"/"Forbidden" mis-classified, and parser errors of
shape "invalid URI: expected forgejo://..." fell through to -32603
because the existing "kind must be"/"index must be numeric"
substrings did not cover them.
Introduce resource.ErrInvalidParams sentinel; wrap every parser
return path with it (Parse{Owner,Repo,Commit,Issue,PR,Comment,Status},
parseForgejoURI, validateSHA). MapForgejoError now classifies via
errors.Is(err, ErrInvalidParams) before falling back to HTTP
substring matching for 403/404. Substring matching for 403/404
stays as a transition fallback because the SDK still surfaces
HTTP status only via formatted error strings.
Update handler tests that asserted Code == -32603 on parse errors
(set by forgejo-mcp-vzz) to assert -32602.
Closes forgejo-mcp-wgs.
Ref PR #172 review https://codeberg.org/goern/forgejo-mcp/pulls/172#issuecomment-16022165
44 lines
1.5 KiB
Go
44 lines
1.5 KiB
Go
package resource
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ResourceError wraps a forgejo URI and an HTTP status code into a structured error
|
|
// for MCP resource handlers.
|
|
type ResourceError struct {
|
|
URI string
|
|
Code int
|
|
Message string
|
|
}
|
|
|
|
func (e *ResourceError) Error() string {
|
|
return fmt.Sprintf("resource %q: %s (code %d)", e.URI, e.Message, e.Code)
|
|
}
|
|
|
|
// MapForgejoError maps a forgejo SDK error to a ResourceError using the error
|
|
// message to detect HTTP status codes. Returns a *ResourceError with:
|
|
// - Code -32002 for HTTP 403 access-denied responses
|
|
// - Code -32003 for HTTP 404 not-found responses
|
|
// - Code -32602 for invalid-params errors (ErrInvalidParams sentinel or parser phrases)
|
|
// - Code -32603 (internal error) for all other errors
|
|
func MapForgejoError(uri string, err error) *ResourceError {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
// Check sentinel first — covers all parse-time invalid-input errors.
|
|
if errors.Is(err, ErrInvalidParams) {
|
|
return &ResourceError{URI: uri, Code: -32602, Message: "invalid params: " + err.Error()}
|
|
}
|
|
msg := err.Error()
|
|
switch {
|
|
case strings.Contains(msg, "403") || strings.Contains(msg, "Forbidden") || strings.Contains(msg, "forbidden"):
|
|
return &ResourceError{URI: uri, Code: -32002, Message: "access denied: " + msg}
|
|
case strings.Contains(msg, "404") || strings.Contains(msg, "Not Found") || strings.Contains(msg, "not found"):
|
|
return &ResourceError{URI: uri, Code: -32003, Message: "not found: " + msg}
|
|
default:
|
|
return &ResourceError{URI: uri, Code: -32603, Message: "internal error: " + msg}
|
|
}
|
|
}
|