Use go_errors library everywhere.
This commit is contained in:
@@ -8,10 +8,10 @@ import (
|
||||
"gemini-grc/common/snapshot"
|
||||
_url "gemini-grc/common/url"
|
||||
"gemini-grc/config"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/gemini"
|
||||
"gemini-grc/gopher"
|
||||
"gemini-grc/logging"
|
||||
"github.com/antanst/go_errors"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -26,7 +26,7 @@ func main() {
|
||||
|
||||
func runApp() error {
|
||||
if len(os.Args) != 2 {
|
||||
return errors.NewError(fmt.Errorf("missing URL to visit"))
|
||||
return go_errors.NewError(fmt.Errorf("missing URL to visit"))
|
||||
}
|
||||
url := os.Args[1]
|
||||
var s *snapshot.Snapshot
|
||||
@@ -36,7 +36,7 @@ func runApp() error {
|
||||
} else if _url.IsGopherURL(url) {
|
||||
s, err = gopher.Visit(url)
|
||||
} else {
|
||||
return errors.NewFatalError(fmt.Errorf("not a Gemini or Gopher URL"))
|
||||
return go_errors.NewFatalError(fmt.Errorf("not a Gemini or Gopher URL"))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"gemini-grc/config"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/logging"
|
||||
"github.com/antanst/go_errors"
|
||||
)
|
||||
|
||||
var Blacklist []regexp.Regexp //nolint:gochecknoglobals
|
||||
@@ -21,7 +21,7 @@ func LoadBlacklist() error {
|
||||
data, err := os.ReadFile(config.CONFIG.BlacklistPath)
|
||||
if err != nil {
|
||||
Blacklist = []regexp.Regexp{}
|
||||
return errors.NewError(fmt.Errorf("could not load Blacklist file: %w", err))
|
||||
return go_errors.NewError(fmt.Errorf("could not load Blacklist file: %w", err))
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
@@ -32,7 +32,7 @@ func LoadBlacklist() error {
|
||||
}
|
||||
regex, err := regexp.Compile(line)
|
||||
if err != nil {
|
||||
return errors.NewError(fmt.Errorf("could not compile Blacklist line %s: %w", line, err))
|
||||
return go_errors.NewError(fmt.Errorf("could not compile Blacklist line %s: %w", line, err))
|
||||
}
|
||||
Blacklist = append(Blacklist, *regex)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package errors
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gemini-grc/errors"
|
||||
"github.com/antanst/go_errors"
|
||||
)
|
||||
|
||||
// HostError is an error encountered while
|
||||
@@ -30,7 +30,7 @@ func IsHostError(err error) bool {
|
||||
return false
|
||||
}
|
||||
var asError *HostError
|
||||
return errors.As(err, &asError)
|
||||
return go_errors.As(err, &asError)
|
||||
}
|
||||
|
||||
// Sentinel errors used for their string message primarily.
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"gemini-grc/common/linkList"
|
||||
commonUrl "gemini-grc/common/url"
|
||||
"gemini-grc/errors"
|
||||
"github.com/antanst/go_errors"
|
||||
"github.com/guregu/null/v5"
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ type Snapshot struct {
|
||||
func SnapshotFromURL(u string, normalize bool) (*Snapshot, error) {
|
||||
url, err := commonUrl.ParseURL(u, "", normalize)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(err)
|
||||
return nil, go_errors.NewError(err)
|
||||
}
|
||||
newSnapshot := Snapshot{
|
||||
URL: *url,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gemini-grc/errors"
|
||||
"github.com/antanst/go_errors"
|
||||
)
|
||||
|
||||
type URL struct {
|
||||
@@ -29,7 +29,7 @@ func (u *URL) Scan(value interface{}) error {
|
||||
}
|
||||
b, ok := value.(string)
|
||||
if !ok {
|
||||
return errors.NewFatalError(fmt.Errorf("database scan error: expected string, got %T", value))
|
||||
return go_errors.NewFatalError(fmt.Errorf("database scan error: expected string, got %T", value))
|
||||
}
|
||||
parsedURL, err := ParseURL(b, "", false)
|
||||
if err != nil {
|
||||
@@ -82,7 +82,7 @@ func ParseURL(input string, descr string, normalize bool) (*URL, error) {
|
||||
} else {
|
||||
u, err = url.Parse(input)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(fmt.Errorf("error parsing URL: %w: %s", err, input))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error parsing URL: %w: %s", err, input))
|
||||
}
|
||||
}
|
||||
protocol := u.Scheme
|
||||
@@ -99,7 +99,7 @@ func ParseURL(input string, descr string, normalize bool) (*URL, error) {
|
||||
}
|
||||
port, err := strconv.Atoi(strPort)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(fmt.Errorf("error parsing URL: %w: %s", err, input))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error parsing URL: %w: %s", err, input))
|
||||
}
|
||||
full := fmt.Sprintf("%s://%s:%d%s", protocol, hostname, port, urlPath)
|
||||
// full field should also contain query params and url fragments
|
||||
@@ -145,13 +145,13 @@ func NormalizeURL(rawURL string) (*url.URL, error) {
|
||||
// Parse the URL
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(fmt.Errorf("error normalizing URL: %w: %s", err, rawURL))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error normalizing URL: %w: %s", err, rawURL))
|
||||
}
|
||||
if u.Scheme == "" {
|
||||
return nil, errors.NewError(fmt.Errorf("error normalizing URL: No scheme: %s", rawURL))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error normalizing URL: No scheme: %s", rawURL))
|
||||
}
|
||||
if u.Host == "" {
|
||||
return nil, errors.NewError(fmt.Errorf("error normalizing URL: No host: %s", rawURL))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error normalizing URL: No host: %s", rawURL))
|
||||
}
|
||||
|
||||
// Convert scheme to lowercase
|
||||
@@ -275,7 +275,7 @@ func ExtractRedirectTargetFromHeader(currentURL URL, input string) (*URL, error)
|
||||
re := regexp.MustCompile(pattern)
|
||||
matches := re.FindStringSubmatch(input)
|
||||
if len(matches) < 2 {
|
||||
return nil, errors.NewError(fmt.Errorf("error extracting redirect target from string %s", input))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error extracting redirect target from string %s", input))
|
||||
}
|
||||
newURL, err := DeriveAbsoluteURL(currentURL, matches[1])
|
||||
if err != nil {
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"gemini-grc/common/snapshot"
|
||||
url2 "gemini-grc/common/url"
|
||||
_db "gemini-grc/db"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/gemini"
|
||||
"gemini-grc/gopher"
|
||||
"gemini-grc/hostPool"
|
||||
"gemini-grc/logging"
|
||||
"github.com/antanst/go_errors"
|
||||
"github.com/guregu/null/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
@@ -25,12 +25,12 @@ func CrawlOneURL(db *sqlx.DB, url *string) error {
|
||||
}
|
||||
|
||||
if !url2.IsGeminiUrl(parsedURL.String()) && !url2.IsGopherURL(parsedURL.String()) {
|
||||
return errors.NewError(fmt.Errorf("error parsing URL: not a Gemini or Gopher URL: %s", parsedURL.String()))
|
||||
return go_errors.NewError(fmt.Errorf("error parsing URL: not a Gemini or Gopher URL: %s", parsedURL.String()))
|
||||
}
|
||||
|
||||
tx, err := db.Beginx()
|
||||
if err != nil {
|
||||
return errors.NewFatalError(err)
|
||||
return go_errors.NewFatalError(err)
|
||||
}
|
||||
|
||||
err = _db.InsertURL(tx, parsedURL.Full)
|
||||
@@ -49,9 +49,9 @@ func CrawlOneURL(db *sqlx.DB, url *string) error {
|
||||
// logging.LogError("Deadlock detected. Rolling back")
|
||||
// time.Sleep(time.Duration(10) * time.Second)
|
||||
// err := tx.Rollback()
|
||||
// return errors.NewFatalError(err)
|
||||
// return go_errors.NewFatalError(err)
|
||||
//}
|
||||
return errors.NewFatalError(err)
|
||||
return go_errors.NewFatalError(err)
|
||||
}
|
||||
logging.LogInfo("Done")
|
||||
return nil
|
||||
@@ -156,7 +156,7 @@ func workOnUrl(workerID int, tx *sqlx.Tx, url string) (err error) {
|
||||
isGemini := url2.IsGeminiUrl(s.URL.String())
|
||||
isGopher := url2.IsGopherURL(s.URL.String())
|
||||
if !isGemini && !isGopher {
|
||||
return errors.NewError(fmt.Errorf("not a Gopher or Gemini URL: %s", s.URL.String()))
|
||||
return go_errors.NewError(fmt.Errorf("not a Gopher or Gemini URL: %s", s.URL.String()))
|
||||
}
|
||||
|
||||
if blackList.IsBlacklisted(s.URL.String()) {
|
||||
@@ -270,14 +270,14 @@ func haveWeVisitedURL(tx *sqlx.Tx, u string) (bool, error) {
|
||||
var result []bool
|
||||
err := tx.Select(&result, `SELECT TRUE FROM urls WHERE url=$1`, u)
|
||||
if err != nil {
|
||||
return false, errors.NewFatalError(fmt.Errorf("database error: %w", err))
|
||||
return false, go_errors.NewFatalError(fmt.Errorf("database error: %w", err))
|
||||
}
|
||||
if len(result) > 0 {
|
||||
return result[0], nil
|
||||
}
|
||||
err = tx.Select(&result, `SELECT TRUE FROM snapshots WHERE snapshots.url=$1`, u)
|
||||
if err != nil {
|
||||
return false, errors.NewFatalError(fmt.Errorf("database error: %w", err))
|
||||
return false, go_errors.NewFatalError(fmt.Errorf("database error: %w", err))
|
||||
}
|
||||
if len(result) > 0 {
|
||||
return result[0], nil
|
||||
|
||||
28
db/db.go
28
db/db.go
@@ -10,8 +10,8 @@ import (
|
||||
"gemini-grc/common/snapshot"
|
||||
commonUrl "gemini-grc/common/url"
|
||||
"gemini-grc/config"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/logging"
|
||||
"github.com/antanst/go_errors"
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // PGX driver for PostgreSQL
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
@@ -29,17 +29,17 @@ func ConnectToDB() (*sqlx.DB, error) {
|
||||
// Create a connection pool
|
||||
db, err := sqlx.Open("pgx", connStr)
|
||||
if err != nil {
|
||||
return nil, errors.NewFatalError(fmt.Errorf("unable to connect to database with URL %s: %w", connStr, err))
|
||||
return nil, go_errors.NewFatalError(fmt.Errorf("unable to connect to database with URL %s: %w", connStr, err))
|
||||
}
|
||||
// TODO move PG_MAX_OPEN_CONNECTIONS to config env variables
|
||||
maxConnections, err := strconv.Atoi(os.Getenv("PG_MAX_OPEN_CONNECTIONS"))
|
||||
if err != nil {
|
||||
return nil, errors.NewFatalError(fmt.Errorf("unable to set DB max connections: %w", err))
|
||||
return nil, go_errors.NewFatalError(fmt.Errorf("unable to set DB max connections: %w", err))
|
||||
}
|
||||
db.SetMaxOpenConns(maxConnections)
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return nil, errors.NewFatalError(fmt.Errorf("unable to ping database: %w", err))
|
||||
return nil, go_errors.NewFatalError(fmt.Errorf("unable to ping database: %w", err))
|
||||
}
|
||||
|
||||
logging.LogDebug("Connected to database")
|
||||
@@ -48,9 +48,9 @@ func ConnectToDB() (*sqlx.DB, error) {
|
||||
|
||||
// IsDeadlockError checks if the error is a PostgreSQL deadlock error.
|
||||
func IsDeadlockError(err error) bool {
|
||||
err = errors.Unwrap(err)
|
||||
err = go_errors.Unwrap(err)
|
||||
var pqErr *pq.Error
|
||||
if errors.As(err, &pqErr) {
|
||||
if go_errors.As(err, &pqErr) {
|
||||
return pqErr.Code == "40P01" // PostgreSQL deadlock error code
|
||||
}
|
||||
return false
|
||||
@@ -60,7 +60,7 @@ func GetRandomUrls(tx *sqlx.Tx) ([]string, error) {
|
||||
var urls []string
|
||||
err := tx.Select(&urls, SQL_SELECT_RANDOM_URLS, config.CONFIG.WorkerBatchSize)
|
||||
if err != nil {
|
||||
return nil, errors.NewFatalError(err)
|
||||
return nil, go_errors.NewFatalError(err)
|
||||
}
|
||||
return urls, nil
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func GetRandomUrlsWithBasePath(tx *sqlx.Tx) ([]string, error) {
|
||||
var urls []string
|
||||
err := tx.Select(&urls, SqlQuery, config.CONFIG.WorkerBatchSize)
|
||||
if err != nil {
|
||||
return nil, errors.NewFatalError(err)
|
||||
return nil, go_errors.NewFatalError(err)
|
||||
}
|
||||
return urls, nil
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func InsertURL(tx *sqlx.Tx, url string) error {
|
||||
}
|
||||
_, err = tx.NamedExec(query, a)
|
||||
if err != nil {
|
||||
return errors.NewFatalError(fmt.Errorf("cannot insert URL: database error %w URL %s", err, url))
|
||||
return go_errors.NewFatalError(fmt.Errorf("cannot insert URL: database error %w URL %s", err, url))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func DeleteURL(tx *sqlx.Tx, url string) error {
|
||||
query := SQL_DELETE_URL
|
||||
_, err := tx.Exec(query, url)
|
||||
if err != nil {
|
||||
return errors.NewFatalError(fmt.Errorf("cannot delete URL: database error %w URL %s", err, url))
|
||||
return go_errors.NewFatalError(fmt.Errorf("cannot delete URL: database error %w URL %s", err, url))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func OverwriteSnapshot(tx *sqlx.Tx, s *snapshot.Snapshot) (err error) {
|
||||
if config.CONFIG.DryRun {
|
||||
marshalled, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
return errors.NewFatalError(fmt.Errorf("JSON serialization error for %v", s))
|
||||
return go_errors.NewFatalError(fmt.Errorf("JSON serialization error for %v", s))
|
||||
}
|
||||
logging.LogDebug("Would upsert snapshot %s", marshalled)
|
||||
return nil
|
||||
@@ -120,19 +120,19 @@ func OverwriteSnapshot(tx *sqlx.Tx, s *snapshot.Snapshot) (err error) {
|
||||
query := SQL_UPSERT_SNAPSHOT
|
||||
rows, err := tx.NamedQuery(query, s)
|
||||
if err != nil {
|
||||
return errors.NewFatalError(fmt.Errorf("cannot overwrite snapshot: %w", err))
|
||||
return go_errors.NewFatalError(fmt.Errorf("cannot overwrite snapshot: %w", err))
|
||||
}
|
||||
defer func() {
|
||||
_err := rows.Close()
|
||||
if err == nil && _err != nil {
|
||||
err = errors.NewFatalError(fmt.Errorf("cannot overwrite snapshot: error closing rows: %w", err))
|
||||
err = go_errors.NewFatalError(fmt.Errorf("cannot overwrite snapshot: error closing rows: %w", err))
|
||||
}
|
||||
}()
|
||||
if rows.Next() {
|
||||
var returnedID int
|
||||
err = rows.Scan(&returnedID)
|
||||
if err != nil {
|
||||
return errors.NewFatalError(fmt.Errorf("cannot overwrite snapshot: error scanning rows: %w", err))
|
||||
return go_errors.NewFatalError(fmt.Errorf("cannot overwrite snapshot: error scanning rows: %w", err))
|
||||
}
|
||||
s.ID = returnedID
|
||||
}
|
||||
|
||||
104
errors/errors.go
104
errors/errors.go
@@ -1,104 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fatal interface {
|
||||
Fatal() bool
|
||||
}
|
||||
|
||||
func IsFatal(err error) bool {
|
||||
var e fatal
|
||||
ok := As(err, &e)
|
||||
return ok && e.Fatal()
|
||||
}
|
||||
|
||||
func As(err error, target any) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
func Is(err, target error) bool {
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
func Unwrap(err error) error {
|
||||
return errors.Unwrap(err)
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Err error
|
||||
Stack string
|
||||
fatal bool
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("%v\n", e.Err))
|
||||
sb.WriteString(fmt.Sprintf("Stack Trace:\n%s", e.Stack))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (e *Error) Fatal() bool {
|
||||
return e.fatal
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func NewError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if it's already of our own
|
||||
// Error type, so we don't add stack twice.
|
||||
var asError *Error
|
||||
if errors.As(err, &asError) {
|
||||
return err
|
||||
}
|
||||
|
||||
// has the stack trace
|
||||
var stack strings.Builder
|
||||
buf := make([]uintptr, 50)
|
||||
n := runtime.Callers(2, buf)
|
||||
frames := runtime.CallersFrames(buf[:n])
|
||||
|
||||
// Format the stack trace
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
// Skip runtime and standard library frames
|
||||
if !strings.Contains(frame.File, "runtime/") {
|
||||
stack.WriteString(fmt.Sprintf("\t%s:%d - %s\n", frame.File, frame.Line, frame.Function))
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &Error{
|
||||
Err: err,
|
||||
Stack: stack.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFatalError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if it's already of our own
|
||||
// Error type.
|
||||
var asError *Error
|
||||
if errors.As(err, &asError) {
|
||||
asError.fatal = true // Set fatal even for existing Error types
|
||||
return err
|
||||
}
|
||||
err2 := NewError(err)
|
||||
err2.(*Error).fatal = true
|
||||
return err2
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type CustomError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *CustomError) Error() string { return e.Err.Error() }
|
||||
|
||||
func IsCustomError(err error) bool {
|
||||
var asError *CustomError
|
||||
return errors.As(err, &asError)
|
||||
}
|
||||
|
||||
func TestWrapping(t *testing.T) {
|
||||
t.Parallel()
|
||||
originalErr := errors.New("original error")
|
||||
err1 := NewError(originalErr)
|
||||
if !errors.Is(err1, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
if !Is(err1, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
unwrappedErr := errors.Unwrap(err1)
|
||||
if !errors.Is(unwrappedErr, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
if !Is(unwrappedErr, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
unwrappedErr = Unwrap(err1)
|
||||
if !errors.Is(unwrappedErr, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
if !Is(unwrappedErr, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
wrappedErr := fmt.Errorf("wrapped: %w", originalErr)
|
||||
if !errors.Is(wrappedErr, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
if !Is(wrappedErr, originalErr) {
|
||||
t.Errorf("original error is not wrapped")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewError(t *testing.T) {
|
||||
t.Parallel()
|
||||
originalErr := &CustomError{errors.New("err1")}
|
||||
if !IsCustomError(originalErr) {
|
||||
t.Errorf("TestNewError fail #1")
|
||||
}
|
||||
err1 := NewError(originalErr)
|
||||
if !IsCustomError(err1) {
|
||||
t.Errorf("TestNewError fail #2")
|
||||
}
|
||||
wrappedErr1 := fmt.Errorf("wrapped %w", err1)
|
||||
if !IsCustomError(wrappedErr1) {
|
||||
t.Errorf("TestNewError fail #3")
|
||||
}
|
||||
unwrappedErr1 := Unwrap(wrappedErr1)
|
||||
if !IsCustomError(unwrappedErr1) {
|
||||
t.Errorf("TestNewError fail #4")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFatal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil error",
|
||||
err: nil,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "simple non-fatal error",
|
||||
err: fmt.Errorf("regular error"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "direct fatal error",
|
||||
err: NewFatalError(fmt.Errorf("fatal error")),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-fatal Error type",
|
||||
err: NewError(fmt.Errorf("non-fatal error")),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "wrapped fatal error - one level",
|
||||
err: fmt.Errorf("outer: %w", NewFatalError(fmt.Errorf("inner fatal"))),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "wrapped fatal error - two levels",
|
||||
err: fmt.Errorf("outer: %w",
|
||||
fmt.Errorf("middle: %w",
|
||||
NewFatalError(fmt.Errorf("inner fatal")))),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "wrapped fatal error - three levels",
|
||||
err: fmt.Errorf("outer: %w",
|
||||
fmt.Errorf("middle1: %w",
|
||||
fmt.Errorf("middle2: %w",
|
||||
NewFatalError(fmt.Errorf("inner fatal"))))),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple wrapped errors - non-fatal",
|
||||
err: fmt.Errorf("outer: %w",
|
||||
fmt.Errorf("middle: %w",
|
||||
fmt.Errorf("inner: %w",
|
||||
NewError(fmt.Errorf("base error"))))),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "wrapped non-fatal Error type",
|
||||
err: fmt.Errorf("outer: %w", NewError(fmt.Errorf("inner"))),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "wrapped basic error",
|
||||
err: fmt.Errorf("outer: %w", fmt.Errorf("inner")),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "fatal error wrapping fatal error",
|
||||
err: NewFatalError(NewFatalError(fmt.Errorf("double fatal"))),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "fatal error wrapping non-fatal Error",
|
||||
err: NewFatalError(NewError(fmt.Errorf("mixed"))),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-fatal Error wrapping fatal error",
|
||||
err: NewError(NewFatalError(fmt.Errorf("mixed"))),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Error wrapping Error",
|
||||
err: NewError(NewError(fmt.Errorf("double wrapped"))),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "wrapped nil error",
|
||||
err: fmt.Errorf("outer: %w", nil),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "fatal wrapping nil",
|
||||
err: NewFatalError(nil),
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := IsFatal(tt.err)
|
||||
if got != tt.want {
|
||||
t.Errorf("IsFatal() = %v, want %v", got, tt.want)
|
||||
if tt.err != nil {
|
||||
t.Errorf("Error was: %v", tt.err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package gemini
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gemini-grc/errors"
|
||||
"github.com/antanst/go_errors"
|
||||
)
|
||||
|
||||
// GeminiError is used to represent
|
||||
@@ -48,5 +48,5 @@ func IsGeminiError(err error) bool {
|
||||
return false
|
||||
}
|
||||
var asError *GeminiError
|
||||
return errors.As(err, &asError)
|
||||
return go_errors.As(err, &asError)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"gemini-grc/common/linkList"
|
||||
url2 "gemini-grc/common/url"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/logging"
|
||||
"gemini-grc/util"
|
||||
"github.com/antanst/go_errors"
|
||||
)
|
||||
|
||||
func GetPageLinks(currentURL url2.URL, gemtext string) linkList.LinkList {
|
||||
@@ -37,14 +37,14 @@ func ParseGeminiLinkLine(linkLine string, currentURL string) (*url2.URL, error)
|
||||
// Check: currentURL is parseable
|
||||
baseURL, err := url.Parse(currentURL)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
}
|
||||
|
||||
// Extract the actual URL and the description
|
||||
re := regexp.MustCompile(`^=>[ \t]+(\S+)([ \t]+.*)?`)
|
||||
matches := re.FindStringSubmatch(linkLine)
|
||||
if len(matches) == 0 {
|
||||
return nil, errors.NewError(fmt.Errorf("error parsing link line: no regexp match for line %s", linkLine))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error parsing link line: no regexp match for line %s", linkLine))
|
||||
}
|
||||
|
||||
originalURLStr := matches[1]
|
||||
@@ -52,7 +52,7 @@ func ParseGeminiLinkLine(linkLine string, currentURL string) (*url2.URL, error)
|
||||
// Check: Unescape the URL if escaped
|
||||
_, err = url.QueryUnescape(originalURLStr)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
}
|
||||
|
||||
description := ""
|
||||
@@ -63,7 +63,7 @@ func ParseGeminiLinkLine(linkLine string, currentURL string) (*url2.URL, error)
|
||||
// Parse the URL from the link line
|
||||
parsedURL, err := url.Parse(originalURLStr)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
}
|
||||
|
||||
// If link URL is relative, resolve full URL
|
||||
@@ -80,7 +80,7 @@ func ParseGeminiLinkLine(linkLine string, currentURL string) (*url2.URL, error)
|
||||
|
||||
finalURL, err := url2.ParseURL(parsedURL.String(), description, true)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
return nil, go_errors.NewError(fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine))
|
||||
}
|
||||
|
||||
return finalURL, nil
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"gemini-grc/common/snapshot"
|
||||
_url "gemini-grc/common/url"
|
||||
"gemini-grc/config"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/logging"
|
||||
"github.com/antanst/go_errors"
|
||||
"github.com/guregu/null/v5"
|
||||
)
|
||||
|
||||
@@ -41,8 +41,8 @@ func Visit(url string) (s *snapshot.Snapshot, err error) {
|
||||
err = nil
|
||||
} else if IsGeminiError(err) {
|
||||
s.Error = null.StringFrom(err.Error())
|
||||
s.Header = null.StringFrom(errors.Unwrap(err).(*GeminiError).Header)
|
||||
s.ResponseCode = null.IntFrom(int64(errors.Unwrap(err).(*GeminiError).Code))
|
||||
s.Header = null.StringFrom(go_errors.Unwrap(err).(*GeminiError).Header)
|
||||
s.ResponseCode = null.IntFrom(int64(go_errors.Unwrap(err).(*GeminiError).Code))
|
||||
err = nil
|
||||
} else {
|
||||
s = nil
|
||||
@@ -73,7 +73,7 @@ func Visit(url string) (s *snapshot.Snapshot, err error) {
|
||||
func ConnectAndGetData(url string) ([]byte, error) {
|
||||
parsedURL, err := stdurl.Parse(url)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(err)
|
||||
return nil, go_errors.NewError(err)
|
||||
}
|
||||
hostname := parsedURL.Hostname()
|
||||
port := parsedURL.Port()
|
||||
@@ -148,7 +148,7 @@ func ConnectAndGetData(url string) ([]byte, error) {
|
||||
return nil, errors2.NewHostError(err)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
if go_errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, errors2.NewHostError(err)
|
||||
@@ -182,7 +182,7 @@ func processData(s snapshot.Snapshot, data []byte) (*snapshot.Snapshot, error) {
|
||||
if mimeType == "text/gemini" {
|
||||
validBody, err := BytesToValidUTF8(body)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(err)
|
||||
return nil, go_errors.NewError(err)
|
||||
}
|
||||
s.GemText = null.StringFrom(validBody)
|
||||
} else {
|
||||
|
||||
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module gemini-grc
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/antanst/go_errors v0.0.1
|
||||
github.com/guregu/null/v5 v5.0.0
|
||||
github.com/jackc/pgx/v5 v5.7.2
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,5 +1,7 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/antanst/go_errors v0.0.1 h1:55BJ8I3u9IeLJxVslbI8Hv8fM0+fWyIE2VQXuwuYg9Y=
|
||||
github.com/antanst/go_errors v0.0.1/go.mod h1:VDiDlRB7JfRhr6GMqdChBGT1XTBIfzELhg3Yq7sVwhM=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package gopher
|
||||
|
||||
import (
|
||||
"gemini-grc/errors"
|
||||
)
|
||||
import "github.com/antanst/go_errors"
|
||||
|
||||
// GopherError is an error encountered while
|
||||
// visiting a Gopher host, and is only for
|
||||
@@ -28,5 +26,5 @@ func IsGopherError(err error) bool {
|
||||
return false
|
||||
}
|
||||
var asError *GopherError
|
||||
return errors.As(err, &asError)
|
||||
return go_errors.As(err, &asError)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"gemini-grc/common/snapshot"
|
||||
_url "gemini-grc/common/url"
|
||||
"gemini-grc/config"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/logging"
|
||||
"github.com/antanst/go_errors"
|
||||
"github.com/guregu/null/v5"
|
||||
)
|
||||
|
||||
@@ -118,7 +118,7 @@ func Visit(url string) (*snapshot.Snapshot, error) {
|
||||
func connectAndGetData(url string) ([]byte, error) {
|
||||
parsedURL, err := stdurl.Parse(url)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(err)
|
||||
return nil, go_errors.NewError(err)
|
||||
}
|
||||
|
||||
hostname := parsedURL.Hostname()
|
||||
@@ -169,7 +169,7 @@ func connectAndGetData(url string) ([]byte, error) {
|
||||
data = append(data, buf[:n]...)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
if go_errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, errors2.NewHostError(err)
|
||||
|
||||
8
main.go
8
main.go
@@ -10,8 +10,8 @@ import (
|
||||
"gemini-grc/common/blackList"
|
||||
"gemini-grc/config"
|
||||
"gemini-grc/db"
|
||||
"gemini-grc/errors"
|
||||
"gemini-grc/logging"
|
||||
"github.com/antanst/go_errors"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/rs/zerolog"
|
||||
zlog "github.com/rs/zerolog/log"
|
||||
@@ -24,8 +24,8 @@ func main() {
|
||||
zlog.Logger = zlog.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "[2006-01-02 15:04:05]"})
|
||||
err := runApp()
|
||||
if err != nil {
|
||||
var asErr *errors.Error
|
||||
if errors.As(err, &asErr) {
|
||||
var asErr *go_errors.Error
|
||||
if go_errors.As(err, &asErr) {
|
||||
logging.LogError("Unexpected error: %v", err)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unexpected error: %v", err)
|
||||
} else {
|
||||
@@ -73,7 +73,7 @@ func runApp() (err error) {
|
||||
logging.LogWarn("Received SIGINT or SIGTERM signal, exiting")
|
||||
return nil
|
||||
case err := <-common.ErrorsChan:
|
||||
if errors.IsFatal(err) {
|
||||
if go_errors.IsFatal(err) {
|
||||
return err
|
||||
}
|
||||
logging.LogError("%s", fmt.Sprintf("%v", err))
|
||||
|
||||
Reference in New Issue
Block a user