feat: add agent-friendly ufile CLI
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type commandError struct {
|
||||
Code string
|
||||
Message string
|
||||
HTTPStatus int
|
||||
ExitCode int
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (e *commandError) Error() string {
|
||||
if e.Cause != nil {
|
||||
return fmt.Sprintf("%s: %v", e.Message, e.Cause)
|
||||
}
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func usageError(message string) *commandError {
|
||||
return &commandError{Code: "usage_error", Message: message, ExitCode: exitUsage}
|
||||
}
|
||||
|
||||
func localError(message string, cause error) *commandError {
|
||||
return &commandError{Code: "local_io_error", Message: message, ExitCode: exitLocalIO, Cause: cause}
|
||||
}
|
||||
|
||||
func networkError(message string, cause error) *commandError {
|
||||
return &commandError{Code: "network_error", Message: message, ExitCode: exitNetwork, Cause: cause}
|
||||
}
|
||||
|
||||
func fromHTTPError(err *apiError) *commandError {
|
||||
code := err.Code
|
||||
exitCode := exitGeneral
|
||||
switch err.Status {
|
||||
case http.StatusBadRequest, http.StatusConflict, http.StatusUnprocessableEntity:
|
||||
exitCode = exitValidation
|
||||
if code == "" {
|
||||
code = "validation_failed"
|
||||
}
|
||||
case http.StatusUnauthorized:
|
||||
exitCode = exitAuth
|
||||
if code == "" {
|
||||
code = "authentication_required"
|
||||
}
|
||||
case http.StatusForbidden:
|
||||
exitCode = exitPermission
|
||||
if code == "" {
|
||||
code = "permission_denied"
|
||||
}
|
||||
case http.StatusNotFound, http.StatusGone:
|
||||
exitCode = exitUnavailable
|
||||
if code == "" {
|
||||
code = "file_unavailable"
|
||||
}
|
||||
default:
|
||||
if err.Status >= 500 {
|
||||
exitCode = exitNetwork
|
||||
}
|
||||
}
|
||||
if code == "" {
|
||||
code = "request_failed"
|
||||
}
|
||||
return &commandError{Code: code, Message: err.Message, HTTPStatus: err.Status, ExitCode: exitCode}
|
||||
}
|
||||
|
||||
func classifyError(err error) *commandError {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var commandErr *commandError
|
||||
if errors.As(err, &commandErr) {
|
||||
return commandErr
|
||||
}
|
||||
var remoteErr *apiError
|
||||
if errors.As(err, &remoteErr) {
|
||||
return fromHTTPError(remoteErr)
|
||||
}
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
return networkError("请求 ufile 服务失败", err)
|
||||
}
|
||||
return &commandError{Code: "unexpected_error", Message: "执行失败", ExitCode: exitGeneral, Cause: err}
|
||||
}
|
||||
|
||||
func writeCommandError(out io.Writer, err *commandError) int {
|
||||
payload := struct {
|
||||
Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
HTTPStatus int `json:"httpStatus,omitempty"`
|
||||
} `json:"error"`
|
||||
}{}
|
||||
payload.Error.Code = err.Code
|
||||
payload.Error.Message = err.Message
|
||||
if err.Cause != nil {
|
||||
payload.Error.Message += ": " + err.Cause.Error()
|
||||
}
|
||||
payload.Error.HTTPStatus = err.HTTPStatus
|
||||
encoder := json.NewEncoder(out)
|
||||
encoder.SetEscapeHTML(false)
|
||||
_ = encoder.Encode(payload)
|
||||
if err.ExitCode == 0 {
|
||||
return exitGeneral
|
||||
}
|
||||
return err.ExitCode
|
||||
}
|
||||
|
||||
func writeJSON(out io.Writer, value any) int {
|
||||
encoder := json.NewEncoder(out)
|
||||
encoder.SetEscapeHTML(false)
|
||||
if err := encoder.Encode(value); err != nil {
|
||||
return exitLocalIO
|
||||
}
|
||||
return exitOK
|
||||
}
|
||||
Reference in New Issue
Block a user