1 Commits

Author SHA1 Message Date
user
25cbbfd42a Replace string-matching error detection with typed SQLite errors
All checks were successful
check / check (push) Successful in 2m17s
Use errors.As with *sqlite.Error and SQLITE_CONSTRAINT_UNIQUE code
instead of fragile strings.Contains(err.Error(), "UNIQUE") checks.

Add db.IsUniqueConstraintError helper in internal/db/errors.go and
replace all three string-matching call sites in api.go and auth.go.
2026-03-10 03:18:33 -07:00
4 changed files with 26 additions and 13 deletions

20
internal/db/errors.go Normal file
View File

@@ -0,0 +1,20 @@
// Package db provides database access and migration management.
package db
import (
"errors"
"modernc.org/sqlite"
sqlite3 "modernc.org/sqlite/lib"
)
// IsUniqueConstraintError reports whether err is a SQLite
// unique-constraint violation.
func IsUniqueConstraintError(err error) bool {
var sqliteErr *sqlite.Error
if !errors.As(err, &sqliteErr) {
return false
}
return sqliteErr.Code() == sqlite3.SQLITE_CONSTRAINT_UNIQUE
}

View File

@@ -10,6 +10,7 @@ import (
"strings"
"time"
"git.eeqj.de/sneak/neoirc/internal/db"
"git.eeqj.de/sneak/neoirc/internal/irc"
"github.com/go-chi/chi"
)
@@ -199,7 +200,7 @@ func (hdlr *Handlers) handleCreateSessionError(
request *http.Request,
err error,
) {
if strings.Contains(err.Error(), "UNIQUE") {
if db.IsUniqueConstraintError(err) {
hdlr.respondError(
writer, request,
"nick already taken",
@@ -1427,7 +1428,7 @@ func (hdlr *Handlers) executeNickChange(
request.Context(), sessionID, newNick,
)
if err != nil {
if strings.Contains(err.Error(), "UNIQUE") {
if db.IsUniqueConstraintError(err) {
hdlr.respondIRCError(
writer, request, clientID, sessionID,
irc.ErrNicknameInUse, nick, []string{newNick},

View File

@@ -4,6 +4,8 @@ import (
"encoding/json"
"net/http"
"strings"
"git.eeqj.de/sneak/neoirc/internal/db"
)
const minPasswordLength = 8
@@ -94,7 +96,7 @@ func (hdlr *Handlers) handleRegisterError(
request *http.Request,
err error,
) {
if strings.Contains(err.Error(), "UNIQUE") {
if db.IsUniqueConstraintError(err) {
hdlr.respondError(
writer, request,
"nick already taken",

View File

@@ -16,11 +16,6 @@ import (
const routeTimeout = 60 * time.Second
// cspHeader is the Content-Security-Policy applied to the embedded web SPA.
// The SPA loads external scripts and stylesheets from the same origin only;
// all API communication uses same-origin fetch (no WebSockets).
const cspHeader = "default-src 'self'; script-src 'self'; style-src 'self'"
// SetupRoutes configures the HTTP routes and middleware.
func (srv *Server) SetupRoutes() {
srv.router = chi.NewRouter()
@@ -138,11 +133,6 @@ func (srv *Server) setupSPA() {
writer http.ResponseWriter,
request *http.Request,
) {
writer.Header().Set(
"Content-Security-Policy",
cspHeader,
)
readFS, ok := distFS.(fs.ReadFileFS)
if !ok {
fileServer.ServeHTTP(writer, request)