From c734118cd2a26fc4bb6bed83d51db17efbc5c284 Mon Sep 17 00:00:00 2001 From: Ayu Date: Mon, 4 Mar 2024 13:57:41 +0000 Subject: [PATCH] fix(core): wrap errors with no linter rules --- core/.golangci.yml | 20 +++++++++++++------- core/cmd/config.go | 7 ++++--- core/cmd/start.go | 30 +++++++++++++++++------------- core/db/duckdb/client.go | 7 ++++--- core/db/duckdb/event.go | 5 +++-- core/db/duckdb/helpers_test.go | 12 +++++++----- core/db/duckdb/types.go | 25 +++++++++++++------------ core/db/duckdb/utm.go | 25 +++++++++++++------------ core/db/duckdb/websites.go | 4 +++- core/db/sqlite/client.go | 3 ++- core/db/sqlite/helpers_test.go | 6 ++++-- core/db/sqlite/users.go | 20 ++++++++++---------- core/db/sqlite/websites.go | 18 +++++++++--------- core/services/oas.go | 5 +++-- core/services/users.go | 16 ++++++++-------- core/services/websites.go | 18 +++++++++--------- core/util/auth.go | 21 +++++++++++---------- core/util/auth_test.go | 30 ++++++++++++++++-------------- core/util/cache_test.go | 32 +++++++++++++++++--------------- 19 files changed, 166 insertions(+), 138 deletions(-) diff --git a/core/.golangci.yml b/core/.golangci.yml index 63e9e20f..8584dc8e 100644 --- a/core/.golangci.yml +++ b/core/.golangci.yml @@ -15,10 +15,6 @@ linters-settings: # The maximal code complexity to report. # Default: 10 max-complexity: 30 - # The maximal average package complexity. - # If it's higher than 0.0 (float) the check is enabled - # Default: 0.0 - package-average: 10.0 errcheck: # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. @@ -104,6 +100,9 @@ linters-settings: # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. # Default: false all: true + wrapcheck: + ignorePackageGlobs: + - services linters: disable-all: true @@ -121,6 +120,8 @@ linters: - asciicheck # checks that your code does not contain non-ASCII identifiers - bidichk # checks for dangerous unicode character sequences - bodyclose # checks whether HTTP response body is closed successfully + - containedctx # checks for context.Context contained in a struct + - contextcheck # checks for common mistakes using context - cyclop # checks function and package cyclomatic complexity # - dupl # tool for code clone detection - durationcheck # checks for two durations multiplied together @@ -131,6 +132,7 @@ linters: - exportloopref # checks for pointers to enclosing loop variables - forbidigo # forbids identifiers # - funlen # tool for detection of long functions # disabled because of routers and handlers in one function + - gci # controls golang package import order and makes it always deterministic - gocheckcompilerdirectives # validates go compiler directive comments (//go:) - gochecknoglobals # checks that no global variables exist - gochecknoinits # checks that no init functions are present in Go code @@ -138,6 +140,7 @@ linters: - goconst # finds repeated strings that could be replaced by a constant - gocritic # provides diagnostics that check for bugs, performance and style issues - gocyclo # computes and checks the cyclomatic complexity of functions + - gofumpt # checks if the code is formatted with gofumpt - godot # checks if comments end in a period - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt - gomnd # detects magic numbers @@ -157,24 +160,27 @@ linters: - nolintlint # reports ill-formed or insufficient nolint directives - nonamedreturns # reports all named returns - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - prealloc # finds slice declarations that could potentially be preallocated - predeclared # finds code that shadows one of Go's predeclared identifiers - promlinter # checks Prometheus metrics naming via promlint - reassign # checks that package variables are not reassigned - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint - rowserrcheck # checks whether Err of rows is checked successfully - - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - sloglint # ensure consistent logging style + # - sqlclosecheck # Disabled until https://github.com/ryanrolds/sqlclosecheck/issues/35 is resolved - stylecheck # is a replacement for golint - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks for common mistakes using the testify package - testpackage # makes you use a separate _test package + - thelper # checks that the helper functions are not used in the test - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes - unconvert # removes unnecessary type conversions - unparam # reports unused function parameters - usestdlibvars # detects the possibility to use variables/constants from the Go standard library - wastedassign # finds wasted assignment statements - whitespace # detects leading and trailing whitespace - - gci # controls golang package import order and makes it always deterministic - - godox # detects FIXME, TODO and other comment keywords + - wrapcheck # checks that the code does not use the wrapping of errors issues: # Maximum count of issues with the same text. diff --git a/core/cmd/config.go b/core/cmd/config.go index 6f3f9a79..44057910 100644 --- a/core/cmd/config.go +++ b/core/cmd/config.go @@ -4,6 +4,7 @@ import ( "time" "github.com/caarlos0/env/v10" + "github.com/go-faster/errors" ) type ServerConfig struct { @@ -59,7 +60,7 @@ func NewServerConfig() (*ServerConfig, error) { // Load config from environment variables. if err := env.Parse(config); err != nil { - return nil, err + return nil, errors.Wrap(err, "config") } return config, nil @@ -73,7 +74,7 @@ func NewAppDBConfig() (*AppDBConfig, error) { // Load config from environment variables. if err := env.Parse(config); err != nil { - return nil, err + return nil, errors.Wrap(err, "config") } return config, nil @@ -87,7 +88,7 @@ func NewAnalyticsDBConfig() (*AnalyticsDBConfig, error) { // Load config from environment variables. if err := env.Parse(config); err != nil { - return nil, err + return nil, errors.Wrap(err, "config") } return config, nil diff --git a/core/cmd/start.go b/core/cmd/start.go index 6f1a3a04..ec9a9e91 100644 --- a/core/cmd/start.go +++ b/core/cmd/start.go @@ -11,6 +11,7 @@ import ( "strconv" "syscall" + "github.com/go-faster/errors" generate "github.com/medama-io/medama" "github.com/medama-io/medama/api" "github.com/medama-io/medama/db/duckdb" @@ -33,17 +34,17 @@ type StartCommand struct { func NewStartCommand() (*StartCommand, error) { serverConfig, err := NewServerConfig() if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create server config") } appConfig, err := NewAppDBConfig() if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create app db config") } analyticsConfig, err := NewAnalyticsDBConfig() if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create analytics db config") } return &StartCommand{ @@ -60,7 +61,12 @@ func (s *StartCommand) ParseFlags(args []string) error { fs.Int64Var(&s.Server.Port, "port", DefaultPort, "Port to listen on") // Parse flags - return fs.Parse(args) + err := fs.Parse(args) + if err != nil { + return errors.Wrap(err, "failed to parse flags") + } + + return nil } // Run executes the start command. @@ -71,36 +77,34 @@ func (s *StartCommand) Run(ctx context.Context) error { // Setup database sqlite, err := sqlite.NewClient(s.AppDB.Host) if err != nil { - return err + return errors.Wrap(err, "failed to create sqlite client") } duckdb, err := duckdb.NewClient(s.AnalyticsDB.Host) if err != nil { - return err + return errors.Wrap(err, "failed to create duckdb client") } // Run migrations m := migrations.NewMigrationsService(ctx, sqlite, duckdb) if m == nil { - slog.Error("Could not create migrations service") - return err + return errors.New("could not create migrations service") } err = m.AutoMigrate(ctx) if err != nil { - slog.Error("Could not run migrations", "error", err) - return err + return errors.Wrap(err, "could not run migrations") } // Setup auth service auth, err := util.NewAuthService(ctx) if err != nil { - return err + return errors.Wrap(err, "failed to create auth service") } // Setup handlers service, err := services.NewService(auth, sqlite, duckdb) if err != nil { - return err + return errors.Wrap(err, "failed to create handlers") } authMiddleware := middlewares.NewAuthHandler(auth) @@ -116,7 +120,7 @@ func (s *StartCommand) Run(ctx context.Context) error { api.WithNotFound(middlewares.NotFound()), ) if err != nil { - return err + return errors.Wrap(err, "failed to create server") } // We need to add additional static routes for the web app. diff --git a/core/db/duckdb/client.go b/core/db/duckdb/client.go index 24a754b9..d1ab35ce 100644 --- a/core/db/duckdb/client.go +++ b/core/db/duckdb/client.go @@ -1,6 +1,7 @@ package duckdb import ( + "github.com/go-faster/errors" "github.com/jmoiron/sqlx" "github.com/medama-io/medama/db" ) @@ -18,21 +19,21 @@ var ( func NewClient(host string) (*Client, error) { db, err := sqlx.Connect("duckdb", host) if err != nil { - return nil, err + return nil, errors.Wrap(err, "duckdb") } // Enable ICU extension _, err = db.Exec(`--sql INSTALL icu;`) if err != nil { - return nil, err + return nil, errors.Wrap(err, "duckdb") } // Load ICU extension _, err = db.Exec(`--sql LOAD icu;`) if err != nil { - return nil, err + return nil, errors.Wrap(err, "duckdb") } return &Client{ diff --git a/core/db/duckdb/event.go b/core/db/duckdb/event.go index 716e014f..5ed05fa0 100644 --- a/core/db/duckdb/event.go +++ b/core/db/duckdb/event.go @@ -3,6 +3,7 @@ package duckdb import ( "context" + "github.com/go-faster/errors" "github.com/medama-io/medama/model" ) @@ -14,7 +15,7 @@ func (c *Client) AddPageView(ctx context.Context, event *model.PageViewHit) erro _, err := c.DB.ExecContext(ctx, exec, event.BID, event.Hostname, event.Pathname, event.IsUniqueUser, event.IsUniquePage, event.Referrer, event.CountryCode, event.Language, event.BrowserName, event.OS, event.DeviceType, event.UTMSource, event.UTMMedium, event.UTMCampaign) if err != nil { - return err + return errors.Wrap(err, "db") } return nil @@ -26,7 +27,7 @@ func (c *Client) UpdatePageView(ctx context.Context, event *model.PageViewDurati UPDATE views SET bid = NULL, duration_ms = ? WHERE bid = ?`, event.DurationMs, event.BID) if err != nil { - return err + return errors.Wrap(err, "db") } return nil diff --git a/core/db/duckdb/helpers_test.go b/core/db/duckdb/helpers_test.go index 8cd4f335..88627a1f 100644 --- a/core/db/duckdb/helpers_test.go +++ b/core/db/duckdb/helpers_test.go @@ -14,29 +14,31 @@ import ( "github.com/medama-io/medama/migrations" "github.com/medama-io/medama/model" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func SetupDatabase(t *testing.T) (*assert.Assertions, context.Context, *duckdb.Client) { t.Helper() assert := assert.New(t) + require := require.New(t) ctx := context.Background() // Disable logging log.SetOutput(io.Discard) // Generate new memory db per test client, err := sqlite.NewClient(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())) - assert.NoError(err) + require.NoError(err) assert.NotNil(client) // In memory duckdb client duckdbClient, err := duckdb.NewClient("") - assert.NoError(err) + require.NoError(err) assert.NotNil(duckdbClient) // Run migrations m := migrations.NewMigrationsService(ctx, client, duckdbClient) err = m.AutoMigrate(ctx) - assert.NoError(err) + require.NoError(err) // Create test user userCreate := model.NewUser( @@ -48,7 +50,7 @@ func SetupDatabase(t *testing.T) (*assert.Assertions, context.Context, *duckdb.C 2, // dateUpdated ) err = client.CreateUser(ctx, userCreate) - assert.NoError(err) + require.NoError(err) // Create test website websiteCreate := model.NewWebsite( @@ -59,7 +61,7 @@ func SetupDatabase(t *testing.T) (*assert.Assertions, context.Context, *duckdb.C 2, // dateUpdated ) err = client.CreateWebsite(ctx, websiteCreate) - assert.NoError(err) + require.NoError(err) return assert, ctx, duckdbClient } diff --git a/core/db/duckdb/types.go b/core/db/duckdb/types.go index 5f82609e..d643d8e8 100644 --- a/core/db/duckdb/types.go +++ b/core/db/duckdb/types.go @@ -4,6 +4,7 @@ import ( "context" "strings" + "github.com/go-faster/errors" "github.com/medama-io/medama/db" "github.com/medama-io/medama/model" ) @@ -40,7 +41,7 @@ func (c *Client) GetWebsiteBrowsersSummary(ctx context.Context, filter *db.Filte rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -48,7 +49,7 @@ func (c *Client) GetWebsiteBrowsersSummary(ctx context.Context, filter *db.Filte var browser model.StatsBrowsersSummary err := rows.StructScan(&browser) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } browsers = append(browsers, &browser) } @@ -93,7 +94,7 @@ func (c *Client) GetWebsiteBrowsers(ctx context.Context, filter *db.Filters) ([] rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -101,7 +102,7 @@ func (c *Client) GetWebsiteBrowsers(ctx context.Context, filter *db.Filters) ([] var browser model.StatsBrowsers err := rows.StructScan(&browser) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } browsers = append(browsers, &browser) } @@ -140,7 +141,7 @@ func (c *Client) GetWebsiteOSSummary(ctx context.Context, filter *db.Filters) ([ rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -148,7 +149,7 @@ func (c *Client) GetWebsiteOSSummary(ctx context.Context, filter *db.Filters) ([ var o model.StatsOSSummary err := rows.StructScan(&o) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } os = append(os, &o) } @@ -190,7 +191,7 @@ func (c *Client) GetWebsiteOS(ctx context.Context, filter *db.Filters) ([]*model rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -198,7 +199,7 @@ func (c *Client) GetWebsiteOS(ctx context.Context, filter *db.Filters) ([]*model var o model.StatsOS err := rows.StructScan(&o) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } os = append(os, &o) } @@ -237,7 +238,7 @@ func (c *Client) GetWebsiteDevicesSummary(ctx context.Context, filter *db.Filter rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -245,7 +246,7 @@ func (c *Client) GetWebsiteDevicesSummary(ctx context.Context, filter *db.Filter var device model.StatsDevicesSummary err := rows.StructScan(&device) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } devices = append(devices, &device) } @@ -287,7 +288,7 @@ func (c *Client) GetWebsiteDevices(ctx context.Context, filter *db.Filters) ([]* rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -295,7 +296,7 @@ func (c *Client) GetWebsiteDevices(ctx context.Context, filter *db.Filters) ([]* var device model.StatsDevices err := rows.StructScan(&device) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } devices = append(devices, &device) } diff --git a/core/db/duckdb/utm.go b/core/db/duckdb/utm.go index 37a00fae..1b7fc149 100644 --- a/core/db/duckdb/utm.go +++ b/core/db/duckdb/utm.go @@ -4,6 +4,7 @@ import ( "context" "strings" + "github.com/go-faster/errors" "github.com/medama-io/medama/db" "github.com/medama-io/medama/model" ) @@ -39,7 +40,7 @@ func (c *Client) GetWebsiteUTMSourcesSummary(ctx context.Context, filter *db.Fil rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -47,7 +48,7 @@ func (c *Client) GetWebsiteUTMSourcesSummary(ctx context.Context, filter *db.Fil var utm model.StatsUTMSourcesSummary err := rows.StructScan(&utm) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } utms = append(utms, &utm) } @@ -89,7 +90,7 @@ func (c *Client) GetWebsiteUTMSources(ctx context.Context, filter *db.Filters) ( rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -97,7 +98,7 @@ func (c *Client) GetWebsiteUTMSources(ctx context.Context, filter *db.Filters) ( var utm model.StatsUTMSources err := rows.StructScan(&utm) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } utms = append(utms, &utm) } @@ -136,7 +137,7 @@ func (c *Client) GetWebsiteUTMMediumsSummary(ctx context.Context, filter *db.Fil rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -144,7 +145,7 @@ func (c *Client) GetWebsiteUTMMediumsSummary(ctx context.Context, filter *db.Fil var utm model.StatsUTMMediumsSummary err := rows.StructScan(&utm) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } utms = append(utms, &utm) } @@ -186,7 +187,7 @@ func (c *Client) GetWebsiteUTMMediums(ctx context.Context, filter *db.Filters) ( rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -194,7 +195,7 @@ func (c *Client) GetWebsiteUTMMediums(ctx context.Context, filter *db.Filters) ( var utm model.StatsUTMMediums err := rows.StructScan(&utm) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } utms = append(utms, &utm) } @@ -233,7 +234,7 @@ func (c *Client) GetWebsiteUTMCampaignsSummary(ctx context.Context, filter *db.F rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -241,7 +242,7 @@ func (c *Client) GetWebsiteUTMCampaignsSummary(ctx context.Context, filter *db.F var utm model.StatsUTMCampaignsSummary err := rows.StructScan(&utm) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } utms = append(utms, &utm) } @@ -283,7 +284,7 @@ func (c *Client) GetWebsiteUTMCampaigns(ctx context.Context, filter *db.Filters) rows, err := c.NamedQueryContext(ctx, query.String(), filter.Args(nil)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer rows.Close() @@ -291,7 +292,7 @@ func (c *Client) GetWebsiteUTMCampaigns(ctx context.Context, filter *db.Filters) var utm model.StatsUTMCampaigns err := rows.StructScan(&utm) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } utms = append(utms, &utm) } diff --git a/core/db/duckdb/websites.go b/core/db/duckdb/websites.go index bd48985a..68bf4a8c 100644 --- a/core/db/duckdb/websites.go +++ b/core/db/duckdb/websites.go @@ -2,6 +2,8 @@ package duckdb import ( "context" + + "github.com/go-faster/errors" ) // DeleteWebsite deletes all rows associated with the given hostname. @@ -11,7 +13,7 @@ func (c *Client) DeleteWebsite(ctx context.Context, hostname string) error { _, err := c.ExecContext(ctx, query, hostname) if err != nil { - return err + return errors.Wrap(err, "db") } return nil diff --git a/core/db/sqlite/client.go b/core/db/sqlite/client.go index b78c9b80..106ba36d 100644 --- a/core/db/sqlite/client.go +++ b/core/db/sqlite/client.go @@ -3,6 +3,7 @@ package sqlite import ( "fmt" + "github.com/go-faster/errors" "github.com/jmoiron/sqlx" "github.com/medama-io/medama/db" ) @@ -19,7 +20,7 @@ func NewClient(host string) (*Client, error) { // Enable foreign key support in sqlite db, err := sqlx.Connect("sqlite3", fmt.Sprintf("file:%s", host)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "sqlite") } return &Client{ diff --git a/core/db/sqlite/helpers_test.go b/core/db/sqlite/helpers_test.go index 2237e02b..af817377 100644 --- a/core/db/sqlite/helpers_test.go +++ b/core/db/sqlite/helpers_test.go @@ -14,23 +14,25 @@ import ( "github.com/medama-io/medama/migrations" "github.com/medama-io/medama/model" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func SetupDatabase(t *testing.T) (*assert.Assertions, context.Context, *sqlite.Client) { t.Helper() assert := assert.New(t) + require := require.New(t) ctx := context.Background() // Disable logging log.SetOutput(io.Discard) // Generate new memory db per test client, err := sqlite.NewClient(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())) - assert.NoError(err) + require.NoError(err) assert.NotNil(client) // Empty duckdb client not used in tests duckdbClient, err := duckdb.NewClient("") - assert.NoError(err) + require.NoError(err) assert.NotNil(duckdbClient) // Run migrations diff --git a/core/db/sqlite/users.go b/core/db/sqlite/users.go index 9d520852..f7e9cc72 100644 --- a/core/db/sqlite/users.go +++ b/core/db/sqlite/users.go @@ -3,8 +3,8 @@ package sqlite import ( "context" "database/sql" - "errors" + "github.com/go-faster/errors" "github.com/mattn/go-sqlite3" "github.com/medama-io/medama/model" ) @@ -22,7 +22,7 @@ func (c *Client) CreateUser(ctx context.Context, user *model.User) error { } } - return err + return errors.Wrap(err, "db") } return nil @@ -38,7 +38,7 @@ func (c *Client) GetUser(ctx context.Context, id string) (*model.User, error) { return nil, model.ErrUserNotFound } - return nil, err + return nil, errors.Wrap(err, "db") } defer res.Close() @@ -48,7 +48,7 @@ func (c *Client) GetUser(ctx context.Context, id string) (*model.User, error) { err := res.StructScan(user) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } return user, nil @@ -63,7 +63,7 @@ func (c *Client) GetUserByUsername(ctx context.Context, username string) (*model res, err := c.DB.QueryxContext(ctx, query, username) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } defer res.Close() @@ -73,7 +73,7 @@ func (c *Client) GetUserByUsername(ctx context.Context, username string) (*model err := res.StructScan(user) if err != nil { - return nil, err + return nil, errors.Wrap(err, "db") } return user, nil @@ -98,7 +98,7 @@ func (c *Client) UpdateUserUsername(ctx context.Context, id string, username str if errors.Is(err, sql.ErrNoRows) { return model.ErrUserNotFound } - return err + return errors.Wrap(err, "db") } return nil @@ -114,7 +114,7 @@ func (c *Client) UpdateUserPassword(ctx context.Context, id string, password str return model.ErrUserNotFound } - return err + return errors.Wrap(err, "db") } return nil @@ -126,13 +126,13 @@ func (c *Client) DeleteUser(ctx context.Context, id string) error { res, err := c.DB.ExecContext(ctx, exec, id) if err != nil { - return err + return errors.Wrap(err, "db") } // Delete statement will silently succeed if the user does not exist. count, err := res.RowsAffected() if err != nil { - return err + return errors.Wrap(err, "db") } if count == 0 { diff --git a/core/db/sqlite/websites.go b/core/db/sqlite/websites.go index 7903ac0f..79345e6a 100644 --- a/core/db/sqlite/websites.go +++ b/core/db/sqlite/websites.go @@ -3,9 +3,9 @@ package sqlite import ( "context" "database/sql" - "errors" "log/slog" + "github.com/go-faster/errors" "github.com/mattn/go-sqlite3" "github.com/medama-io/medama/model" ) @@ -39,7 +39,7 @@ func (c *Client) CreateWebsite(ctx context.Context, website *model.Website) erro slog.LogAttrs(ctx, slog.LevelError, "failed to create website", attributes...) - return err + return errors.Wrap(err, "db") } return nil @@ -60,7 +60,7 @@ func (c *Client) ListWebsites(ctx context.Context, userID string) ([]*model.Webs slog.LogAttrs(ctx, slog.LevelError, "failed to list websites", attributes...) - return nil, err + return nil, errors.Wrap(err, "db") } if len(websites) == 0 { @@ -95,7 +95,7 @@ func (c *Client) UpdateWebsite(ctx context.Context, website *model.Website) erro slog.LogAttrs(ctx, slog.LevelError, "failed to update website", attributes...) - return err + return errors.Wrap(err, "db") } rowsAffected, err := res.RowsAffected() @@ -109,7 +109,7 @@ func (c *Client) UpdateWebsite(ctx context.Context, website *model.Website) erro slog.LogAttrs(ctx, slog.LevelError, "failed to get rows affected", attributes...) - return err + return errors.Wrap(err, "db") } if rowsAffected == 0 { @@ -140,7 +140,7 @@ func (c *Client) GetWebsite(ctx context.Context, hostname string) (*model.Websit slog.LogAttrs(ctx, slog.LevelError, "failed to get website", attributes...) - return nil, err + return nil, errors.Wrap(err, "db") } return &website, nil @@ -159,7 +159,7 @@ func (c *Client) DeleteWebsite(ctx context.Context, hostname string) error { slog.LogAttrs(ctx, slog.LevelError, "failed to delete website", attributes...) - return err + return errors.Wrap(err, "db") } rowsAffected, err := res.RowsAffected() @@ -171,7 +171,7 @@ func (c *Client) DeleteWebsite(ctx context.Context, hostname string) error { slog.LogAttrs(ctx, slog.LevelError, "failed to get rows affected", attributes...) - return err + return errors.Wrap(err, "db") } if rowsAffected == 0 { @@ -197,7 +197,7 @@ func (c *Client) WebsiteExists(ctx context.Context, hostname string) (bool, erro slog.LogAttrs(ctx, slog.LevelError, "failed to check if website exists", attributes...) - return false, err + return false, errors.Wrap(err, "db") } return exists, nil diff --git a/core/services/oas.go b/core/services/oas.go index 6aa6edfe..d93d945e 100644 --- a/core/services/oas.go +++ b/core/services/oas.go @@ -1,6 +1,7 @@ package services import ( + "github.com/go-faster/errors" tz "github.com/medama-io/go-timezone-country" "github.com/medama-io/go-useragent" "github.com/medama-io/medama/db/duckdb" @@ -21,12 +22,12 @@ type Handler struct { func NewService(auth *util.AuthService, sqlite *sqlite.Client, duckdb *duckdb.Client) (*Handler, error) { tzMap, err := tz.NewTimezoneCodeMap() if err != nil { - return nil, err + return nil, errors.Wrap(err, "services") } codeCountryMap, err := tz.NewCodeCountryMap() if err != nil { - return nil, err + return nil, errors.Wrap(err, "services") } return &Handler{ diff --git a/core/services/users.go b/core/services/users.go index bfed2fe0..37955a60 100644 --- a/core/services/users.go +++ b/core/services/users.go @@ -2,10 +2,10 @@ package services import ( "context" - "errors" "log/slog" "time" + "github.com/go-faster/errors" "github.com/medama-io/medama/api" "github.com/medama-io/medama/model" ) @@ -31,7 +31,7 @@ func (h *Handler) GetUser(ctx context.Context, params api.GetUserParams) (api.Ge } slog.LogAttrs(ctx, slog.LevelError, "failed to get user", attributes...) - return nil, err + return nil, errors.Wrap(err, "services") } return &api.UserGet{ @@ -64,7 +64,7 @@ func (h *Handler) PatchUser(ctx context.Context, req *api.UserPatch, params api. } slog.LogAttrs(ctx, slog.LevelError, "failed to get user", attributes...) - return nil, err + return nil, errors.Wrap(err, "services") } // Update values @@ -86,7 +86,7 @@ func (h *Handler) PatchUser(ctx context.Context, req *api.UserPatch, params api. attributes = append(attributes, slog.String("error", err.Error())) slog.LogAttrs(ctx, slog.LevelError, "failed to update user email", attributes...) - return nil, err + return nil, errors.Wrap(err, "services") } } @@ -96,14 +96,14 @@ func (h *Handler) PatchUser(ctx context.Context, req *api.UserPatch, params api. if err != nil { attributes = append(attributes, slog.String("error", err.Error())) slog.LogAttrs(ctx, slog.LevelError, "failed to hash password", attributes...) - return nil, err + return nil, errors.Wrap(err, "services") } err = h.db.UpdateUserPassword(ctx, user.ID, pwdHash, dateUpdated) if err != nil { attributes = append(attributes, slog.String("error", err.Error())) slog.LogAttrs(ctx, slog.LevelError, "failed to update user password", attributes...) - return nil, err + return nil, errors.Wrap(err, "services") } } @@ -137,7 +137,7 @@ func (h *Handler) DeleteUser(ctx context.Context, params api.DeleteUserParams) ( } slog.LogAttrs(ctx, slog.LevelError, "failed to get user", attributes...) - return nil, err + return nil, errors.Wrap(err, "services") } attributes = append(attributes, @@ -152,7 +152,7 @@ func (h *Handler) DeleteUser(ctx context.Context, params api.DeleteUserParams) ( if err != nil { attributes = append(attributes, slog.String("error", err.Error())) slog.LogAttrs(ctx, slog.LevelError, "failed to delete user", attributes...) - return nil, err + return nil, errors.Wrap(err, "services") } return &api.DeleteUserOK{}, nil diff --git a/core/services/websites.go b/core/services/websites.go index 024fedca..420f0659 100644 --- a/core/services/websites.go +++ b/core/services/websites.go @@ -2,9 +2,9 @@ package services import ( "context" - "errors" "time" + "github.com/go-faster/errors" "github.com/medama-io/medama/api" "github.com/medama-io/medama/model" ) @@ -22,7 +22,7 @@ func (h *Handler) DeleteWebsitesID(ctx context.Context, params api.DeleteWebsite return ErrNotFound(err), nil } - return nil, err + return nil, errors.Wrap(err, "services") } var website *model.Website @@ -44,7 +44,7 @@ func (h *Handler) DeleteWebsitesID(ctx context.Context, params api.DeleteWebsite return ErrNotFound(err), nil } - return nil, err + return nil, errors.Wrap(err, "services") } // Delete all views associated with website @@ -54,7 +54,7 @@ func (h *Handler) DeleteWebsitesID(ctx context.Context, params api.DeleteWebsite return ErrNotFound(err), nil } - return nil, err + return nil, errors.Wrap(err, "services") } return &api.DeleteWebsitesIDOK{}, nil @@ -72,7 +72,7 @@ func (h *Handler) GetWebsites(ctx context.Context, params api.GetWebsitesParams) if errors.Is(err, model.ErrWebsiteNotFound) { return ErrNotFound(err), nil } - return nil, err + return nil, errors.Wrap(err, "services") } // Map to API response @@ -100,7 +100,7 @@ func (h *Handler) GetWebsitesID(ctx context.Context, params api.GetWebsitesIDPar return ErrNotFound(err), nil } - return nil, err + return nil, errors.Wrap(err, "services") } if website.UserID != userId { @@ -126,7 +126,7 @@ func (h *Handler) PatchWebsitesID(ctx context.Context, req *api.WebsitePatch, pa return ErrNotFound(err), nil } - return nil, err + return nil, errors.Wrap(err, "services") } if website.UserID != userId { @@ -151,7 +151,7 @@ func (h *Handler) PatchWebsitesID(ctx context.Context, req *api.WebsitePatch, pa return ErrNotFound(err), nil } - return nil, err + return nil, errors.Wrap(err, "services") } return &api.WebsiteGet{ @@ -179,7 +179,7 @@ func (h *Handler) PostWebsites(ctx context.Context, req *api.WebsiteCreate) (api err := h.db.CreateWebsite(ctx, websiteCreate) if err != nil { - return nil, err + return nil, errors.Wrap(err, "services") } return &api.WebsiteGet{ diff --git a/core/util/auth.go b/core/util/auth.go index 4ead0a46..ac0a6a70 100644 --- a/core/util/auth.go +++ b/core/util/auth.go @@ -13,6 +13,7 @@ import ( "time" "github.com/alexedwards/argon2id" + "github.com/go-faster/errors" "github.com/medama-io/medama/model" "go.jetpack.io/typeid" ) @@ -38,7 +39,7 @@ func NewAuthService(ctx context.Context) (*AuthService, error) { key := make([]byte, DefaultCipherKeySize) _, err := rand.Read(key) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to generate cipher key") } return &AuthService{ @@ -51,7 +52,7 @@ func NewAuthService(ctx context.Context) (*AuthService, error) { func (a *AuthService) HashPassword(password string) (string, error) { hash, err := argon2id.CreateHash(password, argon2id.DefaultParams) if err != nil { - return "", err + return "", errors.Wrap(err, "failed to hash password") } return hash, nil @@ -61,7 +62,7 @@ func (a *AuthService) HashPassword(password string) (string, error) { func (a *AuthService) ComparePasswords(suppliedPassword string, storedHash string) (bool, error) { match, err := argon2id.ComparePasswordAndHash(suppliedPassword, storedHash) if err != nil { - return false, err + return false, errors.Wrap(err, "failed to compare passwords") } return match, nil @@ -72,20 +73,20 @@ func (a *AuthService) EncryptSession(ctx context.Context, sessionId string, dura // Create a new AES cipher block. block, err := aes.NewCipher(a.aes32Key) if err != nil { - return "", err + return "", errors.Wrap(err, "auth: encrypt") } // Wrap the block in a GCM cipher. aesgcm, err := cipher.NewGCM(block) if err != nil { - return "", err + return "", errors.Wrap(err, "auth: encrypt") } // Create a random 12 byte nonce. nonce := make([]byte, aesgcm.NonceSize()) _, err = io.ReadFull(rand.Reader, nonce) if err != nil { - return "", err + return "", errors.Wrap(err, "auth: encrypt") } // Authenticate cookie name and value with {name:value} format. @@ -103,13 +104,13 @@ func (a *AuthService) DecryptSession(ctx context.Context, session string) (strin // Create a new AES cipher block. block, err := aes.NewCipher(a.aes32Key) if err != nil { - return "", err + return "", errors.Wrap(err, "auth: decrypt") } // Wrap the block in a GCM cipher. aesgcm, err := cipher.NewGCM(block) if err != nil { - return "", err + return "", errors.Wrap(err, "auth: decrypt") } // Check for potential index out of range error. @@ -147,7 +148,7 @@ func (a *AuthService) CreateSession(ctx context.Context, userId string) (*http.C // Generate session token. sessionIdType, err := typeid.WithPrefix("sess") if err != nil { - return nil, err + return nil, errors.Wrap(err, "auth: session") } sessionId := sessionIdType.String() @@ -185,7 +186,7 @@ func (a *AuthService) ReadSession(ctx context.Context, session string) (string, // Decrypt session token. sessionId, err := a.DecryptSession(ctx, string(encryptedSession)) if err != nil { - return "", err + return "", errors.Wrap(err, "session") } // Check if session exists. diff --git a/core/util/auth_test.go b/core/util/auth_test.go index 691c4cb0..5a462172 100644 --- a/core/util/auth_test.go +++ b/core/util/auth_test.go @@ -9,26 +9,28 @@ import ( "github.com/medama-io/medama/model" "github.com/medama-io/medama/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func SetupAuthTest(t *testing.T) (*assert.Assertions, context.Context, *util.AuthService) { +func SetupAuthTest(t *testing.T) (*assert.Assertions, *require.Assertions, context.Context, *util.AuthService) { t.Helper() assert := assert.New(t) + require := require.New(t) ctx := context.Background() auth, err := util.NewAuthService(ctx) - assert.NoError(err) + require.NoError(err) assert.NotNil(auth) - return assert, ctx, auth + return assert, require, ctx, auth } func TestAuthCreateAndRead(t *testing.T) { - assert, ctx, auth := SetupAuthTest(t) + assert, require, ctx, auth := SetupAuthTest(t) // We don't want the cookie to expire cookie, err := auth.CreateSession(ctx, "test_user_id") - assert.NoError(err) + require.NoError(err) assert.NotNil(cookie) assert.Equal("_me_sess", cookie.Name) @@ -39,41 +41,41 @@ func TestAuthCreateAndRead(t *testing.T) { // Decrypt cookie userId, err := auth.ReadSession(ctx, cookie.Value) - assert.NoError(err) + require.NoError(err) assert.Equal("test_user_id", userId) } func TestAuthWithInvalidSession(t *testing.T) { - assert, ctx, auth := SetupAuthTest(t) + assert, require, ctx, auth := SetupAuthTest(t) // We don't want the cookie to expire cookie, err := auth.CreateSession(ctx, "test_user") - assert.NoError(err) + require.NoError(err) assert.NotNil(cookie) // Decrypt cookie userId, err := auth.ReadSession(ctx, "invalid_session") - assert.ErrorIs(err, model.ErrInvalidSession) + require.ErrorIs(err, model.ErrInvalidSession) assert.Equal("", userId) } func TestAuthWithExpiredSession(t *testing.T) { - assert, ctx, auth := SetupAuthTest(t) + assert, require, ctx, auth := SetupAuthTest(t) cookie, err := auth.CreateSession(ctx, "test_user_id") - assert.NoError(err) + require.NoError(err) assert.NotNil(cookie) // Delete from cache to simulate expired session base64Decode, err := base64.URLEncoding.DecodeString(cookie.Value) - assert.NoError(err) + require.NoError(err) sessionId, err := auth.DecryptSession(ctx, string(base64Decode)) - assert.NoError(err) + require.NoError(err) assert.NotEmpty(sessionId) auth.Cache.Delete(sessionId) // Try to read from session with expired cookie userId, err := auth.ReadSession(ctx, cookie.Value) - assert.ErrorIs(err, model.ErrSessionNotFound) + require.ErrorIs(err, model.ErrSessionNotFound) assert.Equal("", userId) } diff --git a/core/util/cache_test.go b/core/util/cache_test.go index 33383d03..260b71ab 100644 --- a/core/util/cache_test.go +++ b/core/util/cache_test.go @@ -9,18 +9,20 @@ import ( "github.com/medama-io/medama/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func SetupCacheTest(t *testing.T) (*assert.Assertions, context.Context) { +func SetupCacheTest(t *testing.T) (*assert.Assertions, *require.Assertions, context.Context) { t.Helper() assert := assert.New(t) + require := require.New(t) ctx := context.Background() log.SetOutput(io.Discard) - return assert, ctx + return assert, require, ctx } func TestGetSet(t *testing.T) { - assert, ctx := SetupCacheTest(t) + _, require, ctx := SetupCacheTest(t) cycle := 100 * time.Millisecond c := util.NewCache(ctx, cycle) @@ -29,7 +31,7 @@ func TestGetSet(t *testing.T) { c.Set("sticky", "forever", cycle*2) c.Set("hello", "Hello", cycle/2) hello, err := c.Get(ctx, "hello") - assert.NoError(err, "Cache miss") + require.NoError(err, "Cache miss") if hello.(string) != "Hello" { t.Log("Cache value mismatch") @@ -39,46 +41,46 @@ func TestGetSet(t *testing.T) { time.Sleep(cycle / 2) _, err = c.Get(ctx, "hello") - assert.Error(err, "Cache value not expired") + require.Error(err, "Cache value not expired") time.Sleep(cycle) _, err = c.Get(ctx, "404") - assert.Error(err, "Cache value not expired") + require.Error(err, "Cache value not expired") _, err = c.Get(ctx, "sticky") - assert.NoError(err, "Cache value not found") + require.NoError(err, "Cache value not found") } func TestHas(t *testing.T) { - assert, ctx := SetupCacheTest(t) + assert, require, ctx := SetupCacheTest(t) c := util.NewCache(ctx, time.Minute) c.Set("hello", "Hello", time.Hour) ok, err := c.Has(ctx, "hello") assert.True(ok, "Cache miss") - assert.NoError(err, "Cache error") + require.NoError(err, "Cache error") ok, err = c.Has(ctx, "404") assert.False(ok, "Cache hit") - assert.NoError(err, "Cache error") + require.NoError(err, "Cache error") } func TestDelete(t *testing.T) { - assert, ctx := SetupCacheTest(t) + _, require, ctx := SetupCacheTest(t) c := util.NewCache(ctx, time.Minute) c.Set("hello", "Hello", time.Hour) _, err := c.Get(ctx, "hello") - assert.NoError(err, "Cache miss") + require.NoError(err, "Cache miss") c.Delete("hello") _, err = c.Get(ctx, "hello") - assert.Error(err, "Cache value not deleted") + require.Error(err, "Cache value not deleted") } func TestRange(t *testing.T) { - assert, ctx := SetupCacheTest(t) + assert, _, ctx := SetupCacheTest(t) c := util.NewCache(ctx, time.Minute) c.Set("hello", "Hello", time.Hour) c.Set("world", "World", time.Hour) @@ -93,7 +95,7 @@ func TestRange(t *testing.T) { } func TestRangeTimer(t *testing.T) { - _, ctx := SetupCacheTest(t) + _, _, ctx := SetupCacheTest(t) c := util.NewCache(ctx, time.Minute) c.Set("message", "Hello", time.Nanosecond) c.Set("world", "World", time.Nanosecond)