From 2b2fbb994e6ce6257c8365363639f3ab2b4496aa Mon Sep 17 00:00:00 2001 From: TypicalAM Date: Fri, 1 Sep 2023 03:42:55 +0200 Subject: [PATCH] style: propagate errors --- internal/backend/backend.go | 17 +++++--- internal/backend/cache/cache.go | 39 +++++++++--------- internal/backend/cache/read_status.go | 19 ++++----- internal/backend/rss/rss.go | 58 ++++++++++++--------------- internal/theme/theme.go | 23 +++++------ internal/ui/browser/browser.go | 31 +++++++++----- 6 files changed, 98 insertions(+), 89 deletions(-) diff --git a/internal/backend/backend.go b/internal/backend/backend.go index fd333d7..8c3a48b 100644 --- a/internal/backend/backend.go +++ b/internal/backend/backend.go @@ -2,6 +2,7 @@ package backend import ( "errors" + "fmt" "log" "sort" @@ -25,12 +26,12 @@ func New(urlPath, cacheDir string, resetCache bool) (*Backend, error) { log.Println("Creating new backend") store, err := cache.New(cacheDir) if err != nil { - return nil, err + return nil, fmt.Errorf("backend.New: %w", err) } readStatus, err := cache.NewReadStatus(cacheDir) if err != nil { - return nil, err + return nil, fmt.Errorf("backend.New: %w", err) } if !resetCache { @@ -45,7 +46,7 @@ func New(urlPath, cacheDir string, resetCache bool) (*Backend, error) { rss, err := rss.New(urlPath) if err != nil { - return nil, err + return nil, fmt.Errorf("backend.New: %w", err) } if err = rss.Load(); err != nil { @@ -131,14 +132,18 @@ func (b Backend) DownloadItem(feedName string, index int) tea.Cmd { // Close closes the backend and saves its components. func (b Backend) Close() error { if err := b.Rss.Save(); err != nil { - return err + return fmt.Errorf("backend.Close: %w", err) } if err := b.Cache.Save(); err != nil { - return err + return fmt.Errorf("backend.Close: %w", err) } - return b.ReadStatus.Save() + if err := b.ReadStatus.Save(); err != nil { + return fmt.Errorf("backend.Close: %w", err) + } + + return nil } // articlesToSuccessMsg converts a list of items to a FetchArticleSuccessMsg. diff --git a/internal/backend/cache/cache.go b/internal/backend/cache/cache.go index 6348af5..2a52d22 100644 --- a/internal/backend/cache/cache.go +++ b/internal/backend/cache/cache.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "encoding/json" + "errors" "fmt" "log" "net/http" @@ -58,7 +59,7 @@ func New(dir string) (*Cache, error) { if dir == "" { defaultDir, err := getDefaultDir() if err != nil { - return nil, err + return nil, fmt.Errorf("cache.New: %w", err) } dir = defaultDir @@ -74,21 +75,17 @@ func New(dir string) (*Cache, error) { // Load reads the cache from disk func (c *Cache) Load() error { log.Println("Loading cache from", c.filePath) - if _, err := os.Stat(c.filePath); err != nil { + data, err := os.ReadFile(c.filePath) + if err != nil { if os.IsNotExist(err) { return nil } - return err - } - - data, err := os.ReadFile(c.filePath) - if err != nil { - return err + return fmt.Errorf("cache.Load: %w", err) } if err = json.Unmarshal(data, &c); err != nil { - return err + return fmt.Errorf("cache.Load: %w", err) } log.Println("Loaded cache entries: ", len(c.Content)) @@ -106,17 +103,17 @@ func (c *Cache) Save() error { cacheData, err := json.Marshal(c) if err != nil { - return err + return fmt.Errorf("cache.Save: %w", err) } // Try to write the data to the file if err = os.WriteFile(c.filePath, cacheData, 0600); err != nil { if err = os.MkdirAll(filepath.Dir(c.filePath), 0755); err != nil { - return err + return fmt.Errorf("cache.Save: %w", err) } if err = os.WriteFile(c.filePath, cacheData, 0600); err != nil { - return err + return fmt.Errorf("cache.Save: %w", err) } } @@ -137,12 +134,12 @@ func (c *Cache) GetArticles(url string, ignoreCache bool) (SortableArticles, err } if c.OfflineMode { - return nil, fmt.Errorf("offline mode") + return nil, errors.New("offline mode") } articles, err := fetchArticles(url) if err != nil { - return nil, err + return nil, fmt.Errorf("cache.GetArticles: %w", err) } c.Content[url] = Entry{time.Now().Add(DefaultCacheDuration), articles} @@ -180,7 +177,7 @@ func (c *Cache) AddToDownloaded(item gofeed.Item) { // RemoveFromDownloaded removes an item from the downloaded list func (c *Cache) RemoveFromDownloaded(index int) error { if index < 0 || index >= len(c.Downloaded) { - return fmt.Errorf("index out of range") + return errors.New("index out of range") } c.Downloaded = append(c.Downloaded[:index], c.Downloaded[index+1:]...) @@ -192,7 +189,7 @@ func fetchArticles(url string) (SortableArticles, error) { log.Println("Fetching articles from", url) feed, err := parseFeed(url) if err != nil { - return nil, err + return nil, fmt.Errorf("cache.fetchArticles: %w", err) } items := make(SortableArticles, len(feed.Items)) @@ -208,7 +205,7 @@ func fetchArticles(url string) (SortableArticles, error) { func parseFeed(url string) (*gofeed.Feed, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { - return nil, err + return nil, fmt.Errorf("cache.parseFeed: %w", err) } req.Header.Set("User-Agent", "goread (by /u/TypicalAM)") @@ -224,7 +221,7 @@ func parseFeed(url string) (*gofeed.Feed, error) { resp, err := client.Do(req.WithContext(ctx)) if err != nil { - return nil, err + return nil, fmt.Errorf("cache.parseFeed: %w", err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { @@ -236,11 +233,11 @@ func parseFeed(url string) (*gofeed.Feed, error) { feed, err := gofeed.NewParser().Parse(resp.Body) if err != nil { - return nil, err + return nil, fmt.Errorf("cache.parseFeed: %w", err) } if err := resp.Body.Close(); err != nil { - return nil, err + return nil, fmt.Errorf("cache.parseFeed: %w", err) } return feed, nil @@ -250,7 +247,7 @@ func parseFeed(url string) (*gofeed.Feed, error) { func getDefaultDir() (string, error) { dir, err := os.UserCacheDir() if err != nil { - return "", err + return "", fmt.Errorf("cache.getDefaultDir: %w", err) } return filepath.Join(dir, "goread"), nil diff --git a/internal/backend/cache/read_status.go b/internal/backend/cache/read_status.go index da7b7bf..4e74cbf 100644 --- a/internal/backend/cache/read_status.go +++ b/internal/backend/cache/read_status.go @@ -3,6 +3,7 @@ package cache import ( "encoding/binary" "errors" + "fmt" "log" "os" "path/filepath" @@ -24,7 +25,7 @@ func NewReadStatus(dir string) (*ReadStatus, error) { if dir == "" { defaultDir, err := getDefaultDir() if err != nil { - return nil, err + return nil, fmt.Errorf("cache.New: %w", err) } dir = defaultDir @@ -39,21 +40,21 @@ func NewReadStatus(dir string) (*ReadStatus, error) { // Load reads the cache from disk func (rs *ReadStatus) Load() error { log.Println("Loading read status from", rs.filePath) - if _, err := os.Stat(rs.filePath); err != nil { + data, err := os.ReadFile(rs.filePath) + if err != nil { if os.IsNotExist(err) { return nil } - return err + return fmt.Errorf("cache.Load: %w", err) } - data, err := os.ReadFile(rs.filePath) + rs.set, err = unmarshal(data) if err != nil { - return err + return fmt.Errorf("cache.Load: %w", err) } - rs.set, err = unmarshal(data) - return err + return nil } // Save writes the cache to disk @@ -64,11 +65,11 @@ func (rs ReadStatus) Save() error { // Try to write the data to the file if err := os.WriteFile(rs.filePath, data, 0600); err != nil { if err = os.MkdirAll(filepath.Dir(rs.filePath), 0755); err != nil { - return err + return fmt.Errorf("cache.Save: %w", err) } if err = os.WriteFile(rs.filePath, data, 0600); err != nil { - return err + return fmt.Errorf("cache.Save: %w", err) } } diff --git a/internal/backend/rss/rss.go b/internal/backend/rss/rss.go index 5669add..bf787c6 100644 --- a/internal/backend/rss/rss.go +++ b/internal/backend/rss/rss.go @@ -2,6 +2,7 @@ package rss import ( "errors" + "fmt" "log" "os" "path/filepath" @@ -84,7 +85,7 @@ func New(path string) (*Rss, error) { if path == "" { defaultPath, err := getDefaultPath() if err != nil { - return nil, err + return nil, fmt.Errorf("rss.New: %w", err) } // Set the path @@ -99,21 +100,17 @@ func New(path string) (*Rss, error) { // Load will try to load the Rss structure from a file func (rss *Rss) Load() error { log.Println("Loading rss from", rss.filePath) - if _, err := os.Stat(rss.filePath); err != nil { + data, err := os.ReadFile(rss.filePath) + if err != nil { if os.IsNotExist(err) { return nil } - return err - } - - data, err := os.ReadFile(rss.filePath) - if err != nil { - return err + return fmt.Errorf("rss.Load: %w", err) } if err = yaml.Unmarshal(data, rss); err != nil { - return err + return fmt.Errorf("rss.Load: %w", err) } log.Printf("Rss loaded with %d categories\n", len(rss.Categories)) @@ -124,16 +121,16 @@ func (rss *Rss) Load() error { func (rss Rss) Save() error { yamlData, err := yaml.Marshal(rss) if err != nil { - return err + return fmt.Errorf("rss.Save: %w", err) } if err = os.WriteFile(rss.filePath, yamlData, 0600); err != nil { if err = os.MkdirAll(filepath.Dir(rss.filePath), 0755); err != nil { - return err + return fmt.Errorf("rss.Save: %w", err) } if err = os.WriteFile(rss.filePath, yamlData, 0600); err != nil { - return err + return fmt.Errorf("rss.Save: %w", err) } } @@ -229,16 +226,11 @@ func YassifyItem(item *gofeed.Item) string { // HTMLToMarkdown converts html to markdown using the html-to-markdown library func HTMLToMarkdown(content string) (string, error) { - // Create a new converter - converter := md.NewConverter("", true, nil) - - // Convert the html to markdown - markdown, err := converter.ConvertString(content) + markdown, err := md.NewConverter("", true, nil).ConvertString(content) if err != nil { - return "", err + return "", fmt.Errorf("HTMLToMarkdown: %w", err) } - // Return the markdown return markdown, nil } @@ -246,7 +238,7 @@ func HTMLToMarkdown(content string) (string, error) { func (rss *Rss) LoadOPML(path string) error { parsed, err := opml.NewOPMLFromFile(path) if err != nil { - return err + return fmt.Errorf("rss.LoadOPML: %w", err) } for _, o := range parsed.Outlines() { @@ -258,13 +250,13 @@ func (rss *Rss) LoadOPML(path string) error { catDesc = o.Text } - if err = rss.AddCategory(catName, catDesc); err != nil && err != ErrAlreadyExists { - return err + if err = rss.AddCategory(catName, catDesc); err != nil && !errors.Is(err, ErrAlreadyExists) { + return fmt.Errorf("rss.LoadOPML: %w", err) } if len(o.Outlines) == 0 { - if err = rss.AddFeed(DefaultCategoryName, o.Title, o.XMLURL); err != nil && err != ErrAlreadyExists { - return err + if err = rss.AddFeed(DefaultCategoryName, o.Title, o.XMLURL); err != nil && !errors.Is(err, ErrAlreadyExists) { + return fmt.Errorf("rss.LoadOPML: %w", err) } continue @@ -272,8 +264,8 @@ func (rss *Rss) LoadOPML(path string) error { for _, so := range o.Outlines { log.Println("Adding feed:", so.Title) - if err = rss.AddFeed(catName, so.Title, so.XMLURL); err != nil && err != ErrAlreadyExists { - return err + if err = rss.AddFeed(catName, so.Title, so.XMLURL); err != nil && !errors.Is(err, ErrAlreadyExists) { + return fmt.Errorf("rss.LoadOPML: %w", err) } } } @@ -308,21 +300,23 @@ func (rss *Rss) ExportOPML(path string) error { data, err := result.XML() if err != nil { - return err + return fmt.Errorf("rss.ExportOPML: %w", err) } - return os.WriteFile(path, []byte(data), 0600) + if err = os.WriteFile(path, []byte(data), 0600); err != nil { + return fmt.Errorf("rss.ExportOPML: %w", err) + } + + return nil } // HTMLToText converts html to text using the goquery library func HTMLToText(content string) (string, error) { - // Create a new document doc, err := goquery.NewDocumentFromReader(strings.NewReader(content)) if err != nil { - return "", err + return "", fmt.Errorf("rss.HTMLToText: %w", err) } - // Return the text return doc.Text(), nil } @@ -330,7 +324,7 @@ func HTMLToText(content string) (string, error) { func getDefaultPath() (string, error) { configDir, err := os.UserConfigDir() if err != nil { - return "", err + return "", fmt.Errorf("rss.getDefaultPath: %w", err) } return filepath.Join(configDir, "goread", "urls.yml"), nil diff --git a/internal/theme/theme.go b/internal/theme/theme.go index e7794fb..27fc465 100644 --- a/internal/theme/theme.go +++ b/internal/theme/theme.go @@ -52,7 +52,7 @@ func New(path string) (*Colors, error) { if path == "" { defaultPath, err := getDefaultPath() if err != nil { - return nil, err + return nil, fmt.Errorf("theme.New: %w", err) } path = defaultPath @@ -68,11 +68,11 @@ func New(path string) (*Colors, error) { func (c *Colors) Load() error { fileContent, err := os.ReadFile(c.FilePath) if err != nil { - return err + return fmt.Errorf("theme.Load: %w", err) } if err = json.Unmarshal(fileContent, c); err != nil { - return err + return fmt.Errorf("theme.Load: %w", err) } c.genMarkdownStyle() @@ -83,16 +83,16 @@ func (c *Colors) Load() error { func (c Colors) Save() error { jsonData, err := json.MarshalIndent(c, "", " ") if err != nil { - return err + return fmt.Errorf("theme.Save: %w", err) } if err = os.WriteFile(c.FilePath, jsonData, 0600); err != nil { if err = os.MkdirAll(filepath.Dir(c.FilePath), 0755); err != nil { - return err + return fmt.Errorf("theme.Save: %w", err) } if err = os.WriteFile(c.FilePath, jsonData, 0600); err != nil { - return err + return fmt.Errorf("theme.Save: %w", err) } } @@ -104,7 +104,7 @@ func (c *Colors) Convert(pywalFilePath string) error { if pywalFilePath == "" { cacheDir, err := os.UserCacheDir() if err != nil { - return err + return fmt.Errorf("theme.Convert: %w", err) } pywalFilePath = filepath.Join(cacheDir, "wal", "colors.json") @@ -112,13 +112,12 @@ func (c *Colors) Convert(pywalFilePath string) error { fileContent, err := os.ReadFile(pywalFilePath) if err != nil { - return err + return fmt.Errorf("theme.Convert: %w", err) } var walColorscheme map[string]interface{} - err = json.Unmarshal(fileContent, &walColorscheme) - if err != nil { - return err + if err = json.Unmarshal(fileContent, &walColorscheme); err != nil { + return fmt.Errorf("theme.Convert: %w", err) } // Set the colors @@ -160,7 +159,7 @@ func getDefaultPath() (string, error) { // Get the default config path configDir, err := os.UserConfigDir() if err != nil { - return "", err + return "", fmt.Errorf("theme.getDefaultPath: %w", err) } // Create the config path diff --git a/internal/ui/browser/browser.go b/internal/ui/browser/browser.go index 3755d34..437e6e7 100644 --- a/internal/ui/browser/browser.go +++ b/internal/ui/browser/browser.go @@ -1,6 +1,7 @@ package browser import ( + "errors" "fmt" "log" "strconv" @@ -114,7 +115,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { log.Printf("Error fetching data in tab %d: %v \n", m.activeTab, msg.Err) updated, _ := m.tabs[m.activeTab].Update(msg) m.tabs[m.activeTab] = updated.(tab.Tab) - m.msg = fmt.Sprintf("%s: %s", msg.Description, msg.Err.Error()) + m.msg = fmt.Sprintf("%s: %s", msg.Description, unwrapErrs(msg.Err)) return m, nil case overview.ChosenCategoryMsg: @@ -123,13 +124,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.IsEdit { if err := m.backend.Rss.UpdateCategory(msg.OldName, msg.Name, msg.Desc); err != nil { - m.msg = fmt.Sprintf("Error updating category: %s", err.Error()) + m.msg = fmt.Sprintf("Error updating category: %s", unwrapErrs(err)) } else { m.msg = fmt.Sprintf("Updated category %s", msg.Name) } } else { if err := m.backend.Rss.AddCategory(msg.Name, msg.Desc); err != nil { - m.msg = fmt.Sprintf("Error adding category: %s", err.Error()) + m.msg = fmt.Sprintf("Error adding category: %s", unwrapErrs(err)) } else { m.msg = fmt.Sprintf("Added category %s", msg.Name) } @@ -144,13 +145,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.IsEdit { if err := m.backend.Rss.UpdateFeed(msg.Parent, msg.OldName, msg.Name, msg.URL); err != nil { - m.msg = fmt.Sprintf("Error updating feed: %s", err.Error()) + m.msg = fmt.Sprintf("Error updating feed: %s", unwrapErrs(err)) } else { m.msg = fmt.Sprintf("Updated feed %s", msg.Name) } } else { if err := m.backend.Rss.AddFeed(msg.Parent, msg.Name, msg.URL); err != nil { - m.msg = fmt.Sprintf("Error adding feed: %s", err.Error()) + m.msg = fmt.Sprintf("Error adding feed: %s", unwrapErrs(err)) } else { m.msg = fmt.Sprintf("Added feed %s", msg.Name) } @@ -411,13 +412,13 @@ func (m Model) deleteItem(msg backend.DeleteItemMsg) (tea.Model, tea.Cmd) { case overview.Model: cmd = m.backend.FetchCategories("") if err := m.backend.Rss.RemoveCategory(msg.ItemName); err != nil { - m.msg = fmt.Sprintf("Error deleting category %s: %s", msg.ItemName, err.Error()) + m.msg = fmt.Sprintf("Error deleting category %s: %s", msg.ItemName, unwrapErrs(err)) } case category.Model: cmd = m.backend.FetchFeeds(m.tabs[m.activeTab].Title()) if err := m.backend.Rss.RemoveFeed(m.tabs[m.activeTab].Title(), msg.ItemName); err != nil { - m.msg = fmt.Sprintf("Error deleting feed %s: %s", msg.ItemName, err.Error()) + m.msg = fmt.Sprintf("Error deleting feed %s: %s", msg.ItemName, unwrapErrs(err)) } case feed.Model: @@ -425,11 +426,11 @@ func (m Model) deleteItem(msg backend.DeleteItemMsg) (tea.Model, tea.Cmd) { if msg.Sender.Title() == rss.DownloadedFeedsName { index, err := strconv.Atoi(msg.ItemName) if err != nil { - m.msg = fmt.Sprintf("Error deleting download %s: %s", msg.ItemName, err.Error()) + m.msg = fmt.Sprintf("Error deleting download %s: %s", msg.ItemName, unwrapErrs(err)) } if err := m.backend.Cache.RemoveFromDownloaded(index); err != nil { - m.msg = fmt.Sprintf("Error deleting download %s: %s", msg.ItemName, err.Error()) + m.msg = fmt.Sprintf("Error deleting download %s: %s", msg.ItemName, unwrapErrs(err)) } } } @@ -509,3 +510,15 @@ func (m Model) renderStatusBar() string { gap := m.style.statusBarGap.Render(strings.Repeat(" ", gapAmount)) return lipgloss.JoinHorizontal(lipgloss.Bottom, row, gap) } + +// unwrapErrs unwraps all errors in a chain of wrapped errors for use in a status message +func unwrapErrs(err error) error { + for { + unwrapErr := errors.Unwrap(err) + if unwrapErr == nil { + break + } + err = unwrapErr + } + return err +}