Index: common/errors/tags.go |
diff --git a/common/errors/tags.go b/common/errors/tags.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b80f135c94bda7fa9280987201735df73a0155ed |
--- /dev/null |
+++ b/common/errors/tags.go |
@@ -0,0 +1,160 @@ |
+// Copyright 2017 The LUCI Authors. All rights reserved. |
+// Use of this source code is governed under the Apache License, Version 2.0 |
+// that can be found in the LICENSE file. |
+ |
+package errors |
+ |
+type ( |
+ // TagKeyOrValue is used for functions taking either a TagKey or a TagValue. |
+ TagKeyOrValue interface { |
+ isTagKeyOrValue() |
+ } |
+ |
+ tagDescription struct { |
+ description string |
+ } |
+ |
+ // TagKey objects are used for applying tags and finding tags/values in |
+ // errors. See NewTag for details. |
+ TagKey *tagDescription |
+ |
+ // TagValue represents a (tag, value) to be used with Annotate.Tag, or may be |
+ // applied to an error directly with the Apply method. |
+ // |
+ // Usually tag implementations will have a typesafe With method that generates |
+ // these. Avoid constructing these ad-hoc so that a given tag definition can |
+ // control the type safety around these. |
+ TagValue struct { |
+ Key TagKey |
+ Value interface{} |
+ } |
+ |
+ // A TagValueGenerator generates (TagKey, value) pairs, for use with Annoatator.Tag |
+ // and New(). |
+ TagValueGenerator interface { |
dnj
2017/06/27 03:57:03
There are a lot of tag functions. I'm seeing two c
iannucci
2017/06/27 18:14:27
I considered it; as it is, the errors library dele
|
+ GenerateErrorTagValue() TagValue |
+ } |
+) |
+ |
+// TagValueIn will retrieve the tagged value from the error that's associated |
+// with this key, and a boolean indicating if the tag was present or not. |
+func TagValueIn(t TagKey, err error) (value interface{}, ok bool) { |
+ Walk(err, func(err error) bool { |
+ if sc, isSC := err.(stackContexter); isSC { |
+ if value, ok = sc.stackContext().tags[t]; ok { |
+ return false |
+ } |
+ } |
+ return true |
+ }) |
+ return |
+} |
+ |
+// GenerateErrorTagValue implements TagValueGenerator |
+func (t TagValue) GenerateErrorTagValue() TagValue { return t } |
+ |
+// Apply applies this tag value (key+value) directly to the error. This is |
+// a shortcut for `errors.Annotate(err).Tag(t).Err()`. |
+func (t TagValue) Apply(err error) error { |
+ if err == nil { |
+ return nil |
+ } |
+ a := &Annotator{err, stackContext{frameInfo: stackFrameInfoForError(1, err)}} |
+ return a.Tag(t).Err() |
+} |
+ |
+// MkTagValue is a shortcut for errors.TagValue{Key: key, Value: val}. |
+func MkTagValue(key TagKey, val interface{}) TagValue { |
+ return TagValue{key, val} |
+} |
+ |
+// BoolTag is an error tag implementation which holds a boolean value. |
+// |
+// It should be constructed like: |
+// var myTag = errors.BoolTag{Key: errors.NewTagKey("some description")} |
+type BoolTag struct{ Key TagKey } |
+ |
+// GenerateErrorTagValue implements TagValueGenerator, and returns a default |
+// value for the tag of `true`. If you want to set this BoolTag value to false, |
+// use BoolTag.Off(). |
+func (b BoolTag) GenerateErrorTagValue() TagValue { return TagValue{b.Key, true} } |
+ |
+// Off allows you to "remove" this boolean tag from an error (by setting it to |
dnj
2017/06/27 03:57:03
Not sure if this is a good thing or not. Oh well,
iannucci
2017/06/27 18:14:27
Yeah, we'll see. I put it in because otherwise it
|
+// false). |
+func (b BoolTag) Off() TagValue { return TagValue{b.Key, false} } |
+ |
+// Apply is a shortcut for With(true).Apply(err) |
+func (b BoolTag) Apply(err error) error { |
+ if err == nil { |
+ return nil |
+ } |
+ a := &Annotator{err, stackContext{frameInfo: stackFrameInfoForError(1, err)}} |
+ return a.Tag(b).Err() |
+} |
+ |
+// In returns true iff this tag value has been set to true on this error. |
+func (b BoolTag) In(err error) bool { |
+ v, ok := TagValueIn(b.Key, err) |
+ if !ok { |
+ return false |
+ } |
+ return v.(bool) |
+} |
+ |
+// NewTagKey creates a new TagKey. |
+// |
+// Use this with a BoolTag or your own custom tag implementation. |
+// |
+// Example (bool tag): |
+// var myTag = errors.BoolTag{Key: errors.NewTagKey("this error is a user error")} |
+// |
+// err = myTag.Apply(err) |
+// myTag.In(err) // == true |
+// |
+// err2 := myTag.Off().Apply(err) |
+// myTag.In(err2) // == false |
+// |
+// Example (custom tag) |
+// type SomeType int |
+// type myTag struct { Key errors.TagKey } |
+// func (m myTag) With(value SomeType) errors.TagValue { |
+// return errors.MkTagValue(vm.Key, value) |
+// } |
+// func (m myTag) In(err error) (v SomeType, ok bool) { |
+// d, ok := errors.TagValueIn(m.Key, err) |
+// if ok { |
+// v = d.(SomeType) |
+// } |
+// return |
+// } |
+// var MyTag = myTag{errors.NewTagKey("has a SomeType")} |
+// |
+// You could then use it like: |
+// err = MyTag.With(100).Apply(err) |
+// MyTag.In(err) // == true |
+// MyTag.ValueIn(err) // == (SomeType(100), true) |
+func NewTagKey(description string) TagKey { |
+ return &tagDescription{description} |
+} |
+ |
+// GetTags returns a map of all TagKeys set in this error to their value. |
+// |
+// A nil value means that the tag is present, but has a nil associated value. |
+// |
+// This is done in a depth-first traversal of the error stack, with the |
+// most-recently-set value of the tag taking precendence. |
+func GetTags(err error) map[TagKey]interface{} { |
+ ret := map[TagKey]interface{}{} |
+ Walk(err, func(err error) bool { |
+ if sc, ok := err.(stackContexter); ok { |
+ ctx := sc.stackContext() |
+ for k, v := range ctx.tags { |
+ if _, ok := ret[k]; !ok { |
+ ret[k] = v |
+ } |
+ } |
+ } |
+ return true |
+ }) |
+ return ret |
+} |