124 lines
2.9 KiB
Go
124 lines
2.9 KiB
Go
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
|
|
}
|