github.com/juju/charmrepo/v7@v7.0.1/csclient/params/error.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package params // import "github.com/juju/charmrepo/v7/csclient/params" 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" 11 "gopkg.in/errgo.v1" 12 ) 13 14 // ErrorCode holds the class of an error in machine-readable format. 15 // It is also an error in its own right. 16 type ErrorCode string 17 18 func (code ErrorCode) Error() string { 19 return string(code) 20 } 21 22 func (code ErrorCode) ErrorCode() ErrorCode { 23 return code 24 } 25 26 const ( 27 // ErrOther is used as an error code when the 28 // charmstore returns an empty error code. 29 ErrOther ErrorCode = "other charmstore error" 30 ErrNotFound ErrorCode = "not found" 31 ErrMetadataNotFound ErrorCode = "metadata not found" 32 ErrForbidden ErrorCode = "forbidden" 33 ErrBadRequest ErrorCode = "bad request" 34 // TODO change to ErrAlreadyExists 35 ErrDuplicateUpload ErrorCode = "duplicate upload" 36 ErrMultipleErrors ErrorCode = "multiple errors" 37 ErrUnauthorized ErrorCode = "unauthorized" 38 ErrMethodNotAllowed ErrorCode = "method not allowed" 39 ErrServiceUnavailable ErrorCode = "service unavailable" 40 ErrEntityIdNotAllowed ErrorCode = "charm or bundle id not allowed" 41 ErrInvalidEntity ErrorCode = "invalid charm or bundle" 42 ErrReadOnly ErrorCode = "charmstore is in read-only mode" 43 44 // Note that these error codes sit in the same name space 45 // as the bakery error codes defined in gopkg.in/macaroon-bakery.v0/httpbakery . 46 // In particular, ErrBadRequest is a shared error code 47 // which needs to share the message too. 48 ) 49 50 // Error represents an error - it is returned for any response that fails. 51 // See https://github.com/juju/charmstore/blob/v4/docs/API.md#errors 52 type Error struct { 53 Message string 54 Code ErrorCode 55 Info map[string]*Error `json:",omitempty"` 56 } 57 58 // NewError returns a new *Error with the given error code 59 // and message. 60 func NewError(code ErrorCode, f string, a ...interface{}) error { 61 return &Error{ 62 Message: fmt.Sprintf(f, a...), 63 Code: code, 64 } 65 } 66 67 // Error implements error.Error. 68 func (e *Error) Error() string { 69 return e.Message 70 } 71 72 // ErrorCode holds the class of the error in 73 // machine readable format. 74 func (e *Error) ErrorCode() string { 75 return e.Code.Error() 76 } 77 78 // ErrorInfo returns additional info on the error. 79 // TODO(rog) rename this so that it more accurately 80 // reflects its role. 81 func (e *Error) ErrorInfo() map[string]*Error { 82 return e.Info 83 } 84 85 // Cause implements errgo.Causer.Cause. 86 func (e *Error) Cause() error { 87 if e.Code != "" { 88 return e.Code 89 } 90 return ErrOther 91 } 92 93 // TermAgreementRequiredError signals that the user 94 // needs to agree to a set of terms and agreements 95 // in order to complete an operation. 96 type TermAgreementRequiredError struct { 97 Terms []string 98 } 99 100 // Error implements the error interface. 101 func (e *TermAgreementRequiredError) Error() string { 102 return fmt.Sprintf("term agreement required %q", strings.Join(e.Terms, " ")) 103 } 104 105 // MaybeTermsAgreementError returns err as a *TermAgreementRequiredError 106 // if it has a "terms agreement required" error code, otherwise 107 // it returns err unchanged. 108 func MaybeTermsAgreementError(err error) error { 109 const code = "term agreement required" 110 e, ok := errgo.Cause(err).(*httpbakery.DischargeError) 111 if !ok || e.Reason == nil || e.Reason.Code != code { 112 return err 113 } 114 // Terms are guaranteed not to contain colon characters, so 115 // there's no ambiguity when finding where the listed terms start. 116 magicMarker := code + ":" 117 index := strings.LastIndex(e.Reason.Message, magicMarker) 118 if index == -1 { 119 return err 120 } 121 return &TermAgreementRequiredError{ 122 Terms: strings.Fields(e.Reason.Message[index+len(magicMarker):]), 123 } 124 }