mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-13 08:50:22 +08:00
* feat(pluginsdk): extension protocol v1, Go SDK and conformance suite * feat(plugin): run code plugins in an embedded plugin host * feat(plugin): remote adapters for web search and data sources * fix(plugin): show plugin errors by their message * feat(frontend): plugin connectors and web search providers in the UI * docs(pluginsdk): how to write, package, test and install a plugin * fix(plugin): gofmt two files and read openapi.yaml line-ending agnostic * fix(plugin): mark a host plugin ready before Activate returns
91 lines
2.7 KiB
Go
91 lines
2.7 KiB
Go
package pluginapi
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// ErrorCode classifies a failed call; WeKnora reacts to each differently.
|
|
type ErrorCode string
|
|
|
|
const (
|
|
// CodeInvalidConfig means the configuration is wrong; Details.Fields says
|
|
// where. WeKnora shows it on the form.
|
|
CodeInvalidConfig ErrorCode = "invalid_config"
|
|
// CodeUnauthorized means the credentials no longer work.
|
|
CodeUnauthorized ErrorCode = "unauthorized"
|
|
// CodeNotFound means the resource does not exist.
|
|
CodeNotFound ErrorCode = "not_found"
|
|
// CodeRateLimited means back off for Details.RetryAfter seconds.
|
|
CodeRateLimited ErrorCode = "rate_limited"
|
|
// CodeUnavailable means the upstream service is down; retry later.
|
|
CodeUnavailable ErrorCode = "unavailable"
|
|
// CodeBadRequest means the call itself is malformed (protocol error).
|
|
CodeBadRequest ErrorCode = "bad_request"
|
|
// CodeInternal means the plugin failed.
|
|
CodeInternal ErrorCode = "internal"
|
|
)
|
|
|
|
// Error is a failed call. Plugins return it (or wrap it) to choose the code;
|
|
// any other error becomes CodeInternal.
|
|
type Error struct {
|
|
Code ErrorCode `json:"code"`
|
|
Message string `json:"message"`
|
|
Retryable bool `json:"retryable,omitempty"`
|
|
Details *ErrorDetails `json:"details,omitempty"`
|
|
}
|
|
|
|
// ErrorDetails refines some codes.
|
|
type ErrorDetails struct {
|
|
// Fields are per-field problems for CodeInvalidConfig, keyed by the
|
|
// dotted field path.
|
|
Fields map[string]string `json:"fields,omitempty"`
|
|
// RetryAfter is seconds to wait for CodeRateLimited.
|
|
RetryAfter int `json:"retryAfter,omitempty"`
|
|
}
|
|
|
|
// ErrorBody is the JSON body of a failed call.
|
|
type ErrorBody struct {
|
|
Error Error `json:"error"`
|
|
}
|
|
|
|
func (e *Error) Error() string { return fmt.Sprintf("%s: %s", e.Code, e.Message) }
|
|
|
|
// Errorf builds an Error. Unavailable and rate-limited errors are retryable.
|
|
func Errorf(code ErrorCode, format string, args ...any) *Error {
|
|
return &Error{
|
|
Code: code, Message: fmt.Sprintf(format, args...),
|
|
Retryable: code == CodeUnavailable || code == CodeRateLimited,
|
|
}
|
|
}
|
|
|
|
// InvalidConfig reports field problems, keyed by dotted path.
|
|
func InvalidConfig(message string, fields map[string]string) *Error {
|
|
return &Error{Code: CodeInvalidConfig, Message: message, Details: &ErrorDetails{Fields: fields}}
|
|
}
|
|
|
|
// AsError finds an *Error in err's chain.
|
|
func AsError(err error) (*Error, bool) {
|
|
var e *Error
|
|
ok := errors.As(err, &e)
|
|
return e, ok
|
|
}
|
|
|
|
// HTTPStatus is the status an error code travels with.
|
|
func (c ErrorCode) HTTPStatus() int {
|
|
switch c {
|
|
case CodeInvalidConfig, CodeBadRequest:
|
|
return 400
|
|
case CodeUnauthorized:
|
|
return 401
|
|
case CodeNotFound:
|
|
return 404
|
|
case CodeRateLimited:
|
|
return 429
|
|
case CodeUnavailable:
|
|
return 503
|
|
default:
|
|
return 500
|
|
}
|
|
}
|