Skip to content

Commit

Permalink
chore: refactor errors (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
williamsjokvist committed Nov 5, 2024
1 parent 98d9216 commit d7856ce
Show file tree
Hide file tree
Showing 26 changed files with 349 additions and 222 deletions.
82 changes: 35 additions & 47 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package cmd

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"runtime"
Expand All @@ -14,7 +11,6 @@ import (
"github.com/hashicorp/go-version"

"github.com/williamsjokvist/cfn-tracker/pkg/config"
"github.com/williamsjokvist/cfn-tracker/pkg/errorsx"
"github.com/williamsjokvist/cfn-tracker/pkg/i18n"
"github.com/williamsjokvist/cfn-tracker/pkg/i18n/locales"
"github.com/williamsjokvist/cfn-tracker/pkg/model"
Expand Down Expand Up @@ -47,28 +43,19 @@ func NewCommandHandler(githubClient github.GithubClient, sqlDb *sql.Storage, nos
func (ch *CommandHandler) CheckForUpdate() (bool, error) {
currentVersion, err := version.NewVersion(ch.cfg.AppVersion)
if err != nil {
log.Println(err)
return false, fmt.Errorf(`failed to parse current app version: %w`, err)
return false, model.NewError(model.ErrCheckForUpdate, err)
}
latestVersion, err := ch.githubClient.GetLatestAppVersion()
if err != nil {
log.Println(err)
return false, fmt.Errorf(`failed to check for update: %w`, err)
return false, model.NewError(model.ErrCheckForUpdate, err)
}

hasUpdate := currentVersion.LessThan(latestVersion)
log.Println(`Has update: `, hasUpdate, `. Current: `, currentVersion.String(), ` Latest: `, latestVersion.String())
return hasUpdate, nil
return currentVersion.LessThan(latestVersion), nil
}

func (ch *CommandHandler) GetTranslation(locale string) (*locales.Localization, error) {
lng, err := i18n.GetTranslation(locale)
if err != nil {
log.Println(err)
if !errorsx.ContainsFormattedError(err) {
err = errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf(`failed to get translation %w`, err))
}
return nil, err
return nil, model.NewError(model.ErrGetTranslations, err)
}
return lng, nil
}
Expand All @@ -77,61 +64,50 @@ func (ch *CommandHandler) GetAppVersion() string {
return ch.cfg.AppVersion
}

func (ch *CommandHandler) OpenResultsDirectory() {
func (ch *CommandHandler) OpenResultsDirectory() error {
switch runtime.GOOS {
case `darwin`:
if err := exec.Command(`Open`, `./results`).Run(); err != nil {
log.Println(err)
return model.NewError(model.ErrOpenResultsDirectory, err)
}
case `windows`:
if err := exec.Command(`explorer.exe`, `.\results`).Run(); err != nil {
log.Println(err)
return model.NewError(model.ErrOpenResultsDirectory, err)
}
}
return nil
}

func (ch *CommandHandler) GetSessions(userId, date string, limit uint8, offset uint16) ([]*model.Session, error) {
sessions, err := ch.sqlDb.GetSessions(context.Background(), userId, date, limit, offset)
if err != nil {
log.Println(err)
if !errorsx.ContainsFormattedError(err) {
err = errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf(`failed to get sessions %w`, err))
}
return nil, model.NewError(model.ErrGetSessions, err)
}
return sessions, err
return sessions, nil
}

func (ch *CommandHandler) GetSessionsStatistics(userId string) (*model.SessionsStatistics, error) {
sessionStatistics, err := ch.sqlDb.GetSessionsStatistics(context.Background(), userId)
if err != nil {
log.Println(err)
if !errorsx.ContainsFormattedError(err) {
err = errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf(`failed to get monthly session counts %w`, err))
}
return nil, model.NewError(model.ErrGetSessionStatistics, err)
}
return sessionStatistics, err
return sessionStatistics, nil
}

func (ch *CommandHandler) GetMatches(sessionId uint16, userId string, limit uint8, offset uint16) ([]*model.Match, error) {
matches, err := ch.sqlDb.GetMatches(context.Background(), sessionId, userId, limit, offset)
if err != nil {
log.Println(err)
if !errorsx.ContainsFormattedError(err) {
err = errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf(`failed to get matches %w`, err))
}
return nil, model.NewError(model.ErrGetMatches, err)
}
return matches, err
return matches, nil
}

func (ch *CommandHandler) GetUsers() ([]*model.User, error) {
users, err := ch.sqlDb.GetUsers(context.Background())
if err != nil {
log.Println(err)
if !errorsx.ContainsFormattedError(err) {
err = errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf(`failed to get users %w`, err))
}
return nil, model.NewError(model.ErrGetUser, err)
}
return users, err
return users, nil
}

func (ch *CommandHandler) GetThemes() ([]model.Theme, error) {
Expand All @@ -152,8 +128,7 @@ func (ch *CommandHandler) GetThemes() ([]model.Theme, error) {
}
css, err := os.ReadFile(fmt.Sprintf(`themes/%s`, fileName))
if err != nil {
log.Println(err)
return nil, errorsx.NewFormattedError(http.StatusInternalServerError, errors.New("failed to read theme css"))
return nil, model.NewError(model.ErrReadThemeCSS, err)
}
name := strings.Split(fileName, `.css`)[0]

Expand All @@ -172,21 +147,34 @@ func (ch *CommandHandler) GetSupportedLanguages() []string {
}

func (ch *CommandHandler) SaveLocale(locale string) error {
return ch.nosqlDb.SaveLocale(locale)
if err := ch.nosqlDb.SaveLocale(locale); err != nil {
return model.NewError(model.ErrSaveLocale, err)
}
return nil
}

func (ch *CommandHandler) GetGuiConfig() (*model.GuiConfig, error) {
return ch.nosqlDb.GetGuiConfig()
guiCfg, err := ch.nosqlDb.GetGuiConfig()
if err != nil {
return nil, model.NewError(model.ErrGetGUIConfig, err)
}
return guiCfg, nil
}

func (ch *CommandHandler) SaveSidebarMinimized(sidebarMinified bool) error {
return ch.nosqlDb.SaveSidebarMinimized(sidebarMinified)
if err := ch.nosqlDb.SaveSidebarMinimized(sidebarMinified); err != nil {
return model.NewError(model.ErrSaveSidebarMinimized, err)
}
return nil
}

func (ch *CommandHandler) SaveTheme(theme model.ThemeName) error {
return ch.nosqlDb.SaveTheme(theme)
if err := ch.nosqlDb.SaveTheme(theme); err != nil {
return model.NewError(model.ErrSaveTheme, err)
}
return nil
}

func (ch *CommandHandler) GetFormattedErrorModelUnused() *errorsx.FormattedError {
func (ch *CommandHandler) GetFormattedErrorModelUnused() *model.FormattedError {
return nil
}
6 changes: 3 additions & 3 deletions cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ var testSuite = struct {
func TestMain(m *testing.M) {
sqlDb, err := sql.NewStorage(true)
if err != nil {
log.Fatalf("failed to init sql store: %v", err)
log.Fatalf("init sql storage: %v", err)
}
nosqlDb, err := nosql.NewStorage()
if err != nil {
log.Fatalf("failed to init nosql store: %v", err)
log.Fatalf("init nosql storage: %v", err)
}
txtDb, err := txt.NewStorage()
if err != nil {
log.Fatalf("failed to init txt store: %v", err)
log.Fatalf("init txt storage: %v", err)
}

cfg := config.Config{
Expand Down
20 changes: 9 additions & 11 deletions cmd/tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import (
"errors"
"fmt"
"log"
"net/http"
"time"

"github.com/williamsjokvist/cfn-tracker/pkg/config"
"github.com/williamsjokvist/cfn-tracker/pkg/errorsx"
"github.com/williamsjokvist/cfn-tracker/pkg/model"
"github.com/williamsjokvist/cfn-tracker/pkg/storage/nosql"
"github.com/williamsjokvist/cfn-tracker/pkg/storage/sql"
Expand Down Expand Up @@ -74,28 +72,28 @@ func (ch *TrackingHandler) StartTracking(userCode string, restore bool) error {
if restore {
sesh, err := ch.sqlDb.GetLatestSession(ctx, userCode)
if err != nil {
return errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf("get latest session: %w", err))
return model.NewError(model.ErrGetLatestSession, err)
}
session = sesh
} else {
user, err := ch.sqlDb.GetUserByCode(ctx, userCode)
if err != nil && !errors.Is(err, sql.ErrUserNotFound) {
return errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf("get user from db: %w", err))
return model.NewError(model.ErrGetUser, err)
}

if user == nil {
usr, err := ch.gameTracker.GetUser(ctx, userCode)
if err != nil {
return errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf("get user from api: %w", err))
return model.NewError(model.ErrGetUser, err)
}
if err := ch.sqlDb.SaveUser(ctx, *usr); err != nil {
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf("save user: %w", err))
return model.NewError(model.ErrGetUser, err)
}
}

sesh, err := ch.sqlDb.CreateSession(ctx, userCode)
if err != nil {
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf("create session: %w", err))
return model.NewError(model.ErrCreateSession, err)
}
session = sesh
// session.LP = bl.GetLP()
Expand All @@ -104,7 +102,7 @@ func (ch *TrackingHandler) StartTracking(userCode string, restore bool) error {
}

if session == nil {
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf("session not created"))
return model.ErrCreateSession
}

ticker := time.NewTicker(30 * time.Second)
Expand Down Expand Up @@ -185,7 +183,6 @@ func (ch *TrackingHandler) StopTracking() {

func (ch *TrackingHandler) SelectGame(game model.GameType) error {
var username, password string

switch game {
case model.GameTypeT8:
ch.gameTracker = t8.NewT8Tracker(ch.wavuClient)
Expand All @@ -194,7 +191,8 @@ func (ch *TrackingHandler) SelectGame(game model.GameType) error {
username = ch.cfg.CapIDEmail
password = ch.cfg.CapIDPassword
default:
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf(`failed to select game`))
errGameNotExist := fmt.Errorf("game does not exist")
return model.NewError(model.ErrSelectGame, errGameNotExist)
}

authChan := make(chan tracker.AuthStatus)
Expand All @@ -203,7 +201,7 @@ func (ch *TrackingHandler) SelectGame(game model.GameType) error {
go ch.gameTracker.Authenticate(ctx, username, password, authChan)
for status := range authChan {
if status.Err != nil {
return errorsx.NewFormattedError(http.StatusUnauthorized, status.Err)
return model.NewError(model.ErrAuth, status.Err)
}

ch.eventEmitter("auth-progress", status.Progress)
Expand Down
4 changes: 2 additions & 2 deletions cmd/tracking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ func TestTrackingSelectGame(t *testing.T) {
{
name: "select SF6",
gameType: model.GameTypeSF6,
expErrMsg: "browser not initialized",
expErrMsg: "unauthenticated: browser not initialized",
},
{
name: "select game that doesn't exist",
gameType: "undefined",
expErrMsg: "failed to select game",
expErrMsg: "select game: game does not exist",
},
}

Expand Down
2 changes: 1 addition & 1 deletion gui/.config/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ export default {
}
},
},
plugins: [require("@tailwindcss/forms")],
plugins: [import("@tailwindcss/forms")],
}
29 changes: 13 additions & 16 deletions gui/src/main/app-error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { useRouteError } from 'react-router-dom'
import { motion } from 'framer-motion'
import { Icon } from '@iconify/react'

import { type model } from '@model'
import * as Page from '@/ui/page'
import { errorsx } from '@model'

import { LocalizedErrorMessage } from './error-popup'
import { AppTitleBar } from './app-titlebar'
import { LocalizationKey } from './i18n'

export function AppErrorBoundary() {
const err = useFormattedError()
Expand All @@ -22,40 +22,39 @@ export function AppErrorBoundary() {
export function PageErrorBoundary() {
const { t } = useTranslation()
const err = useFormattedError()
if (!err?.code) {
if (!err?.localizationKey) {
return null
}
return (
<ErrorWrapper err={err}>
<Page.Header>
<Page.Title>{t(LocalizedErrorMessage[err!.code!])}</Page.Title>
<Page.Title>{t(err.localizationKey)}</Page.Title>
</Page.Header>
</ErrorWrapper>
)
}

const isFormattedError = (error: unknown) =>
error instanceof Object && 'message' in error && 'code' in error
const isFormattedError = (error: unknown) => error instanceof Object && 'translationKey' in error

function useFormattedError() {
const thrownError = useRouteError()
const [err, setErr] = React.useState<errorsx.FormattedError>()
const [err, setErr] = React.useState<model.FormattedError | unknown>()

React.useEffect(() => {
console.error(thrownError)
if (thrownError instanceof Error) {
setErr({ code: 500, message: thrownError.message })
setErr({ translationKey: "", message: thrownError.message, error: thrownError })
} else if (isFormattedError(thrownError)) {
setErr(thrownError as errorsx.FormattedError)
setErr(thrownError)
}
}, [thrownError])

return err
return err as model.FormattedError
}

function ErrorWrapper(props: React.PropsWithChildren & { err?: errorsx.FormattedError }) {
function ErrorWrapper(props: React.PropsWithChildren & { err?: model.FormattedError }) {
const { t } = useTranslation()
if (!props.err?.code) {
if (!props.err) {
return null
}
return (
Expand All @@ -68,10 +67,8 @@ function ErrorWrapper(props: React.PropsWithChildren & { err?: errorsx.Formatted
{props.children}
<div className='mt-8 flex w-full flex-col items-center justify-center rounded-md pb-16 text-center'>
<Icon icon='material-symbols:warning-outline' className='h-40 w-40 text-[#ff6388]' />
<h1 className='text-center text-2xl font-bold'>
{t(LocalizedErrorMessage[props.err.code])}
</h1>
<p className='text-xl'>{props.err.message}</p>
<h1 className='text-center text-2xl font-bold'>{t(props.err?.localizationKey)}</h1>
<p className='text-xl'>{props.err?.message}</p>
</div>
</motion.section>
)
Expand Down
Loading

0 comments on commit d7856ce

Please sign in to comment.