// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-present Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // Package output defines stable error codes and the JSON envelope // veans uses for non-zero exits. package output import ( "encoding/json" "errors" "fmt" "io" "os" ) type Code string const ( CodeNotFound Code = "NOT_FOUND" CodeConflict Code = "CONFLICT" CodeValidation Code = "VALIDATION_ERROR" CodeAuth Code = "AUTH_ERROR" CodeRateLimited Code = "RATE_LIMITED" CodeBotUsersUnavailable Code = "BOT_USERS_UNAVAILABLE" CodeNotConfigured Code = "NOT_CONFIGURED" CodeUnknown Code = "UNKNOWN" ) // Error is the structured error type used for both internal flow and the // `--json` mutation envelope. Callers wrap underlying errors with codes via // Wrap; the cobra runner converts unmapped errors to CodeUnknown. type Error struct { Code Code `json:"code"` Message string `json:"error"` Cause error `json:"-"` } func (e *Error) Error() string { return e.Message } func (e *Error) Unwrap() error { return e.Cause } func New(code Code, format string, args ...any) *Error { return &Error{Code: code, Message: fmt.Sprintf(format, args...)} } func Wrap(code Code, cause error, format string, args ...any) *Error { return &Error{Code: code, Message: fmt.Sprintf(format, args...), Cause: cause} } // AsError extracts an *Error from any error chain, returning a CodeUnknown // wrapper for plain errors. func AsError(err error) *Error { if err == nil { return nil } var e *Error if errors.As(err, &e) { return e } return &Error{Code: CodeUnknown, Message: err.Error(), Cause: err} } // EmitError encodes the error as a JSON envelope `{code, error}` to w // (default stderr). All veans commands share this shape so callers can // branch on `code` without sniffing the output format. func EmitError(err error, w io.Writer) { if w == nil { w = os.Stderr } e := AsError(err) if encErr := json.NewEncoder(w).Encode(e); encErr != nil { fmt.Fprintf(os.Stderr, "{\"code\":%q,\"error\":%q}\n", string(CodeUnknown), "failed to encode error envelope: "+encErr.Error()) } }