From c042978c0fcec85ec1fdafb8a7a827fa3c7214dc Mon Sep 17 00:00:00 2001 From: Omar Kohl Date: Fri, 4 Aug 2023 14:19:26 +0200 Subject: [PATCH] refactor: add errors package that wraps cockroachdb/errors (#112) cockroachdb/errors gives us stack traces and information for end users with WithHint(). The reason for the 'errors' wrapper package is so as not to spread a third party dependency all over the code and make it easier to replace should that ever become necessary. If the API of the internal 'errors' keeps growing and becoming closer and closer to cockroachdb/errors then at some point the internal package might be deleted. In schema.resolvers.go it was necessary to import the packages as 'errors2' because otherwise the automated code generation kept overriding it. --- cleoc/cleoc/config.go | 5 ++- cleoc/cleoc/forecast.go | 12 +++--- cleosrv/cleosrv/cleosrv.go | 37 ++++++++++--------- cleosrv/cleosrv/config.go | 5 ++- cleosrv/graph/resolver.go | 28 +++++++------- cleosrv/graph/schema.resolvers.go | 36 +++++++++--------- .../integrationtest/create_test_sql/main.go | 28 +++++++------- cleosrv/integrationtest/helper.go | 7 ++-- cleoutils/errors/errors.go | 33 +++++++++++++++++ go.mod | 4 ++ go.sum | 4 ++ 11 files changed, 122 insertions(+), 77 deletions(-) create mode 100644 cleoutils/errors/errors.go diff --git a/cleoc/cleoc/config.go b/cleoc/cleoc/config.go index edee522e..5fe183fa 100644 --- a/cleoc/cleoc/config.go +++ b/cleoc/cleoc/config.go @@ -1,11 +1,12 @@ package cleoc import ( - "fmt" "strings" "github.com/adrg/xdg" "github.com/spf13/viper" + + "github.com/cleodora-forecasting/cleodora/cleoutils/errors" ) const DefaultConfigFileName = "cleoc" @@ -37,7 +38,7 @@ func (c *Config) LoadWithViper(v *viper.Viper) error { // Note that ConfigFileNotFoundError is not returned when // explicitly specifying a config path (--config), which should // (and does) cause an error if it doesn't exist. - return fmt.Errorf("error reading config file: %w", err) + return errors.Newf("error reading config file: %w", err) } } } diff --git a/cleoc/cleoc/forecast.go b/cleoc/cleoc/forecast.go index 10544f73..e3caf9e5 100644 --- a/cleoc/cleoc/forecast.go +++ b/cleoc/cleoc/forecast.go @@ -2,7 +2,6 @@ package cleoc import ( "context" - "errors" "fmt" "net/http" "time" @@ -11,6 +10,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/cleodora-forecasting/cleodora/cleoc/gqclient" + "github.com/cleodora-forecasting/cleodora/cleoutils/errors" ) // AddForecast creates a new forecast. @@ -18,13 +18,13 @@ import ( func (a *App) AddForecast(opts AddForecastOptions) error { resolvesT, err := time.Parse(time.RFC3339, opts.Resolves) if err != nil { - return fmt.Errorf("could not parse 'resolves': %w", err) + return errors.Newf("could not parse 'resolves': %w", err) } var closesT *time.Time if opts.Closes != "" { parsedTime, err := time.Parse(time.RFC3339, opts.Closes) if err != nil { - return fmt.Errorf("could not parse 'closes': %w", err) + return errors.Newf("could not parse 'closes': %w", err) } closesT = &parsedTime } @@ -42,7 +42,7 @@ func (a *App) AddForecast(opts AddForecastOptions) error { reqProbabilities, err := parseProbabilities(opts.Probabilities) if err != nil { - return fmt.Errorf("error parsing probabilities: %w", err) + return errors.Newf("error parsing probabilities: %w", err) } estimate := gqclient.NewEstimate{ @@ -51,7 +51,7 @@ func (a *App) AddForecast(opts AddForecastOptions) error { } resp, err := gqclient.CreateForecast(ctx, client, forecast, estimate) if err != nil { - return fmt.Errorf("error calling the API: %w", err) + return errors.Newf("error calling the API: %w", err) } _, err = fmt.Fprint(a.Out, resp.CreateForecast.Id+"\n") if err != nil { @@ -141,7 +141,7 @@ func (opts *AddForecastOptions) Validate() error { if sumProbabilities != 100 { validationErr = multierror.Append( validationErr, - fmt.Errorf( + errors.Newf( "all probabilities must add up to 100 (here only %v)", sumProbabilities, ), diff --git a/cleosrv/cleosrv/cleosrv.go b/cleosrv/cleosrv/cleosrv.go index 0a125305..b2662fbc 100644 --- a/cleosrv/cleosrv/cleosrv.go +++ b/cleosrv/cleosrv/cleosrv.go @@ -18,6 +18,7 @@ import ( "github.com/cleodora-forecasting/cleodora/cleosrv/graph" "github.com/cleodora-forecasting/cleodora/cleosrv/graph/generated" "github.com/cleodora-forecasting/cleodora/cleoutils" + "github.com/cleodora-forecasting/cleodora/cleoutils/errors" ) type App struct { @@ -56,7 +57,7 @@ Listening on: %s err := os.MkdirAll(filepath.Dir(a.Config.Database), 0770) if err != nil { - return fmt.Errorf("error making directories for database %v: %w", a.Config.Database, err) + return errors.Newf("error making directories for database %v: %w", a.Config.Database, err) } router := chi.NewRouter() @@ -103,7 +104,7 @@ func (a *App) InitDB() (*gorm.DB, error) { err = migrateDB(db) if err != nil { - return nil, fmt.Errorf("migrating data: %w", err) + return nil, errors.Newf("migrating data: %w", err) } return db, nil @@ -121,7 +122,7 @@ func migrateDB(db *gorm.DB) error { return tx.Migrator().AutoMigrate(&Migrations{}) }) if err != nil { - return fmt.Errorf("auto migrating 'migrations' table: %w", err) + return errors.Newf("auto migrating 'migrations' table: %w", err) } // If the DB is completely new then we just insert all migrations as 'done' // i.e. they are not really executed because the createDb() function is @@ -130,7 +131,7 @@ func migrateDB(db *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error { err := createDb(tx) if err != nil { - return fmt.Errorf("creating tables: %w", err) + return errors.Newf("creating tables: %w", err) } // save all migrations as done in the DB, without executing the // functions. @@ -146,7 +147,7 @@ func migrateDB(db *gorm.DB) error { } ret := tx.Create(mEntries) if ret.Error != nil { - return fmt.Errorf("storing migrations as done in DB: %w", ret.Error) + return errors.Newf("storing migrations as done in DB: %w", ret.Error) } return nil }) @@ -157,7 +158,7 @@ func migrateDB(db *gorm.DB) error { var count int64 ret := db.Model(&Migrations{}).Where("id = ?", m.ID).Count(&count) if ret.Error != nil { - return fmt.Errorf("selecting migration %v: %w", m.ID, ret.Error) + return errors.Newf("selecting migration %v: %w", m.ID, ret.Error) } if count == 1 { continue // migration already ran in the past @@ -167,7 +168,7 @@ func migrateDB(db *gorm.DB) error { if m.Up != nil { err = m.Up(tx) if err != nil { - return fmt.Errorf("running %v: %w", m.ID, err) + return errors.Newf("running %v: %w", m.ID, err) } } ret = tx.Create(Migrations{ @@ -175,7 +176,7 @@ func migrateDB(db *gorm.DB) error { Applied: time.Now().UTC(), }) if ret.Error != nil { - return fmt.Errorf("saving migration %v: %w", m.ID, ret.Error) + return errors.Newf("saving migration %v: %w", m.ID, ret.Error) } fmt.Printf("Finished DB migration '%v'\n", m.ID) return nil @@ -225,7 +226,7 @@ var dbMigrations = []dbMigration{ var forecasts []Forecast ret := db.Find(&forecasts) if ret.Error != nil { - return fmt.Errorf("getting forecasts: %w", ret.Error) + return errors.Newf("getting forecasts: %w", ret.Error) } for _, f := range forecasts { f.Created = f.Created.UTC() @@ -252,7 +253,7 @@ var dbMigrations = []dbMigration{ } ret = db.Save(f) if ret.Error != nil { - return fmt.Errorf("saving %v: %w", f.ID, ret.Error) + return errors.Newf("saving %v: %w", f.ID, ret.Error) } } return nil @@ -268,14 +269,14 @@ var dbMigrations = []dbMigration{ var estimates []Estimate ret := db.Find(&estimates) if ret.Error != nil { - return fmt.Errorf("getting estimates: %w", ret.Error) + return errors.Newf("getting estimates: %w", ret.Error) } for _, e := range estimates { e.Created = e.Created.UTC() e.CreatedAt = e.CreatedAt.UTC() ret = db.Save(e) if ret.Error != nil { - return fmt.Errorf("saving %v: %w", e.ID, ret.Error) + return errors.Newf("saving %v: %w", e.ID, ret.Error) } } return nil @@ -290,13 +291,13 @@ var dbMigrations = []dbMigration{ var outcomes []Outcome ret := db.Find(&outcomes) if ret.Error != nil { - return fmt.Errorf("getting outcomes: %w", ret.Error) + return errors.Newf("getting outcomes: %w", ret.Error) } for _, o := range outcomes { o.CreatedAt = o.CreatedAt.UTC() ret = db.Save(o) if ret.Error != nil { - return fmt.Errorf("saving %v: %w", o.ID, ret.Error) + return errors.Newf("saving %v: %w", o.ID, ret.Error) } } return nil @@ -311,13 +312,13 @@ var dbMigrations = []dbMigration{ var probabilities []Probability ret := db.Find(&probabilities) if ret.Error != nil { - return fmt.Errorf("getting probabilities: %w", ret.Error) + return errors.Newf("getting probabilities: %w", ret.Error) } for _, p := range probabilities { p.CreatedAt = p.CreatedAt.UTC() ret = db.Save(p) if ret.Error != nil { - return fmt.Errorf("saving %v: %w", p.ID, ret.Error) + return errors.Newf("saving %v: %w", p.ID, ret.Error) } } return nil @@ -366,7 +367,7 @@ var dbMigrations = []dbMigration{ Scan(&estimateBriers) if ret.Error != nil { - return fmt.Errorf("error calculating brier score: %w", ret.Error) + return errors.Newf("error calculating brier score: %w", ret.Error) } for _, r := range estimateBriers { @@ -374,7 +375,7 @@ var dbMigrations = []dbMigration{ Where("id = ?", r.ID). Update("brier_score", r.Brier) if ret.Error != nil { - return fmt.Errorf("error updating brier score: %w", ret.Error) + return errors.Newf("error updating brier score: %w", ret.Error) } } diff --git a/cleosrv/cleosrv/config.go b/cleosrv/cleosrv/config.go index 3dc9bbbb..71ec7d40 100644 --- a/cleosrv/cleosrv/config.go +++ b/cleosrv/cleosrv/config.go @@ -1,11 +1,12 @@ package cleosrv import ( - "fmt" "strings" "github.com/adrg/xdg" "github.com/spf13/viper" + + "github.com/cleodora-forecasting/cleodora/cleoutils/errors" ) const DefaultConfigFileName = "cleosrv" @@ -41,7 +42,7 @@ func (c *Config) LoadWithViper(v *viper.Viper) error { // Note that ConfigFileNotFoundError is not returned when // explicitly specifying a config path (--config), which should // (and does) cause an error if it doesn't exist. - return fmt.Errorf("error reading config file: %w", err) + return errors.Newf("error reading config file: %w", err) } } } diff --git a/cleosrv/graph/resolver.go b/cleosrv/graph/resolver.go index 35cc98c3..a5ceb7ba 100644 --- a/cleosrv/graph/resolver.go +++ b/cleosrv/graph/resolver.go @@ -4,7 +4,6 @@ package graph import ( "context" - "errors" "fmt" "html" "strconv" @@ -15,6 +14,7 @@ import ( "github.com/cleodora-forecasting/cleodora/cleosrv/dbmodel" "github.com/cleodora-forecasting/cleodora/cleosrv/graph/model" + "github.com/cleodora-forecasting/cleodora/cleoutils/errors" ) // This file will not be regenerated automatically. @@ -55,12 +55,12 @@ func createEstimate( estimate model.NewEstimate, ) (*model.Estimate, error) { if err := validateNewEstimate(&estimate, false); err != nil { - return nil, fmt.Errorf("error validating NewEstimate: %w", err) + return nil, errors.Newf("error validating NewEstimate: %w", err) } forecast := dbmodel.Forecast{} ret := tx.Where("id = ?", forecastID).First(&forecast) if ret.Error != nil { - return nil, fmt.Errorf("error getting Forecast with ID %v: %w", forecastID, ret.Error) + return nil, errors.Newf("error getting Forecast with ID %v: %w", forecastID, ret.Error) } if estimate.Created.Before(forecast.Created) { @@ -92,7 +92,7 @@ func createEstimate( ).Where("estimates.forecast_id = ?", forecastID). Distinct().Pluck("outcomes.id", &validOutcomeIds) if ret.Error != nil { - return nil, fmt.Errorf("error getting outcome IDs: %w", ret.Error) + return nil, errors.Newf("error getting outcome IDs: %w", ret.Error) } var submittedOutcomeIds []string @@ -102,12 +102,12 @@ func createEstimate( invalidOutcomeIds := stringSetDiff(submittedOutcomeIds, validOutcomeIds) if len(invalidOutcomeIds) > 0 { - return nil, fmt.Errorf("invalid Outcome IDs: %v", invalidOutcomeIds) + return nil, errors.Newf("invalid Outcome IDs: %v", invalidOutcomeIds) } missingOutcomeIds := stringSetDiff(validOutcomeIds, submittedOutcomeIds) if len(missingOutcomeIds) > 0 { - return nil, fmt.Errorf("missing Outcome IDs: %v", missingOutcomeIds) + return nil, errors.Newf("missing Outcome IDs: %v", missingOutcomeIds) } var probabilities []dbmodel.Probability @@ -115,7 +115,7 @@ func createEstimate( for _, p := range estimate.Probabilities { outcomeID, err := strconv.ParseUint(*p.OutcomeID, 10, 64) if err != nil { - return nil, fmt.Errorf("can't parse %v as uint: %w", outcomeID, err) + return nil, errors.Newf("can't parse %v as uint: %w", outcomeID, err) } probabilities = append( probabilities, @@ -134,7 +134,7 @@ func createEstimate( err := tx.Model(&forecast).Association("Estimates").Append(&dbEstimate) if err != nil { - return nil, fmt.Errorf("error creating Estimate: %w", err) + return nil, errors.Newf("error creating Estimate: %w", err) } return convertEstimateDBToGQL(dbEstimate), nil @@ -192,7 +192,7 @@ func validateNewForecast(forecast *model.NewForecast) error { if forecast.Closes.After(forecast.Resolves) { validationErr = multierror.Append( validationErr, - fmt.Errorf( + errors.Newf( "'Closes' can't be set to a later date than 'Resolves'. "+ "Closes is '%v'. Resolves is '%v'", *forecast.Closes, @@ -217,7 +217,7 @@ func validateNewForecast(forecast *model.NewForecast) error { if forecast.Resolves.Before(*forecast.Created) { validationErr = multierror.Append( validationErr, - fmt.Errorf( + errors.Newf( "'Resolves' can't be set to an earlier date than 'Created'. "+ "Resolves is '%v'. Created is '%v'", forecast.Resolves, @@ -275,7 +275,7 @@ func validateNewEstimate(estimate *model.NewEstimate, duringCreateForecast bool) if _, ok := existingOutcomes[p.Outcome.Text]; ok { validationErr = multierror.Append( validationErr, - fmt.Errorf("outcome '%v' is a duplicate", p.Outcome.Text), + errors.Newf("outcome '%v' is a duplicate", p.Outcome.Text), ) } existingOutcomes[p.Outcome.Text] = true @@ -302,7 +302,7 @@ func validateNewEstimate(estimate *model.NewEstimate, duringCreateForecast bool) if p.Value < 0 || p.Value > 100 { validationErr = multierror.Append( validationErr, - fmt.Errorf("probabilities must be between 0 and 100, not %v", p.Value), + errors.Newf("probabilities must be between 0 and 100, not %v", p.Value), ) } sumProbabilities += p.Value @@ -310,7 +310,7 @@ func validateNewEstimate(estimate *model.NewEstimate, duringCreateForecast bool) if sumProbabilities != 100 { validationErr = multierror.Append( validationErr, - fmt.Errorf("probabilities must add up to 100, not %v", sumProbabilities), + errors.Newf("probabilities must add up to 100, not %v", sumProbabilities), ) } @@ -320,7 +320,7 @@ func validateNewEstimate(estimate *model.NewEstimate, duringCreateForecast bool) if estimate.Created.After(now) { validationErr = multierror.Append( validationErr, - fmt.Errorf( + errors.Newf( "'created' can't be in the future: %v", estimate.Created, ), diff --git a/cleosrv/graph/schema.resolvers.go b/cleosrv/graph/schema.resolvers.go index 791226c8..6bf3cdb6 100644 --- a/cleosrv/graph/schema.resolvers.go +++ b/cleosrv/graph/schema.resolvers.go @@ -6,7 +6,6 @@ package graph import ( "context" - "errors" "fmt" "html" "time" @@ -15,6 +14,7 @@ import ( "github.com/cleodora-forecasting/cleodora/cleosrv/graph/generated" "github.com/cleodora-forecasting/cleodora/cleosrv/graph/model" "github.com/cleodora-forecasting/cleodora/cleoutils" + errors2 "github.com/cleodora-forecasting/cleodora/cleoutils/errors" "gorm.io/gorm" ) @@ -22,14 +22,14 @@ import ( func (r *mutationResolver) CreateForecast(ctx context.Context, forecast model.NewForecast, estimate model.NewEstimate) (*model.Forecast, error) { err := validateNewForecast(&forecast) if err != nil { - return nil, fmt.Errorf("error validating NewForecast: %w", err) + return nil, errors2.Newf("error validating NewForecast: %w", err) } // We want the first estimate to have the same 'Created' time as the // forecast itself because it's logical that it would be that way. estimate.Created = forecast.Created err = validateNewEstimate(&estimate, true) if err != nil { - return nil, fmt.Errorf("error validating NewEstimate: %w", err) + return nil, errors2.Newf("error validating NewEstimate: %w", err) } dbForecast := dbmodel.Forecast{ Title: html.EscapeString(forecast.Title), @@ -46,7 +46,7 @@ func (r *mutationResolver) CreateForecast(ctx context.Context, forecast model.Ne return ret.Error }) if err != nil { - return nil, fmt.Errorf("creating forecast: %w", err) + return nil, errors2.Newf("creating forecast: %w", err) } retForecast := model.Forecast{ @@ -71,7 +71,7 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin } }() if err := tx.Error; err != nil { - return nil, fmt.Errorf("error creating transaction: %w", err) + return nil, errors2.Newf("error creating transaction: %w", err) } resolutionToSet := dbmodel.ResolutionResolved @@ -81,12 +81,12 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin if resolutionToSet == dbmodel.ResolutionUnresolved { _ = tx.Rollback() - return nil, fmt.Errorf("resolution %v is not allowed", resolutionToSet) + return nil, errors2.Newf("resolution %v is not allowed", resolutionToSet) } if resolutionToSet == dbmodel.ResolutionResolved && correctOutcomeID == nil { _ = tx.Rollback() - return nil, fmt.Errorf( + return nil, errors2.Newf( "to resolve as %v, an Outcome must be specified", resolutionToSet, ) @@ -96,12 +96,12 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin ret := tx.Where("id = ?", forecastID).First(&forecast) if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("error getting Forecast with ID %v: %w", forecastID, ret.Error) + return nil, errors2.Newf("error getting Forecast with ID %v: %w", forecastID, ret.Error) } if forecast.Resolution != dbmodel.ResolutionUnresolved { _ = tx.Rollback() - return nil, errors.New("forecast has already been resolved") + return nil, errors2.New("forecast has already been resolved") } forecast.Resolution = resolutionToSet @@ -115,7 +115,7 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin ret = tx.Save(&forecast) if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("error setting resolution: %w", ret.Error) + return nil, errors2.Newf("error setting resolution: %w", ret.Error) } if resolutionToSet == dbmodel.ResolutionResolved { @@ -123,7 +123,7 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin ret = tx.Where("id = ?", correctOutcomeID).First(&outcome) if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("error getting outcome: %w", ret.Error) + return nil, errors2.Newf("error getting outcome: %w", ret.Error) } matchingForecast := dbmodel.Forecast{} @@ -139,19 +139,19 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("can't match forecast and outcome: %w", ret.Error) + return nil, errors2.Newf("can't match forecast and outcome: %w", ret.Error) } if ret.RowsAffected != 1 { _ = tx.Rollback() - return nil, errors.New("can't match forecast and outcome") + return nil, errors2.New("can't match forecast and outcome") } outcome.Correct = true ret = tx.Save(&outcome) if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("error updating outcome: %w", ret.Error) + return nil, errors2.Newf("error updating outcome: %w", ret.Error) } type EstimateBrier struct { @@ -182,7 +182,7 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("error calculating brier score: %w", ret.Error) + return nil, errors2.Newf("error calculating brier score: %w", ret.Error) } for _, r := range estimateBriers { @@ -191,7 +191,7 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin Update("brier_score", r.Brier) if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("error updating brier score: %w", ret.Error) + return nil, errors2.Newf("error updating brier score: %w", ret.Error) } } } @@ -200,7 +200,7 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin ret = tx.Preload("Estimates.Probabilities.Outcome").Find(&forecast) if ret.Error != nil { _ = tx.Rollback() - return nil, fmt.Errorf("error preloading: %w", ret.Error) + return nil, errors2.Newf("error preloading: %w", ret.Error) } var estimates []*model.Estimate @@ -243,7 +243,7 @@ func (r *mutationResolver) ResolveForecast(ctx context.Context, forecastID strin } ret = tx.Commit() if ret.Error != nil { - return nil, fmt.Errorf("error committing: %w", ret.Error) + return nil, errors2.Newf("error committing: %w", ret.Error) } return &rf, nil } diff --git a/cleosrv/integrationtest/create_test_sql/main.go b/cleosrv/integrationtest/create_test_sql/main.go index 0b5c69b8..3945cb24 100644 --- a/cleosrv/integrationtest/create_test_sql/main.go +++ b/cleosrv/integrationtest/create_test_sql/main.go @@ -11,7 +11,6 @@ package main import ( "bytes" "context" - "errors" "fmt" "os" "os/exec" @@ -22,6 +21,7 @@ import ( "github.com/Khan/genqlient/graphql" "github.com/cleodora-forecasting/cleodora/cleosrv/integrationtest" + "github.com/cleodora-forecasting/cleodora/cleoutils/errors" ) func main() { @@ -86,7 +86,7 @@ Example: version, err := getVersion(c) if err != nil { - return fmt.Errorf("getting version: %w", err) + return errors.Newf("getting version: %w", err) } fmt.Println("cleosrv version:", version) @@ -96,7 +96,7 @@ Example: err = cleosrvCmd.Process.Kill() if err != nil { - return fmt.Errorf("stopping cleosrvCmd: %w", err) + return errors.Newf("stopping cleosrvCmd: %w", err) } err = integrationtest.CopyFile(dbPath, resultDb) @@ -137,10 +137,10 @@ func executeQueries(c graphql.Client, version string) error { } resp, err := integrationtest.CreateForecast(context.Background(), c, f1, e1) if err != nil { - return fmt.Errorf("create f1: %w", err) + return errors.Newf("create f1: %w", err) } if resp.CreateForecast.Id == "" { - return fmt.Errorf("unexpected response f1: %v", resp) + return errors.Newf("unexpected response f1: %v", resp) } // Create another forecast with 'created', 'resolves' and 'closes' in the @@ -149,7 +149,7 @@ func executeQueries(c graphql.Client, version string) error { newYork, err := time.LoadLocation("America/New_York") if err != nil { - return fmt.Errorf("can't get TZ loc: %w", err) + return errors.Newf("can't get TZ loc: %w", err) } f2 := integrationtest.NewForecast{ @@ -177,10 +177,10 @@ func executeQueries(c graphql.Client, version string) error { } resp, err = integrationtest.CreateForecast(context.Background(), c, f2, e2) if err != nil { - return fmt.Errorf("create f2: %w", err) + return errors.Newf("create f2: %w", err) } if resp.CreateForecast.Id == "" { - return fmt.Errorf("unexpected response f2: %v", resp) + return errors.Newf("unexpected response f2: %v", resp) } f3 := integrationtest.NewForecast{ @@ -211,10 +211,10 @@ func executeQueries(c graphql.Client, version string) error { } resp, err = integrationtest.CreateForecast(context.Background(), c, f3, e3) if err != nil { - return fmt.Errorf("create f3: %w", err) + return errors.Newf("create f3: %w", err) } if resp.CreateForecast.Id == "" { - return fmt.Errorf("unexpected response f3: %v", resp) + return errors.Newf("unexpected response f3: %v", resp) } // Resolve the forecast f3 @@ -226,7 +226,7 @@ func executeQueries(c graphql.Client, version string) error { } } if yesOutcomeId == "" { - return fmt.Errorf("could not find outcome 'Yes': %v", resp) + return errors.Newf("could not find outcome 'Yes': %v", resp) } // Backport this script to 0.2.0 then overwrite the DB @@ -239,14 +239,14 @@ func executeQueries(c graphql.Client, version string) error { &resolutionResolved, ) if err != nil { - return fmt.Errorf("resolve f3 err: %w", err) + return errors.Newf("resolve f3 err: %w", err) } if resolveResp.ResolveForecast.Resolution != integrationtest.ResolutionResolved { - return fmt.Errorf("resolution is not RESOLVED: %v", resolveResp) + return errors.Newf("resolution is not RESOLVED: %v", resolveResp) } if resolveResp.ResolveForecast.Id != f3Id { - return fmt.Errorf("the IDs don't match: %v", resolveResp) + return errors.Newf("the IDs don't match: %v", resolveResp) } return nil diff --git a/cleosrv/integrationtest/helper.go b/cleosrv/integrationtest/helper.go index af5a24ca..75a0fe96 100644 --- a/cleosrv/integrationtest/helper.go +++ b/cleosrv/integrationtest/helper.go @@ -17,6 +17,7 @@ import ( "github.com/cleodora-forecasting/cleodora/cleosrv/cleosrv" "github.com/cleodora-forecasting/cleodora/cleosrv/graph" "github.com/cleodora-forecasting/cleodora/cleosrv/graph/generated" + "github.com/cleodora-forecasting/cleodora/cleoutils/errors" ) // initServerAndGetClient returns a graphql.Client generated by genqlient @@ -83,20 +84,20 @@ func CopyFile(src string, dst string) error { // Open original file srcF, err := os.Open(src) if err != nil { - return fmt.Errorf("open src: %w", err) + return errors.Newf("open src: %w", err) } defer func() { _ = srcF.Close() }() // Create new file dstF, err := os.Create(dst) if err != nil { - return fmt.Errorf("open dst: %w", err) + return errors.Newf("open dst: %w", err) } defer func() { _ = dstF.Close() }() _, err = io.Copy(dstF, srcF) if err != nil { - return fmt.Errorf("copying file: %w", err) + return errors.Newf("copying file: %w", err) } return nil } diff --git a/cleoutils/errors/errors.go b/cleoutils/errors/errors.go new file mode 100644 index 00000000..67ac03ac --- /dev/null +++ b/cleoutils/errors/errors.go @@ -0,0 +1,33 @@ +// Package errors is a wrapper for github.com/cockroachdb/errors +// to make it easier to replace later, if necessary. +package errors + +import "github.com/cockroachdb/errors" + +func New(msg string) error { + return errors.New(msg) +} + +func Newf(format string, args ...interface{}) error { + return errors.Newf(format, args...) +} + +func Wrap(err error, msg string) error { + return errors.Wrap(err, msg) +} + +func Wrapf(err error, msg string, args ...interface{}) error { + return errors.Wrapf(err, msg, args...) +} + +func WithHint(err error, msg string) error { + return errors.WithHint(err, msg) +} + +func WithHintf(err error, format string, args ...interface{}) error { + return errors.WithHintf(err, format, args...) +} + +func FlattenHints(err error) string { + return errors.FlattenHints(err) +} diff --git a/go.mod b/go.mod index cd551ee7..ebe95acf 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Khan/genqlient v0.6.0 github.com/adrg/xdg v0.4.0 github.com/carolynvs/magex v0.9.0 + github.com/cockroachdb/errors v1.2.4 github.com/glebarez/sqlite v1.9.0 github.com/go-chi/chi/v5 v5.0.10 github.com/golangci/golangci-lint v1.53.3 @@ -118,12 +119,14 @@ require ( github.com/caarlos0/log v0.4.2 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/charmbracelet/lipgloss v0.7.1 // indirect github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 // indirect github.com/cloudflare/circl v1.3.3 // indirect + github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect @@ -155,6 +158,7 @@ require ( github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/getsentry/raven-go v0.2.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/go-critic/go-critic v0.8.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect diff --git a/go.sum b/go.sum index 9b72250b..8f3eccc7 100644 --- a/go.sum +++ b/go.sum @@ -1017,6 +1017,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -1071,7 +1072,9 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcKDqs= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= @@ -1366,6 +1369,7 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=