From bcd588ec1537108a7a3849cc432eca97e89d9efd Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Thu, 14 Aug 2014 11:54:20 -0700 Subject: [PATCH] Break api error handling into a new pkg --- pkg/api/errors/errors.go | 104 ++++++++++++++++++++++++++++++++++ pkg/api/errors/errors_test.go | 85 +++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 pkg/api/errors/errors.go create mode 100644 pkg/api/errors/errors_test.go diff --git a/pkg/api/errors/errors.go b/pkg/api/errors/errors.go new file mode 100644 index 00000000000..d0b4d53ca04 --- /dev/null +++ b/pkg/api/errors/errors.go @@ -0,0 +1,104 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package errors + +import ( + "fmt" + "strings" +) + +// ValidationErrorEnum is a type of validation error. +type ValidationErrorEnum string + +// These are known errors of validation. +const ( + Invalid ValidationErrorEnum = "invalid value" + NotSupported ValidationErrorEnum = "unsupported value" + Duplicate ValidationErrorEnum = "duplicate value" + NotFound ValidationErrorEnum = "not found" +) + +// ValidationError is an implementation of the 'error' interface, which represents an error of validation. +type ValidationError struct { + Type ValidationErrorEnum + Field string + BadValue interface{} +} + +func (v ValidationError) Error() string { + return fmt.Sprintf("%s: %v '%v'", v.Field, v.Type, v.BadValue) +} + +// NewInvalid returns a ValidationError indicating "invalid value". Use this to +// report malformed values (e.g. failed regex match) or missing "required" fields. +func NewInvalid(field string, value interface{}) ValidationError { + return ValidationError{Invalid, field, value} +} + +// NewNotSupported returns a ValidationError indicating "unsuported value". Use +// this to report valid (as per formatting rules) values that can not be handled +// (e.g. an enumerated string). +func NewNotSupported(field string, value interface{}) ValidationError { + return ValidationError{NotSupported, field, value} +} + +// NewDuplicate returns a ValidationError indicating "duplicate value". Use this +// to report collisions of values that must be unique (e.g. unique IDs). +func NewDuplicate(field string, value interface{}) ValidationError { + return ValidationError{Duplicate, field, value} +} + +// NewNotFound returns a ValidationError indicating "value not found". Use this +// to report failure to find a requested value (e.g. looking up an ID). +func NewNotFound(field string, value interface{}) ValidationError { + return ValidationError{NotFound, field, value} +} + +// ErrorList is a collection of errors. This does not implement the error +// interface to avoid confusion where an empty ErrorList would still be an +// error (non-nil). To produce a single error instance from an ErrorList, use +// the ToError() method, which will return nil for an empty ErrorList. +type ErrorList []error + +// This helper implements the error interface for ErrorList, but must prevents +// accidental conversion of ErrorList to error. +type errorListInternal ErrorList + +// Error is part of the error interface. +func (list errorListInternal) Error() string { + if len(list) == 0 { + return "" + } + sl := make([]string, len(list)) + for i := range list { + sl[i] = list[i].Error() + } + return strings.Join(sl, "; ") +} + +// Deprecated, will be removed soon. +func (list *ErrorList) Append(errs ...error) { + *list = append(*list, errs...) +} + +// ToError converts an ErrorList into a "normal" error, or nil if the list is empty. +func (list ErrorList) ToError() error { + if len(list) == 0 { + return nil + } + return errorListInternal(list) +} diff --git a/pkg/api/errors/errors_test.go b/pkg/api/errors/errors_test.go new file mode 100644 index 00000000000..d42345bbca9 --- /dev/null +++ b/pkg/api/errors/errors_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package errors + +import ( + "fmt" + "testing" +) + +func TestMakeFuncs(t *testing.T) { + testCases := []struct { + fn func() ValidationError + expected ValidationErrorEnum + }{ + { + func() ValidationError { return NewInvalid("f", "v") }, + Invalid, + }, + { + func() ValidationError { return NewNotSupported("f", "v") }, + NotSupported, + }, + { + func() ValidationError { return NewDuplicate("f", "v") }, + Duplicate, + }, + { + func() ValidationError { return NewNotFound("f", "v") }, + NotFound, + }, + } + + for _, testCase := range testCases { + err := testCase.fn() + if err.Type != testCase.expected { + t.Errorf("expected Type %q, got %q", testCase.expected, err.Type) + } + } +} + +func TestErrorList(t *testing.T) { + errList := ErrorList{} + errList = append(errList, NewInvalid("field", "value")) + // The fact that this compiles is the test. +} + +func TestErrorListToError(t *testing.T) { + errList := ErrorList{} + err := errList.ToError() + if err != nil { + t.Errorf("expected nil, got %v", err) + } + + testCases := []struct { + errs ErrorList + expected string + }{ + {ErrorList{fmt.Errorf("abc")}, "abc"}, + {ErrorList{fmt.Errorf("abc"), fmt.Errorf("123")}, "abc; 123"}, + } + for _, testCase := range testCases { + err := testCase.errs.ToError() + if err == nil { + t.Errorf("expected an error, got nil: ErrorList=%v", testCase) + continue + } + if err.Error() != testCase.expected { + t.Errorf("expected %q, got %q", testCase.expected, err.Error()) + } + } +}