104 lines
3.1 KiB
Go
104 lines
3.1 KiB
Go
package woodpecker
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// APIError represents a failure returned by the Woodpecker API or an
|
|
// unrecoverable error while communicating with it. The Status field is an
|
|
// HTTP status code when the failure came from the API, or 500 for transport
|
|
// and decoding errors, mirroring the behaviour of the original client.
|
|
type APIError struct {
|
|
Status int
|
|
Message string
|
|
}
|
|
|
|
func (e *APIError) Error() string {
|
|
if e.Message == "" {
|
|
return fmt.Sprintf("woodpecker api error (status %d)", e.Status)
|
|
}
|
|
return fmt.Sprintf("woodpecker api error (status %d): %s", e.Status, e.Message)
|
|
}
|
|
|
|
// errorContext is the structured payload returned to the MCP client when a
|
|
// tool call fails. The shape is kept identical to the original TypeScript
|
|
// implementation so existing MCP clients see the same output.
|
|
type errorContext struct {
|
|
Description string `json:"description"`
|
|
Suggestions []string `json:"suggestions"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// ToContext renders a client-facing error context for the given operation.
|
|
// The status code drives both the description and the suggested next steps.
|
|
func (e *APIError) ToContext(context string) string {
|
|
description := "Request failed for " + context
|
|
if e.Message != "" {
|
|
description += ": " + e.Message
|
|
}
|
|
ctx := errorContext{
|
|
Description: description,
|
|
Suggestions: []string{
|
|
"Check your network connection",
|
|
"Verify the Woodpecker CI server URL is correct",
|
|
"Try again in a few moments",
|
|
},
|
|
Error: "UNKNOWN_ERROR",
|
|
}
|
|
switch e.Status {
|
|
case 401:
|
|
ctx = errorContext{
|
|
Description: "Authentication failed for " + context,
|
|
Suggestions: []string{
|
|
"Check that your WOODPECKER_TOKEN environment variable is set correctly",
|
|
"Verify the token has not expired",
|
|
"Ensure the token has sufficient permissions",
|
|
},
|
|
Error: "AUTH_ERROR",
|
|
}
|
|
case 403:
|
|
ctx = errorContext{
|
|
Description: "Access denied for " + context,
|
|
Suggestions: []string{
|
|
"Check that you have permission to access this resource",
|
|
"Verify the repository/pipeline exists and is accessible",
|
|
"Contact your Woodpecker CI administrator if needed",
|
|
},
|
|
Error: "PERMISSION_ERROR",
|
|
}
|
|
case 404:
|
|
ctx = errorContext{
|
|
Description: "Resource not found for " + context,
|
|
Suggestions: []string{
|
|
"Double-check the repository name, pipeline number, or step ID",
|
|
"Verify the resource exists and hasn't been deleted",
|
|
},
|
|
Error: "NOT_FOUND",
|
|
}
|
|
case 429:
|
|
ctx = errorContext{
|
|
Description: "Rate limit exceeded for " + context,
|
|
Suggestions: []string{
|
|
"Wait a moment before retrying",
|
|
"Consider reducing the frequency of requests",
|
|
},
|
|
Error: "RATE_LIMIT",
|
|
}
|
|
case 500, 502, 503, 504:
|
|
ctx = errorContext{
|
|
Description: "Woodpecker CI server error for " + context,
|
|
Suggestions: []string{
|
|
"Try again in a few moments",
|
|
"Check Woodpecker CI server status",
|
|
"Contact your administrator if the issue persists",
|
|
},
|
|
Error: "SERVER_ERROR",
|
|
}
|
|
}
|
|
|
|
// Marshalling a plain struct never fails; ignore the error defensively.
|
|
b, _ := json.Marshal(ctx)
|
|
return string(b)
|
|
}
|