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 }