Chromium Code Reviews| 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 |
| +} |