merr: refactor Wrap/WrapSkip/New to take multiple contexts rather than annotations

This commit is contained in:
Brian Picciano 2019-02-27 13:05:51 -05:00
parent 4303da03cc
commit 4536db8968
17 changed files with 112 additions and 83 deletions

5
m/m.go
View File

@ -6,7 +6,6 @@ package m
import ( import (
"context" "context"
"log"
"os" "os"
"os/signal" "os/signal"
@ -43,9 +42,9 @@ func ServiceContext() context.Context {
ctx, logLevelStr := mcfg.WithString(ctx, "log-level", "info", "Maximum log level which will be printed.") ctx, logLevelStr := mcfg.WithString(ctx, "log-level", "info", "Maximum log level which will be printed.")
ctx = mrun.WithStartHook(ctx, func(context.Context) error { ctx = mrun.WithStartHook(ctx, func(context.Context) error {
logLevel := mlog.LevelFromString(*logLevelStr) logLevel := mlog.LevelFromString(*logLevelStr)
log.Printf("setting log level to %v", logLevel)
if logLevel == nil { if logLevel == nil {
return merr.New(ctx, "invalid log level", "log-level", *logLevelStr) ctx := mctx.Annotate(ctx, "log-level", *logLevelStr)
return merr.New("invalid log level", ctx)
} }
logger.SetMaxLevel(logLevel) logger.SetMaxLevel(logLevel)
return nil return nil

View File

@ -90,7 +90,8 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
break break
} }
if !pvOk { if !pvOk {
return nil, merr.New(context.Background(), "unexpected config parameter", "param", arg) ctx := mctx.Annotate(context.Background(), "param", arg)
return nil, merr.New("unexpected config parameter", ctx)
} }
} }
@ -121,7 +122,8 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
pvStrValOk = false pvStrValOk = false
} }
if pvOk && !pvStrValOk { if pvOk && !pvStrValOk {
return nil, merr.New(p.Context, "param expected a value", "param", key) ctx := mctx.Annotate(p.Context, "param", key)
return nil, merr.New("param expected a value", ctx)
} }
return pvs, nil return pvs, nil
} }

View File

@ -59,7 +59,8 @@ func (env SourceEnv) Parse(params []Param) ([]ParamValue, error) {
for _, kv := range kvs { for _, kv := range kvs {
split := strings.SplitN(kv, "=", 2) split := strings.SplitN(kv, "=", 2)
if len(split) != 2 { if len(split) != 2 {
return nil, merr.New(context.Background(), "malformed environment key/value pair", "kv", kv) ctx := mctx.Annotate(context.Background(), "kv", kv)
return nil, merr.New("malformed environment key/value pair", ctx)
} }
k, v := split[0], split[1] k, v := split[0], split[1]
if p, ok := pM[k]; ok { if p, ok := pM[k]; ok {

View File

@ -110,8 +110,9 @@ func populate(params []Param, src Source) error {
if !p.Required { if !p.Required {
continue continue
} else if _, ok := pvM[hash]; !ok { } else if _, ok := pvM[hash]; !ok {
return merr.New(p.Context, "required parameter is not set", ctx := mctx.Annotate(p.Context,
"param", paramFullName(mctx.Path(p.Context), p.Name)) "param", paramFullName(mctx.Path(p.Context), p.Name))
return merr.New("required parameter is not set", ctx)
} }
} }
@ -133,7 +134,7 @@ func populate(params []Param, src Source) error {
// of the Params which were provided by the respective Source. // of the Params which were provided by the respective Source.
// //
// Source may be nil to indicate that no configuration is provided. Only default // Source may be nil to indicate that no configuration is provided. Only default
// values will be used, and if any paramaters are required this will error. // values will be used, and if any parameters are required this will error.
func Populate(ctx context.Context, src Source) error { func Populate(ctx context.Context, src Source) error {
return populate(collectParams(ctx), src) return populate(collectParams(ctx), src)
} }

View File

@ -14,6 +14,7 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/mediocregopher/mediocre-go-lib/mctx"
"github.com/mediocregopher/mediocre-go-lib/merr" "github.com/mediocregopher/mediocre-go-lib/merr"
) )
@ -59,7 +60,8 @@ func (pk PublicKey) verify(s Signature, r io.Reader) error {
return err return err
} }
if err := rsa.VerifyPSS(&pk.PublicKey, crypto.SHA256, h.Sum(nil), s.sig, nil); err != nil { if err := rsa.VerifyPSS(&pk.PublicKey, crypto.SHA256, h.Sum(nil), s.sig, nil); err != nil {
return merr.Wrap(context.Background(), ErrInvalidSig, "sig", s) ctx := mctx.Annotate(context.Background(), "sig", s)
return merr.Wrap(ErrInvalidSig, ctx)
} }
return nil return nil
} }
@ -89,12 +91,14 @@ func (pk *PublicKey) UnmarshalText(b []byte) error {
str := string(b) str := string(b)
strEnc, ok := stripPrefix(str, pubKeyV0) strEnc, ok := stripPrefix(str, pubKeyV0)
if !ok || len(strEnc) <= hex.EncodedLen(8) { if !ok || len(strEnc) <= hex.EncodedLen(8) {
return merr.Wrap(context.Background(), errMalformedPublicKey, "pubKeyStr", str) ctx := mctx.Annotate(context.Background(), "pubKeyStr", str)
return merr.Wrap(errMalformedPublicKey, ctx)
} }
b, err := hex.DecodeString(strEnc) b, err := hex.DecodeString(strEnc)
if err != nil { if err != nil {
return merr.Wrap(context.Background(), err, "pubKeyStr", str) ctx := mctx.Annotate(context.Background(), "pubKeyStr", str)
return merr.Wrap(err, ctx)
} }
pk.E = int(binary.BigEndian.Uint64(b)) pk.E = int(binary.BigEndian.Uint64(b))
@ -185,17 +189,17 @@ func (pk *PrivateKey) UnmarshalText(b []byte) error {
str := string(b) str := string(b)
strEnc, ok := stripPrefix(str, privKeyV0) strEnc, ok := stripPrefix(str, privKeyV0)
if !ok { if !ok {
return merr.Wrap(context.Background(), errMalformedPrivateKey) return merr.Wrap(errMalformedPrivateKey)
} }
b, err := hex.DecodeString(strEnc) b, err := hex.DecodeString(strEnc)
if err != nil { if err != nil {
return merr.Wrap(context.Background(), errMalformedPrivateKey) return merr.Wrap(errMalformedPrivateKey)
} }
e, n := binary.Uvarint(b) e, n := binary.Uvarint(b)
if n <= 0 { if n <= 0 {
return merr.Wrap(context.Background(), errMalformedPrivateKey) return merr.Wrap(errMalformedPrivateKey)
} }
pk.PublicKey.E = int(e) pk.PublicKey.E = int(e)
b = b[n:] b = b[n:]
@ -206,7 +210,7 @@ func (pk *PrivateKey) UnmarshalText(b []byte) error {
} }
l, n := binary.Uvarint(b) l, n := binary.Uvarint(b)
if n <= 0 { if n <= 0 {
err = merr.Wrap(context.Background(), errMalformedPrivateKey) err = merr.Wrap(errMalformedPrivateKey)
} }
b = b[n:] b = b[n:]
i := new(big.Int) i := new(big.Int)

View File

@ -1,7 +1,6 @@
package mcrypto package mcrypto
import ( import (
"context"
"crypto/hmac" "crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
@ -77,9 +76,9 @@ func (s Secret) sign(r io.Reader) (Signature, error) {
func (s Secret) verify(sig Signature, r io.Reader) error { func (s Secret) verify(sig Signature, r io.Reader) error {
sigB, err := s.signRaw(r, uint8(len(sig.sig)), sig.salt, sig.t) sigB, err := s.signRaw(r, uint8(len(sig.sig)), sig.salt, sig.t)
if err != nil { if err != nil {
return merr.Wrap(context.Background(), err, "sig", sig) return merr.Wrap(err)
} else if !hmac.Equal(sigB, sig.sig) { } else if !hmac.Equal(sigB, sig.sig) {
return merr.Wrap(context.Background(), ErrInvalidSig, "sig", sig) return merr.Wrap(ErrInvalidSig)
} }
return nil return nil
} }

View File

@ -2,7 +2,6 @@ package mcrypto
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -67,12 +66,12 @@ func (s *Signature) UnmarshalText(b []byte) error {
str := string(b) str := string(b)
strEnc, ok := stripPrefix(str, sigV0) strEnc, ok := stripPrefix(str, sigV0)
if !ok || len(strEnc) < hex.EncodedLen(10) { if !ok || len(strEnc) < hex.EncodedLen(10) {
return merr.Wrap(context.Background(), errMalformedSig, "sigStr", str) return merr.Wrap(errMalformedSig)
} }
b, err := hex.DecodeString(strEnc) b, err := hex.DecodeString(strEnc)
if err != nil { if err != nil {
return merr.Wrap(context.Background(), err, "sigStr", str) return merr.Wrap(err)
} }
unixNano, b := int64(binary.BigEndian.Uint64(b[:8])), b[8:] unixNano, b := int64(binary.BigEndian.Uint64(b[:8])), b[8:]
@ -82,7 +81,7 @@ func (s *Signature) UnmarshalText(b []byte) error {
if err != nil { if err != nil {
return nil return nil
} else if len(b) < 1+int(b[0]) { } else if len(b) < 1+int(b[0]) {
err = merr.Wrap(context.Background(), errMalformedSig, "sigStr", str) err = merr.Wrap(errMalformedSig)
return nil return nil
} }
out := b[1 : 1+b[0]] out := b[1 : 1+b[0]]

View File

@ -10,6 +10,7 @@ import (
"errors" "errors"
"time" "time"
"github.com/mediocregopher/mediocre-go-lib/mctx"
"github.com/mediocregopher/mediocre-go-lib/merr" "github.com/mediocregopher/mediocre-go-lib/merr"
) )
@ -69,11 +70,13 @@ func (u *UUID) UnmarshalText(b []byte) error {
str := string(b) str := string(b)
strEnc, ok := stripPrefix(str, uuidV0) strEnc, ok := stripPrefix(str, uuidV0)
if !ok || len(strEnc) != hex.EncodedLen(16) { if !ok || len(strEnc) != hex.EncodedLen(16) {
return merr.Wrap(context.Background(), errMalformedUUID, "uuidStr", str) ctx := mctx.Annotate(context.Background(), "uuidStr", str)
return merr.Wrap(errMalformedUUID, ctx)
} }
b, err := hex.DecodeString(strEnc) b, err := hex.DecodeString(strEnc)
if err != nil { if err != nil {
return merr.Wrap(context.Background(), err, "uuidStr", str) ctx := mctx.Annotate(context.Background(), "uuidStr", str)
return merr.Wrap(errMalformedUUID, ctx)
} }
u.b = b u.b = b
return nil return nil

View File

@ -278,9 +278,14 @@ func mergeAnnotations(ctxA, ctxB context.Context) context.Context {
// //
// NOTE this will panic if no Contexts are passed in. // NOTE this will panic if no Contexts are passed in.
func MergeAnnotations(ctxs ...context.Context) context.Context { func MergeAnnotations(ctxs ...context.Context) context.Context {
ctxA := ctxs[0] return MergeAnnotationsInto(ctxs[0], ctxs[1:]...)
for _, ctxB := range ctxs[1:] { }
ctxA = mergeAnnotations(ctxA, ctxB)
} // MergeAnnotationsInto is a convenience function which works like
return ctxA // MergeAnnotations.
func MergeAnnotationsInto(ctx context.Context, ctxs ...context.Context) context.Context {
for _, ctxB := range ctxs {
ctx = mergeAnnotations(ctx, ctxB)
}
return ctx
} }

View File

@ -66,7 +66,7 @@ func WithBigQuery(parent context.Context, gce *mdb.GCE) (context.Context, *BigQu
mlog.Info("connecting to bigquery", bq.ctx) mlog.Info("connecting to bigquery", bq.ctx)
var err error var err error
bq.Client, err = bigquery.NewClient(innerCtx, bq.gce.Project, bq.gce.ClientOptions()...) bq.Client, err = bigquery.NewClient(innerCtx, bq.gce.Project, bq.gce.ClientOptions()...)
return merr.Wrap(bq.ctx, err) return merr.Wrap(err, bq.ctx)
}) })
ctx = mrun.WithStopHook(ctx, func(context.Context) error { ctx = mrun.WithStopHook(ctx, func(context.Context) error {
return bq.Client.Close() return bq.Client.Close()
@ -100,12 +100,12 @@ func (bq *BigQuery) Table(
mlog.Debug("creating/grabbing table", bq.ctx) mlog.Debug("creating/grabbing table", bq.ctx)
schema, err := bigquery.InferSchema(schemaObj) schema, err := bigquery.InferSchema(schemaObj)
if err != nil { if err != nil {
return nil, nil, merr.Wrap(ctx, err) return nil, nil, merr.Wrap(err, ctx)
} }
ds := bq.Dataset(dataset) ds := bq.Dataset(dataset)
if err := ds.Create(ctx, nil); err != nil && !isErrAlreadyExists(err) { if err := ds.Create(ctx, nil); err != nil && !isErrAlreadyExists(err) {
return nil, nil, merr.Wrap(ctx, err) return nil, nil, merr.Wrap(err, ctx)
} }
table := ds.Table(tableName) table := ds.Table(tableName)
@ -114,7 +114,7 @@ func (bq *BigQuery) Table(
Schema: schema, Schema: schema,
} }
if err := table.Create(ctx, meta); err != nil && !isErrAlreadyExists(err) { if err := table.Create(ctx, meta); err != nil && !isErrAlreadyExists(err) {
return nil, nil, merr.Wrap(ctx, err) return nil, nil, merr.Wrap(err, ctx)
} }
uploader := table.Uploader() uploader := table.Uploader()

View File

@ -74,7 +74,7 @@ func WithBigTable(parent context.Context, gce *mdb.GCE, defaultInstance string)
bt.gce.Project, bt.Instance, bt.gce.Project, bt.Instance,
bt.gce.ClientOptions()..., bt.gce.ClientOptions()...,
) )
return merr.Wrap(bt.ctx, err) return merr.Wrap(err, bt.ctx)
}) })
ctx = mrun.WithStopHook(ctx, func(context.Context) error { ctx = mrun.WithStopHook(ctx, func(context.Context) error {
return bt.Client.Close() return bt.Client.Close()
@ -95,14 +95,14 @@ func (bt *Bigtable) EnsureTable(ctx context.Context, name string, colFams ...str
mlog.Debug("creating admin client", ctx) mlog.Debug("creating admin client", ctx)
adminClient, err := bigtable.NewAdminClient(ctx, bt.gce.Project, bt.Instance) adminClient, err := bigtable.NewAdminClient(ctx, bt.gce.Project, bt.Instance)
if err != nil { if err != nil {
return merr.Wrap(ctx, err) return merr.Wrap(err, ctx)
} }
defer adminClient.Close() defer adminClient.Close()
mlog.Debug("creating bigtable table (if needed)", ctx) mlog.Debug("creating bigtable table (if needed)", ctx)
err = adminClient.CreateTable(ctx, name) err = adminClient.CreateTable(ctx, name)
if err != nil && !isErrAlreadyExists(err) { if err != nil && !isErrAlreadyExists(err) {
return merr.Wrap(ctx, err) return merr.Wrap(err, ctx)
} }
for _, colFam := range colFams { for _, colFam := range colFams {
@ -110,7 +110,7 @@ func (bt *Bigtable) EnsureTable(ctx context.Context, name string, colFams ...str
mlog.Debug("creating bigtable column family (if needed)", ctx) mlog.Debug("creating bigtable column family (if needed)", ctx)
err := adminClient.CreateColumnFamily(ctx, name, colFam) err := adminClient.CreateColumnFamily(ctx, name, colFam)
if err != nil && !isErrAlreadyExists(err) { if err != nil && !isErrAlreadyExists(err) {
return merr.Wrap(ctx, err) return merr.Wrap(err, ctx)
} }
} }

View File

@ -44,7 +44,7 @@ func WithDatastore(parent context.Context, gce *mdb.GCE) (context.Context, *Data
mlog.Info("connecting to datastore", ds.ctx) mlog.Info("connecting to datastore", ds.ctx)
var err error var err error
ds.Client, err = datastore.NewClient(innerCtx, ds.gce.Project, ds.gce.ClientOptions()...) ds.Client, err = datastore.NewClient(innerCtx, ds.gce.Project, ds.gce.ClientOptions()...)
return merr.Wrap(ds.ctx, err) return merr.Wrap(err, ds.ctx)
}) })
ctx = mrun.WithStopHook(ctx, func(context.Context) error { ctx = mrun.WithStopHook(ctx, func(context.Context) error {
return ds.Client.Close() return ds.Client.Close()

View File

@ -62,7 +62,7 @@ func WithPubSub(parent context.Context, gce *mdb.GCE) (context.Context, *PubSub)
mlog.Info("connecting to pubsub", ps.ctx) mlog.Info("connecting to pubsub", ps.ctx)
var err error var err error
ps.Client, err = pubsub.NewClient(innerCtx, ps.gce.Project, ps.gce.ClientOptions()...) ps.Client, err = pubsub.NewClient(innerCtx, ps.gce.Project, ps.gce.ClientOptions()...)
return merr.Wrap(ps.ctx, err) return merr.Wrap(err, ps.ctx)
}) })
ctx = mrun.WithStopHook(ctx, func(context.Context) error { ctx = mrun.WithStopHook(ctx, func(context.Context) error {
return ps.Client.Close() return ps.Client.Close()
@ -93,14 +93,14 @@ func (ps *PubSub) Topic(ctx context.Context, name string, create bool) (*Topic,
if isErrAlreadyExists(err) { if isErrAlreadyExists(err) {
t.topic = ps.Client.Topic(name) t.topic = ps.Client.Topic(name)
} else if err != nil { } else if err != nil {
return nil, merr.Wrap(ctx, merr.Wrap(t.ctx, err)) return nil, merr.Wrap(err, t.ctx, ctx)
} }
} else { } else {
t.topic = ps.Client.Topic(name) t.topic = ps.Client.Topic(name)
if exists, err := t.topic.Exists(t.ctx); err != nil { if exists, err := t.topic.Exists(t.ctx); err != nil {
return nil, merr.Wrap(ctx, merr.Wrap(t.ctx, err)) return nil, merr.Wrap(err, t.ctx, ctx)
} else if !exists { } else if !exists {
return nil, merr.Wrap(ctx, merr.New(t.ctx, "topic dne")) return nil, merr.New("topic dne", t.ctx, ctx)
} }
} }
return t, nil return t, nil
@ -110,7 +110,7 @@ func (ps *PubSub) Topic(ctx context.Context, name string, create bool) (*Topic,
func (t *Topic) Publish(ctx context.Context, data []byte) error { func (t *Topic) Publish(ctx context.Context, data []byte) error {
_, err := t.topic.Publish(ctx, &Message{Data: data}).Get(ctx) _, err := t.topic.Publish(ctx, &Message{Data: data}).Get(ctx)
if err != nil { if err != nil {
return merr.Wrap(ctx, merr.Wrap(t.ctx, err)) return merr.Wrap(err, t.ctx, ctx)
} }
return nil return nil
} }
@ -144,14 +144,14 @@ func (t *Topic) Subscription(ctx context.Context, name string, create bool) (*Su
if isErrAlreadyExists(err) { if isErrAlreadyExists(err) {
s.sub = t.ps.Subscription(name) s.sub = t.ps.Subscription(name)
} else if err != nil { } else if err != nil {
return nil, merr.Wrap(ctx, merr.Wrap(s.ctx, err)) return nil, merr.Wrap(err, s.ctx, ctx)
} }
} else { } else {
s.sub = t.ps.Subscription(name) s.sub = t.ps.Subscription(name)
if exists, err := s.sub.Exists(ctx); err != nil { if exists, err := s.sub.Exists(ctx); err != nil {
return nil, merr.Wrap(ctx, merr.Wrap(s.ctx, err)) return nil, merr.Wrap(err, s.ctx, ctx)
} else if !exists { } else if !exists {
return nil, merr.Wrap(ctx, merr.New(s.ctx, "sub dne")) return nil, merr.New("sub dne", s.ctx, ctx)
} }
} }
return s, nil return s, nil
@ -350,7 +350,7 @@ func (s *Subscription) BatchConsume(
case ret := <-retCh: case ret := <-retCh:
return ret, nil return ret, nil
case <-ctx.Done(): case <-ctx.Done():
return false, merr.Wrap(ctx, merr.New(s.ctx, "reading from batch grouping process timed out")) return false, merr.New("reading from batch grouping process timed out", s.ctx, ctx)
} }
}, opts) }, opts)

View File

@ -46,7 +46,7 @@ func WithMySQL(parent context.Context, defaultDB string) (context.Context, *SQL)
mlog.Info("connecting to mysql server", sql.ctx) mlog.Info("connecting to mysql server", sql.ctx)
var err error var err error
sql.DB, err = sqlx.ConnectContext(innerCtx, "mysql", dsn) sql.DB, err = sqlx.ConnectContext(innerCtx, "mysql", dsn)
return merr.Wrap(sql.ctx, err) return merr.Wrap(err, sql.ctx)
}) })
ctx = mrun.WithStopHook(ctx, func(innerCtx context.Context) error { ctx = mrun.WithStopHook(ctx, func(innerCtx context.Context) error {
mlog.Info("closing connection to sql server", sql.ctx) mlog.Info("closing connection to sql server", sql.ctx)

View File

@ -57,7 +57,7 @@ func wrap(e error, cp bool) *err {
er2 := &err{ er2 := &err{
err: er.err, err: er.err,
attr: make(map[interface{}]interface{}, len(er.attr)), attr: make(map[interface{}]interface{}, len(er.attr)+1),
} }
for k, v := range er.attr { for k, v := range er.attr {
er2.attr[k] = v er2.attr[k] = v
@ -102,47 +102,47 @@ func Value(e error, k interface{}) interface{} {
// WrapSkip is like Wrap but also allows for skipping extra stack frames when // WrapSkip is like Wrap but also allows for skipping extra stack frames when
// embedding the stack into the error. // embedding the stack into the error.
func WrapSkip(ctx context.Context, e error, skip int, kvs ...interface{}) error { func WrapSkip(e error, skip int, ctxs ...context.Context) error {
if e == nil {
return nil
}
er := wrap(e, true) er := wrap(e, true)
if _, ok := getStack(er); !ok { if _, ok := getStack(er); !ok {
setStack(er, skip+1) setStack(er, skip+1)
} }
if prevCtx, _ := er.attr[attrKeyCtx].(context.Context); prevCtx != nil { ctx, _ := er.attr[attrKeyCtx].(context.Context)
ctx = mctx.MergeAnnotations(prevCtx, ctx) if ctx != nil {
} ctx = mctx.MergeAnnotationsInto(ctx, ctxs...)
} else if len(ctxs) > 0 {
if len(kvs) > 0 { ctx = mctx.MergeAnnotations(ctxs...)
ctx = mctx.Annotate(ctx, kvs...)
} }
er.attr[attrKeyCtx] = ctx er.attr[attrKeyCtx] = ctx
return er return er
} }
// Wrap takes in an error and returns one which wraps it in merr's inner type, // Wrap returns a copy of the given error wrapped in merr's inner type. It will
// embedding the given Context (which can later be retrieved by Ctx) at the same // perform an mctx.MergeAnnotations on the given Contexts to create a new
// time. // Context, and embed that in the returned error. If the given error already has
// // an embedded Context then ctxs will be merged into that.
// For convenience, extra annotation information can be passed in here as well
// via the kvs argument. See mctx.Annotate for more information.
// //
// This function automatically embeds stack information into the error as it's // This function automatically embeds stack information into the error as it's
// being stored, using WithStack, unless the error already has stack information // being stored, using WithStack, unless the error already has stack information
// in it. // in it.
func Wrap(ctx context.Context, e error, kvs ...interface{}) error { //
return WrapSkip(ctx, e, 1, kvs...) // Wrapping nil returns nil.
func Wrap(e error, ctx ...context.Context) error {
return WrapSkip(e, 1, ctx...)
} }
// New is a shortcut for: // New is a shortcut for:
// merr.Wrap(ctx, errors.New(str), kvs...) // merr.Wrap(errors.New(str), ctxs...)
func New(ctx context.Context, str string, kvs ...interface{}) error { func New(str string, ctxs ...context.Context) error {
return WrapSkip(ctx, errors.New(str), 1, kvs...) return WrapSkip(errors.New(str), 1, ctxs...)
} }
// TODO it would be more convenient in a lot of cases if New and Wrap took in a
// list of Contexts.
type annotateKey string type annotateKey string
func ctx(e error) context.Context { func ctx(e error) context.Context {

View File

@ -10,24 +10,41 @@ import (
) )
func TestError(t *T) { func TestError(t *T) {
e := New(context.Background(), "foo", massert.Fatal(t, massert.Nil(Wrap(nil)))
ctx := mctx.Annotate(context.Background(),
"a", "aaa aaa\n", "a", "aaa aaa\n",
"c", "ccc\nccc\n", "c", "ccc\nccc\n",
"d\t", "weird key but ok", "d\t", "weird key but ok")
)
exp := `foo {
e := New("foo", ctx)
exp := `foo
* a: aaa aaa * a: aaa aaa
* c: * c:
ccc ccc
ccc ccc
* d: weird key but ok * d: weird key but ok
* errLoc: merr/merr_test.go:13` * errLoc: merr/merr_test.go:19`
massert.Fatal(t, massert.Equal(exp, e.Error())) massert.Fatal(t, massert.Equal(exp, e.Error()))
}
{
e := Wrap(errors.New("foo"), ctx)
exp := `foo
* a: aaa aaa
* c:
ccc
ccc
* d: weird key but ok
* errLoc: merr/merr_test.go:31`
massert.Fatal(t, massert.Equal(exp, e.Error()))
}
} }
func TestBase(t *T) { func TestBase(t *T) {
errFoo, errBar := errors.New("foo"), errors.New("bar") errFoo, errBar := errors.New("foo"), errors.New("bar")
erFoo := Wrap(context.Background(), errFoo) erFoo := Wrap(errFoo)
massert.Fatal(t, massert.All( massert.Fatal(t, massert.All(
massert.Nil(Base(nil)), massert.Nil(Base(nil)),
massert.Equal(errFoo, Base(erFoo)), massert.Equal(errFoo, Base(erFoo)),
@ -45,7 +62,7 @@ func TestValue(t *T) {
massert.Nil(Value(nil, "foo")), massert.Nil(Value(nil, "foo")),
)) ))
e1 := New(context.Background(), "foo") e1 := New("foo")
e1 = WithValue(e1, "a", "A") e1 = WithValue(e1, "a", "A")
e2 := WithValue(errors.New("bar"), "a", "A") e2 := WithValue(errors.New("bar"), "a", "A")
massert.Fatal(t, massert.All( massert.Fatal(t, massert.All(
@ -62,7 +79,7 @@ func TestValue(t *T) {
} }
func mkErr(ctx context.Context, err error) error { func mkErr(ctx context.Context, err error) error {
return Wrap(ctx, err) // it's important that this is line 65 return Wrap(err, ctx)
} }
func TestCtx(t *T) { func TestCtx(t *T) {
@ -72,14 +89,14 @@ func TestCtx(t *T) {
// use mkErr so that it's easy to test that the stack info isn't overwritten // use mkErr so that it's easy to test that the stack info isn't overwritten
// when Wrap is called with ctxB. // when Wrap is called with ctxB.
e := mkErr(ctxA, errors.New("hello")) e := mkErr(ctxA, errors.New("hello"))
e = Wrap(ctxB, e) e = Wrap(e, ctxB)
err := massert.Equal(map[string]string{ err := massert.Equal(map[string]string{
"0": "ZERO", "0": "ZERO",
"1": "ONE", "1": "ONE",
"2": "TWO", "2": "TWO",
"err": "hello", "err": "hello",
"errLoc": "merr/merr_test.go:65", "errLoc": "merr/merr_test.go:80",
}, mctx.Annotations(Context(e)).StringMap()).Assert() }, mctx.Annotations(Context(e)).StringMap()).Assert()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -1,7 +1,6 @@
package merr package merr
import ( import (
"context"
"strings" "strings"
. "testing" . "testing"
@ -9,7 +8,7 @@ import (
) )
func TestStack(t *T) { func TestStack(t *T) {
foo := New(context.Background(), "test") foo := New("test")
fooStack, ok := Stack(foo) fooStack, ok := Stack(foo)
massert.Fatal(t, massert.Equal(true, ok)) massert.Fatal(t, massert.Equal(true, ok))