errors.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package field
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "strconv"
  18. "strings"
  19. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  20. "k8s.io/apimachinery/pkg/util/sets"
  21. )
  22. // Error is an implementation of the 'error' interface, which represents a
  23. // field-level validation error.
  24. type Error struct {
  25. Type ErrorType
  26. Field string
  27. BadValue interface{}
  28. Detail string
  29. // Origin uniquely identifies where this error was generated from. It is used in testing to
  30. // compare expected errors against actual errors without relying on exact detail string matching.
  31. // This allows tests to verify the correct validation logic triggered the error
  32. // regardless of how the error message might be formatted or localized.
  33. //
  34. // The value should be either:
  35. // - A simple camelCase identifier (e.g., "maximum", "maxItems")
  36. // - A structured format using "format=<dash-style-identifier>" for validation errors related to specific formats
  37. // (e.g., "format=dns-label", "format=qualified-name")
  38. //
  39. // If the Origin corresponds to an existing declarative validation tag or JSON Schema keyword,
  40. // use that same name for consistency.
  41. //
  42. // Origin should be set in the most deeply nested validation function that
  43. // can still identify the unique source of the error.
  44. Origin string
  45. // CoveredByDeclarative is true when this error is covered by declarative
  46. // validation. This field is to identify errors from imperative validation
  47. // that should also be caught by declarative validation.
  48. CoveredByDeclarative bool
  49. }
  50. var _ error = &Error{}
  51. // Error implements the error interface.
  52. func (e *Error) Error() string {
  53. return fmt.Sprintf("%s: %s", e.Field, e.ErrorBody())
  54. }
  55. type OmitValueType struct{}
  56. var omitValue = OmitValueType{}
  57. // ErrorBody returns the error message without the field name. This is useful
  58. // for building nice-looking higher-level error reporting.
  59. func (e *Error) ErrorBody() string {
  60. var s string
  61. switch e.Type {
  62. case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
  63. s = e.Type.String()
  64. case ErrorTypeInvalid, ErrorTypeTypeInvalid, ErrorTypeNotSupported,
  65. ErrorTypeNotFound, ErrorTypeDuplicate, ErrorTypeTooMany:
  66. if e.BadValue == omitValue {
  67. s = e.Type.String()
  68. break
  69. }
  70. switch t := e.BadValue.(type) {
  71. case int64, int32, float64, float32, bool:
  72. // use simple printer for simple types
  73. s = fmt.Sprintf("%s: %v", e.Type, t)
  74. case string:
  75. s = fmt.Sprintf("%s: %q", e.Type, t)
  76. default:
  77. // use more complex techniques to render more complex types
  78. valstr := ""
  79. jb, err := json.Marshal(e.BadValue)
  80. if err == nil {
  81. // best case
  82. valstr = string(jb)
  83. } else if stringer, ok := e.BadValue.(fmt.Stringer); ok {
  84. // anything that defines String() is better than raw struct
  85. valstr = stringer.String()
  86. } else {
  87. // worst case - fallback to raw struct
  88. // TODO: internal types have panic guards against json.Marshalling to prevent
  89. // accidental use of internal types in external serialized form. For now, use
  90. // %#v, although it would be better to show a more expressive output in the future
  91. valstr = fmt.Sprintf("%#v", e.BadValue)
  92. }
  93. s = fmt.Sprintf("%s: %s", e.Type, valstr)
  94. }
  95. default:
  96. internal := InternalError(nil, fmt.Errorf("unhandled error code: %s: please report this", e.Type))
  97. s = internal.ErrorBody()
  98. }
  99. if len(e.Detail) != 0 {
  100. s += fmt.Sprintf(": %s", e.Detail)
  101. }
  102. return s
  103. }
  104. // WithOrigin adds origin information to the FieldError
  105. func (e *Error) WithOrigin(o string) *Error {
  106. e.Origin = o
  107. return e
  108. }
  109. // MarkCoveredByDeclarative marks the error as covered by declarative validation.
  110. func (e *Error) MarkCoveredByDeclarative() *Error {
  111. e.CoveredByDeclarative = true
  112. return e
  113. }
  114. // ErrorType is a machine readable value providing more detail about why
  115. // a field is invalid. These values are expected to match 1-1 with
  116. // CauseType in api/types.go.
  117. type ErrorType string
  118. // TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it.
  119. const (
  120. // ErrorTypeNotFound is used to report failure to find a requested value
  121. // (e.g. looking up an ID). See NotFound().
  122. ErrorTypeNotFound ErrorType = "FieldValueNotFound"
  123. // ErrorTypeRequired is used to report required values that are not
  124. // provided (e.g. empty strings, null values, or empty arrays). See
  125. // Required().
  126. ErrorTypeRequired ErrorType = "FieldValueRequired"
  127. // ErrorTypeDuplicate is used to report collisions of values that must be
  128. // unique (e.g. unique IDs). See Duplicate().
  129. ErrorTypeDuplicate ErrorType = "FieldValueDuplicate"
  130. // ErrorTypeInvalid is used to report malformed values (e.g. failed regex
  131. // match, too long, out of bounds). See Invalid().
  132. ErrorTypeInvalid ErrorType = "FieldValueInvalid"
  133. // ErrorTypeNotSupported is used to report unknown values for enumerated
  134. // fields (e.g. a list of valid values). See NotSupported().
  135. ErrorTypeNotSupported ErrorType = "FieldValueNotSupported"
  136. // ErrorTypeForbidden is used to report valid (as per formatting rules)
  137. // values which would be accepted under some conditions, but which are not
  138. // permitted by the current conditions (such as security policy). See
  139. // Forbidden().
  140. ErrorTypeForbidden ErrorType = "FieldValueForbidden"
  141. // ErrorTypeTooLong is used to report that the given value is too long.
  142. // This is similar to ErrorTypeInvalid, but the error will not include the
  143. // too-long value. See TooLong().
  144. ErrorTypeTooLong ErrorType = "FieldValueTooLong"
  145. // ErrorTypeTooMany is used to report "too many". This is used to
  146. // report that a given list has too many items. This is similar to FieldValueTooLong,
  147. // but the error indicates quantity instead of length.
  148. ErrorTypeTooMany ErrorType = "FieldValueTooMany"
  149. // ErrorTypeInternal is used to report other errors that are not related
  150. // to user input. See InternalError().
  151. ErrorTypeInternal ErrorType = "InternalError"
  152. // ErrorTypeTypeInvalid is for the value did not match the schema type for that field
  153. ErrorTypeTypeInvalid ErrorType = "FieldValueTypeInvalid"
  154. )
  155. // String converts a ErrorType into its corresponding canonical error message.
  156. func (t ErrorType) String() string {
  157. switch t {
  158. case ErrorTypeNotFound:
  159. return "Not found"
  160. case ErrorTypeRequired:
  161. return "Required value"
  162. case ErrorTypeDuplicate:
  163. return "Duplicate value"
  164. case ErrorTypeInvalid:
  165. return "Invalid value"
  166. case ErrorTypeNotSupported:
  167. return "Unsupported value"
  168. case ErrorTypeForbidden:
  169. return "Forbidden"
  170. case ErrorTypeTooLong:
  171. return "Too long"
  172. case ErrorTypeTooMany:
  173. return "Too many"
  174. case ErrorTypeInternal:
  175. return "Internal error"
  176. case ErrorTypeTypeInvalid:
  177. return "Invalid value"
  178. default:
  179. return fmt.Sprintf("<unknown error %q>", string(t))
  180. }
  181. }
  182. // TypeInvalid returns a *Error indicating "type is invalid"
  183. func TypeInvalid(field *Path, value interface{}, detail string) *Error {
  184. return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, "", false}
  185. }
  186. // NotFound returns a *Error indicating "value not found". This is
  187. // used to report failure to find a requested value (e.g. looking up an ID).
  188. func NotFound(field *Path, value interface{}) *Error {
  189. return &Error{ErrorTypeNotFound, field.String(), value, "", "", false}
  190. }
  191. // Required returns a *Error indicating "value required". This is used
  192. // to report required values that are not provided (e.g. empty strings, null
  193. // values, or empty arrays).
  194. func Required(field *Path, detail string) *Error {
  195. return &Error{ErrorTypeRequired, field.String(), "", detail, "", false}
  196. }
  197. // Duplicate returns a *Error indicating "duplicate value". This is
  198. // used to report collisions of values that must be unique (e.g. names or IDs).
  199. func Duplicate(field *Path, value interface{}) *Error {
  200. return &Error{ErrorTypeDuplicate, field.String(), value, "", "", false}
  201. }
  202. // Invalid returns a *Error indicating "invalid value". This is used
  203. // to report malformed values (e.g. failed regex match, too long, out of bounds).
  204. func Invalid(field *Path, value interface{}, detail string) *Error {
  205. return &Error{ErrorTypeInvalid, field.String(), value, detail, "", false}
  206. }
  207. // NotSupported returns a *Error indicating "unsupported value".
  208. // This is used to report unknown values for enumerated fields (e.g. a list of
  209. // valid values).
  210. func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *Error {
  211. detail := ""
  212. if len(validValues) > 0 {
  213. quotedValues := make([]string, len(validValues))
  214. for i, v := range validValues {
  215. quotedValues[i] = strconv.Quote(fmt.Sprint(v))
  216. }
  217. detail = "supported values: " + strings.Join(quotedValues, ", ")
  218. }
  219. return &Error{ErrorTypeNotSupported, field.String(), value, detail, "", false}
  220. }
  221. // Forbidden returns a *Error indicating "forbidden". This is used to
  222. // report valid (as per formatting rules) values which would be accepted under
  223. // some conditions, but which are not permitted by current conditions (e.g.
  224. // security policy).
  225. func Forbidden(field *Path, detail string) *Error {
  226. return &Error{ErrorTypeForbidden, field.String(), "", detail, "", false}
  227. }
  228. // TooLong returns a *Error indicating "too long". This is used to report that
  229. // the given value is too long. This is similar to Invalid, but the returned
  230. // error will not include the too-long value. If maxLength is negative, it will
  231. // be included in the message. The value argument is not used.
  232. func TooLong(field *Path, _ interface{}, maxLength int) *Error {
  233. var msg string
  234. if maxLength >= 0 {
  235. bs := "bytes"
  236. if maxLength == 1 {
  237. bs = "byte"
  238. }
  239. msg = fmt.Sprintf("may not be more than %d %s", maxLength, bs)
  240. } else {
  241. msg = "value is too long"
  242. }
  243. return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg, "", false}
  244. }
  245. // TooLongMaxLength returns a *Error indicating "too long".
  246. // Deprecated: Use TooLong instead.
  247. func TooLongMaxLength(field *Path, value interface{}, maxLength int) *Error {
  248. return TooLong(field, "", maxLength)
  249. }
  250. // TooMany returns a *Error indicating "too many". This is used to
  251. // report that a given list has too many items. This is similar to TooLong,
  252. // but the returned error indicates quantity instead of length.
  253. func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
  254. var msg string
  255. if maxQuantity >= 0 {
  256. is := "items"
  257. if maxQuantity == 1 {
  258. is = "item"
  259. }
  260. msg = fmt.Sprintf("must have at most %d %s", maxQuantity, is)
  261. } else {
  262. msg = "has too many items"
  263. }
  264. var actual interface{}
  265. if actualQuantity >= 0 {
  266. actual = actualQuantity
  267. } else {
  268. actual = omitValue
  269. }
  270. return &Error{ErrorTypeTooMany, field.String(), actual, msg, "", false}
  271. }
  272. // InternalError returns a *Error indicating "internal error". This is used
  273. // to signal that an error was found that was not directly related to user
  274. // input. The err argument must be non-nil.
  275. func InternalError(field *Path, err error) *Error {
  276. return &Error{ErrorTypeInternal, field.String(), nil, err.Error(), "", false}
  277. }
  278. // ErrorList holds a set of Errors. It is plausible that we might one day have
  279. // non-field errors in this same umbrella package, but for now we don't, so
  280. // we can keep it simple and leave ErrorList here.
  281. type ErrorList []*Error
  282. // NewErrorTypeMatcher returns an errors.Matcher that returns true
  283. // if the provided error is a Error and has the provided ErrorType.
  284. func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
  285. return func(err error) bool {
  286. if e, ok := err.(*Error); ok {
  287. return e.Type == t
  288. }
  289. return false
  290. }
  291. }
  292. // WithOrigin sets the origin for all errors in the list and returns the updated list.
  293. func (list ErrorList) WithOrigin(origin string) ErrorList {
  294. for _, err := range list {
  295. err.Origin = origin
  296. }
  297. return list
  298. }
  299. // MarkCoveredByDeclarative marks all errors in the list as covered by declarative validation.
  300. func (list ErrorList) MarkCoveredByDeclarative() ErrorList {
  301. for _, err := range list {
  302. err.CoveredByDeclarative = true
  303. }
  304. return list
  305. }
  306. // PrefixDetail adds a prefix to the Detail for all errors in the list and returns the updated list.
  307. func (list ErrorList) PrefixDetail(prefix string) ErrorList {
  308. for _, err := range list {
  309. err.Detail = prefix + err.Detail
  310. }
  311. return list
  312. }
  313. // ToAggregate converts the ErrorList into an errors.Aggregate.
  314. func (list ErrorList) ToAggregate() utilerrors.Aggregate {
  315. if len(list) == 0 {
  316. return nil
  317. }
  318. errs := make([]error, 0, len(list))
  319. errorMsgs := sets.NewString()
  320. for _, err := range list {
  321. msg := fmt.Sprintf("%v", err)
  322. if errorMsgs.Has(msg) {
  323. continue
  324. }
  325. errorMsgs.Insert(msg)
  326. errs = append(errs, err)
  327. }
  328. return utilerrors.NewAggregate(errs)
  329. }
  330. func fromAggregate(agg utilerrors.Aggregate) ErrorList {
  331. errs := agg.Errors()
  332. list := make(ErrorList, len(errs))
  333. for i := range errs {
  334. list[i] = errs[i].(*Error)
  335. }
  336. return list
  337. }
  338. // Filter removes items from the ErrorList that match the provided fns.
  339. func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
  340. err := utilerrors.FilterOut(list.ToAggregate(), fns...)
  341. if err == nil {
  342. return nil
  343. }
  344. // FilterOut takes an Aggregate and returns an Aggregate
  345. return fromAggregate(err.(utilerrors.Aggregate))
  346. }
  347. // ExtractCoveredByDeclarative returns a new ErrorList containing only the errors that should be covered by declarative validation.
  348. func (list ErrorList) ExtractCoveredByDeclarative() ErrorList {
  349. newList := ErrorList{}
  350. for _, err := range list {
  351. if err.CoveredByDeclarative {
  352. newList = append(newList, err)
  353. }
  354. }
  355. return newList
  356. }
  357. // RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation.
  358. func (list ErrorList) RemoveCoveredByDeclarative() ErrorList {
  359. newList := ErrorList{}
  360. for _, err := range list {
  361. if !err.CoveredByDeclarative {
  362. newList = append(newList, err)
  363. }
  364. }
  365. return newList
  366. }