mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-05-21 11:35:26 +00:00
fix: better error detail for failed sql
This commit is contained in:
@@ -1315,6 +1315,7 @@ func (b *BunSelectQuery) Scan(ctx context.Context, dest interface{}) (err error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
sqlStr := b.query.String()
|
sqlStr := b.query.String()
|
||||||
logger.Error("BunSelectQuery.Scan failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("BunSelectQuery.Scan failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1371,7 +1372,7 @@ func (b *BunSelectQuery) ScanModel(ctx context.Context) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
sqlStr := b.query.String()
|
sqlStr := b.query.String()
|
||||||
logger.Error("BunSelectQuery.ScanModel failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("BunSelectQuery.ScanModel failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
return err
|
return common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// After main query, load custom preloads using separate queries
|
// After main query, load custom preloads using separate queries
|
||||||
@@ -1401,6 +1402,7 @@ func (b *BunSelectQuery) Count(ctx context.Context) (count int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
sqlStr := b.query.String()
|
sqlStr := b.query.String()
|
||||||
logger.Error("BunSelectQuery.Count failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("BunSelectQuery.Count failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1414,6 +1416,7 @@ func (b *BunSelectQuery) Count(ctx context.Context) (count int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
sqlStr := countQuery.String()
|
sqlStr := countQuery.String()
|
||||||
logger.Error("BunSelectQuery.Count (subquery) failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("BunSelectQuery.Count (subquery) failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1431,6 +1434,7 @@ func (b *BunSelectQuery) Exists(ctx context.Context) (exists bool, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
sqlStr := b.query.String()
|
sqlStr := b.query.String()
|
||||||
logger.Error("BunSelectQuery.Exists failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("BunSelectQuery.Exists failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1619,6 +1623,7 @@ func (b *BunUpdateQuery) Exec(ctx context.Context) (res common.Result, err error
|
|||||||
// Log SQL string for debugging
|
// Log SQL string for debugging
|
||||||
sqlStr := b.query.String()
|
sqlStr := b.query.String()
|
||||||
logger.Error("BunUpdateQuery.Exec failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("BunUpdateQuery.Exec failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(b.metricsEnabled, "UPDATE", b.schema, b.entity, b.tableName, startedAt, err)
|
recordQueryMetrics(b.metricsEnabled, "UPDATE", b.schema, b.entity, b.tableName, startedAt, err)
|
||||||
return &BunResult{result: result}, err
|
return &BunResult{result: result}, err
|
||||||
@@ -1670,6 +1675,7 @@ func (b *BunDeleteQuery) Exec(ctx context.Context) (res common.Result, err error
|
|||||||
// Log SQL string for debugging
|
// Log SQL string for debugging
|
||||||
sqlStr := b.query.String()
|
sqlStr := b.query.String()
|
||||||
logger.Error("BunDeleteQuery.Exec failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("BunDeleteQuery.Exec failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(b.metricsEnabled, "DELETE", b.schema, b.entity, b.tableName, startedAt, err)
|
recordQueryMetrics(b.metricsEnabled, "DELETE", b.schema, b.entity, b.tableName, startedAt, err)
|
||||||
return &BunResult{result: result}, err
|
return &BunResult{result: result}, err
|
||||||
|
|||||||
@@ -583,6 +583,7 @@ func (g *GormSelectQuery) Scan(ctx context.Context, dest interface{}) (err error
|
|||||||
return tx.Find(dest)
|
return tx.Find(dest)
|
||||||
})
|
})
|
||||||
logger.Error("GormSelectQuery.Scan failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("GormSelectQuery.Scan failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(g.metricsEnabled, "SELECT", g.schema, g.entity, g.tableName, startedAt, err)
|
recordQueryMetrics(g.metricsEnabled, "SELECT", g.schema, g.entity, g.tableName, startedAt, err)
|
||||||
return err
|
return err
|
||||||
@@ -613,6 +614,7 @@ func (g *GormSelectQuery) ScanModel(ctx context.Context) (err error) {
|
|||||||
return tx.Find(g.db.Statement.Model)
|
return tx.Find(g.db.Statement.Model)
|
||||||
})
|
})
|
||||||
logger.Error("GormSelectQuery.ScanModel failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("GormSelectQuery.ScanModel failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(g.metricsEnabled, "SELECT", g.schema, g.entity, g.tableName, startedAt, err)
|
recordQueryMetrics(g.metricsEnabled, "SELECT", g.schema, g.entity, g.tableName, startedAt, err)
|
||||||
return err
|
return err
|
||||||
@@ -642,6 +644,7 @@ func (g *GormSelectQuery) Count(ctx context.Context) (count int, err error) {
|
|||||||
return tx.Count(&count64)
|
return tx.Count(&count64)
|
||||||
})
|
})
|
||||||
logger.Error("GormSelectQuery.Count failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("GormSelectQuery.Count failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(g.metricsEnabled, "COUNT", g.schema, g.entity, g.tableName, startedAt, err)
|
recordQueryMetrics(g.metricsEnabled, "COUNT", g.schema, g.entity, g.tableName, startedAt, err)
|
||||||
return int(count64), err
|
return int(count64), err
|
||||||
@@ -671,6 +674,7 @@ func (g *GormSelectQuery) Exists(ctx context.Context) (exists bool, err error) {
|
|||||||
return tx.Limit(1).Count(&count)
|
return tx.Limit(1).Count(&count)
|
||||||
})
|
})
|
||||||
logger.Error("GormSelectQuery.Exists failed. SQL: %s. Error: %v", sqlStr, err)
|
logger.Error("GormSelectQuery.Exists failed. SQL: %s. Error: %v", sqlStr, err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(g.metricsEnabled, "EXISTS", g.schema, g.entity, g.tableName, startedAt, err)
|
recordQueryMetrics(g.metricsEnabled, "EXISTS", g.schema, g.entity, g.tableName, startedAt, err)
|
||||||
return count > 0, err
|
return count > 0, err
|
||||||
@@ -931,6 +935,7 @@ func (g *GormUpdateQuery) Exec(ctx context.Context) (res common.Result, err erro
|
|||||||
return tx.Updates(g.updates)
|
return tx.Updates(g.updates)
|
||||||
})
|
})
|
||||||
logger.Error("GormUpdateQuery.Exec failed. SQL: %s. Error: %v", sqlStr, result.Error)
|
logger.Error("GormUpdateQuery.Exec failed. SQL: %s. Error: %v", sqlStr, result.Error)
|
||||||
|
return &GormResult{result: result}, common.WrapSQLError(result.Error, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(g.metricsEnabled, "UPDATE", g.schema, g.entity, g.tableName, startedAt, result.Error)
|
recordQueryMetrics(g.metricsEnabled, "UPDATE", g.schema, g.entity, g.tableName, startedAt, result.Error)
|
||||||
return &GormResult{result: result}, result.Error
|
return &GormResult{result: result}, result.Error
|
||||||
@@ -992,6 +997,7 @@ func (g *GormDeleteQuery) Exec(ctx context.Context) (res common.Result, err erro
|
|||||||
return tx.Delete(g.model)
|
return tx.Delete(g.model)
|
||||||
})
|
})
|
||||||
logger.Error("GormDeleteQuery.Exec failed. SQL: %s. Error: %v", sqlStr, result.Error)
|
logger.Error("GormDeleteQuery.Exec failed. SQL: %s. Error: %v", sqlStr, result.Error)
|
||||||
|
return &GormResult{result: result}, common.WrapSQLError(result.Error, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(g.metricsEnabled, "DELETE", g.schema, g.entity, g.tableName, startedAt, result.Error)
|
recordQueryMetrics(g.metricsEnabled, "DELETE", g.schema, g.entity, g.tableName, startedAt, result.Error)
|
||||||
return &GormResult{result: result}, result.Error
|
return &GormResult{result: result}, result.Error
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func (p *PgSQLAdapter) Exec(ctx context.Context, query string, args ...interface
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL Exec failed: %v", err)
|
logger.Error("PgSQL Exec failed: %v", err)
|
||||||
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
||||||
return nil, err
|
return nil, common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, nil)
|
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, nil)
|
||||||
return &PgSQLResult{result: result}, nil
|
return &PgSQLResult{result: result}, nil
|
||||||
@@ -164,7 +164,7 @@ func (p *PgSQLAdapter) Query(ctx context.Context, dest interface{}, query string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL Query failed: %v", err)
|
logger.Error("PgSQL Query failed: %v", err)
|
||||||
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
||||||
return err
|
return common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
@@ -511,7 +511,7 @@ func (p *PgSQLSelectQuery) Scan(ctx context.Context, dest interface{}) (err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL SELECT failed: %v", err)
|
logger.Error("PgSQL SELECT failed: %v", err)
|
||||||
recordQueryMetrics(p.metricsEnabled, "SELECT", p.schema, p.entity, p.tableName, startedAt, err)
|
recordQueryMetrics(p.metricsEnabled, "SELECT", p.schema, p.entity, p.tableName, startedAt, err)
|
||||||
return err
|
return common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
@@ -534,8 +534,8 @@ func (p *PgSQLSelectQuery) ScanModel(ctx context.Context) error {
|
|||||||
return p.Scan(ctx, p.model)
|
return p.Scan(ctx, p.model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// countInternal executes the COUNT query and returns the result without recording metrics.
|
// countInternal executes the COUNT query and returns the result and the SQL string without recording metrics.
|
||||||
func (p *PgSQLSelectQuery) countInternal(ctx context.Context) (int, error) {
|
func (p *PgSQLSelectQuery) countInternal(ctx context.Context) (rowCount int, querySQL string, retErr error) {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString("SELECT COUNT(*) FROM ")
|
sb.WriteString("SELECT COUNT(*) FROM ")
|
||||||
sb.WriteString(p.tableName)
|
sb.WriteString(p.tableName)
|
||||||
@@ -571,9 +571,9 @@ func (p *PgSQLSelectQuery) countInternal(ctx context.Context) (int, error) {
|
|||||||
|
|
||||||
var count int
|
var count int
|
||||||
if err := row.Scan(&count); err != nil {
|
if err := row.Scan(&count); err != nil {
|
||||||
return 0, err
|
return 0, query, err
|
||||||
}
|
}
|
||||||
return count, nil
|
return count, query, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PgSQLSelectQuery) Count(ctx context.Context) (count int, err error) {
|
func (p *PgSQLSelectQuery) Count(ctx context.Context) (count int, err error) {
|
||||||
@@ -584,9 +584,11 @@ func (p *PgSQLSelectQuery) Count(ctx context.Context) (count int, err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
count, err = p.countInternal(ctx)
|
var sqlStr string
|
||||||
|
count, sqlStr, err = p.countInternal(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL COUNT failed: %v", err)
|
logger.Error("PgSQL COUNT failed: %v", err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(p.metricsEnabled, "COUNT", p.schema, p.entity, p.tableName, startedAt, err)
|
recordQueryMetrics(p.metricsEnabled, "COUNT", p.schema, p.entity, p.tableName, startedAt, err)
|
||||||
return count, err
|
return count, err
|
||||||
@@ -600,9 +602,11 @@ func (p *PgSQLSelectQuery) Exists(ctx context.Context) (exists bool, err error)
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
count, err := p.countInternal(ctx)
|
var sqlStr string
|
||||||
|
count, sqlStr, err := p.countInternal(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL EXISTS failed: %v", err)
|
logger.Error("PgSQL EXISTS failed: %v", err)
|
||||||
|
err = common.WrapSQLError(err, sqlStr)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(p.metricsEnabled, "EXISTS", p.schema, p.entity, p.tableName, startedAt, err)
|
recordQueryMetrics(p.metricsEnabled, "EXISTS", p.schema, p.entity, p.tableName, startedAt, err)
|
||||||
return count > 0, err
|
return count > 0, err
|
||||||
@@ -702,7 +706,7 @@ func (p *PgSQLInsertQuery) Exec(ctx context.Context) (res common.Result, err err
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL INSERT failed: %v", err)
|
logger.Error("PgSQL INSERT failed: %v", err)
|
||||||
return nil, err
|
return nil, common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PgSQLResult{result: result}, nil
|
return &PgSQLResult{result: result}, nil
|
||||||
@@ -750,7 +754,10 @@ func (p *PgSQLInsertQuery) Scan(ctx context.Context, dest interface{}) (err erro
|
|||||||
row = p.db.QueryRowContext(ctx, query, args...)
|
row = p.db.QueryRowContext(ctx, query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return row.Scan(dest)
|
if err := row.Scan(dest); err != nil {
|
||||||
|
return common.WrapSQLError(err, query)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PgSQLUpdateQuery implements UpdateQuery for PostgreSQL
|
// PgSQLUpdateQuery implements UpdateQuery for PostgreSQL
|
||||||
@@ -929,7 +936,7 @@ func (p *PgSQLUpdateQuery) Exec(ctx context.Context) (res common.Result, err err
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL UPDATE failed: %v", err)
|
logger.Error("PgSQL UPDATE failed: %v", err)
|
||||||
return nil, err
|
return nil, common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PgSQLResult{result: result}, nil
|
return &PgSQLResult{result: result}, nil
|
||||||
@@ -1007,7 +1014,7 @@ func (p *PgSQLDeleteQuery) Exec(ctx context.Context) (res common.Result, err err
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL DELETE failed: %v", err)
|
logger.Error("PgSQL DELETE failed: %v", err)
|
||||||
return nil, err
|
return nil, common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PgSQLResult{result: result}, nil
|
return &PgSQLResult{result: result}, nil
|
||||||
@@ -1088,7 +1095,7 @@ func (p *PgSQLTxAdapter) Exec(ctx context.Context, query string, args ...interfa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL Tx Exec failed: %v", err)
|
logger.Error("PgSQL Tx Exec failed: %v", err)
|
||||||
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
||||||
return nil, err
|
return nil, common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, nil)
|
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, nil)
|
||||||
return &PgSQLResult{result: result}, nil
|
return &PgSQLResult{result: result}, nil
|
||||||
@@ -1102,7 +1109,7 @@ func (p *PgSQLTxAdapter) Query(ctx context.Context, dest interface{}, query stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("PgSQL Tx Query failed: %v", err)
|
logger.Error("PgSQL Tx Query failed: %v", err)
|
||||||
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
recordQueryMetrics(p.metricsEnabled, operation, schema, entity, table, startedAt, err)
|
||||||
return err
|
return common.WrapSQLError(err, query)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
|
// SQLError wraps a database error together with the SQL that caused it,
|
||||||
|
// so callers can surface the query in API error responses for easier debugging.
|
||||||
|
type SQLError struct {
|
||||||
|
Err error
|
||||||
|
SQL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SQLError) Error() string { return e.Err.Error() }
|
||||||
|
func (e *SQLError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
// WrapSQLError wraps err with the given SQL. If err is nil it returns nil.
|
||||||
|
func WrapSQLError(err error, sql string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &SQLError{Err: err, SQL: sql}
|
||||||
|
}
|
||||||
|
|
||||||
type RequestBody struct {
|
type RequestBody struct {
|
||||||
Operation string `json:"operation"`
|
Operation string `json:"operation"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
@@ -104,6 +122,7 @@ type APIError struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Details interface{} `json:"details,omitempty"`
|
Details interface{} `json:"details,omitempty"`
|
||||||
Detail string `json:"detail,omitempty"`
|
Detail string `json:"detail,omitempty"`
|
||||||
|
SQL string `json:"sql,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Column struct {
|
type Column struct {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package funcspec
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -1071,6 +1072,10 @@ func sendError(w http.ResponseWriter, status int, code, message string, err erro
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errObj.Detail = err.Error()
|
errObj.Detail = err.Error()
|
||||||
|
var sqlErr *common.SQLError
|
||||||
|
if errors.As(err, &sqlErr) {
|
||||||
|
errObj.SQL = sqlErr.SQL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _ := json.Marshal(map[string]interface{}{
|
data, _ := json.Marshal(map[string]interface{}{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -1757,18 +1758,21 @@ func (h *Handler) sendResponse(w common.ResponseWriter, data interface{}, metada
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) sendError(w common.ResponseWriter, status int, code, message string, details interface{}) {
|
func (h *Handler) sendError(w common.ResponseWriter, status int, code, message string, details interface{}) {
|
||||||
|
apiErr := &common.APIError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Details: details,
|
||||||
|
Detail: fmt.Sprintf("%v", details),
|
||||||
|
}
|
||||||
|
if asErr, ok := details.(error); ok {
|
||||||
|
var sqlErr *common.SQLError
|
||||||
|
if errors.As(asErr, &sqlErr) {
|
||||||
|
apiErr.SQL = sqlErr.SQL
|
||||||
|
}
|
||||||
|
}
|
||||||
w.SetHeader("Content-Type", "application/json")
|
w.SetHeader("Content-Type", "application/json")
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
err := w.WriteJSON(common.Response{
|
if err := w.WriteJSON(common.Response{Success: false, Error: apiErr}); err != nil {
|
||||||
Success: false,
|
|
||||||
Error: &common.APIError{
|
|
||||||
Code: code,
|
|
||||||
Message: message,
|
|
||||||
Details: details,
|
|
||||||
Detail: fmt.Sprintf("%v", details),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Error sending response: %v", err)
|
logger.Error("Error sending response: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -579,8 +580,8 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
|||||||
// preload LEFT JOIN (to prevent "table name specified more than once" errors).
|
// preload LEFT JOIN (to prevent "table name specified more than once" errors).
|
||||||
if len(options.CustomSQLJoin) > 0 {
|
if len(options.CustomSQLJoin) > 0 {
|
||||||
preloadAliasSet := make(map[string]bool, len(options.Preload))
|
preloadAliasSet := make(map[string]bool, len(options.Preload))
|
||||||
for _, p := range options.Preload {
|
for i := range options.Preload {
|
||||||
if alias := common.RelationPathToBunAlias(p.Relation); alias != "" {
|
if alias := common.RelationPathToBunAlias(options.Preload[i].Relation); alias != "" {
|
||||||
preloadAliasSet[alias] = true
|
preloadAliasSet[alias] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2645,6 +2646,12 @@ func (h *Handler) sendError(w common.ResponseWriter, statusCode int, code, messa
|
|||||||
"_error": errorMsg,
|
"_error": errorMsg,
|
||||||
"_retval": 1,
|
"_retval": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sqlErr *common.SQLError
|
||||||
|
if errors.As(err, &sqlErr) {
|
||||||
|
response["_sql"] = sqlErr.SQL
|
||||||
|
}
|
||||||
|
|
||||||
w.SetHeader("Content-Type", "application/json")
|
w.SetHeader("Content-Type", "application/json")
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
if jsonErr := w.WriteJSON(response); jsonErr != nil {
|
if jsonErr := w.WriteJSON(response); jsonErr != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package websocketspec
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -17,6 +18,17 @@ import (
|
|||||||
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// newErrorResponseFromErr creates an error response from a Go error, including the SQL
|
||||||
|
// query in the error info when the error is a database SQLError.
|
||||||
|
func newErrorResponseFromErr(id, code string, err error) *ResponseMessage {
|
||||||
|
resp := NewErrorResponse(id, code, err.Error())
|
||||||
|
var sqlErr *common.SQLError
|
||||||
|
if errors.As(err, &sqlErr) {
|
||||||
|
resp.Error.SQL = sqlErr.SQL
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
// Handler handles WebSocket connections and messages
|
// Handler handles WebSocket connections and messages
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
db common.Database
|
db common.Database
|
||||||
@@ -236,7 +248,7 @@ func (h *Handler) handleRead(conn *Connection, msg *Message, hookCtx *HookContex
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[WebSocketSpec] Read operation failed: %v", err)
|
logger.Error("[WebSocketSpec] Read operation failed: %v", err)
|
||||||
errResp := NewErrorResponse(msg.ID, "read_error", err.Error())
|
errResp := newErrorResponseFromErr(msg.ID, "read_error", err)
|
||||||
_ = conn.SendJSON(errResp)
|
_ = conn.SendJSON(errResp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -272,7 +284,7 @@ func (h *Handler) handleCreate(conn *Connection, msg *Message, hookCtx *HookCont
|
|||||||
data, err := h.create(hookCtx)
|
data, err := h.create(hookCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[WebSocketSpec] Create operation failed: %v", err)
|
logger.Error("[WebSocketSpec] Create operation failed: %v", err)
|
||||||
errResp := NewErrorResponse(msg.ID, "create_error", err.Error())
|
errResp := newErrorResponseFromErr(msg.ID, "create_error", err)
|
||||||
_ = conn.SendJSON(errResp)
|
_ = conn.SendJSON(errResp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -310,7 +322,7 @@ func (h *Handler) handleUpdate(conn *Connection, msg *Message, hookCtx *HookCont
|
|||||||
data, err := h.update(hookCtx)
|
data, err := h.update(hookCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[WebSocketSpec] Update operation failed: %v", err)
|
logger.Error("[WebSocketSpec] Update operation failed: %v", err)
|
||||||
errResp := NewErrorResponse(msg.ID, "update_error", err.Error())
|
errResp := newErrorResponseFromErr(msg.ID, "update_error", err)
|
||||||
_ = conn.SendJSON(errResp)
|
_ = conn.SendJSON(errResp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -348,7 +360,7 @@ func (h *Handler) handleDelete(conn *Connection, msg *Message, hookCtx *HookCont
|
|||||||
err := h.delete(hookCtx)
|
err := h.delete(hookCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[WebSocketSpec] Delete operation failed: %v", err)
|
logger.Error("[WebSocketSpec] Delete operation failed: %v", err)
|
||||||
errResp := NewErrorResponse(msg.ID, "delete_error", err.Error())
|
errResp := newErrorResponseFromErr(msg.ID, "delete_error", err)
|
||||||
_ = conn.SendJSON(errResp)
|
_ = conn.SendJSON(errResp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ type ErrorInfo struct {
|
|||||||
|
|
||||||
// Details contains additional error context
|
// Details contains additional error context
|
||||||
Details map[string]interface{} `json:"details,omitempty"`
|
Details map[string]interface{} `json:"details,omitempty"`
|
||||||
|
|
||||||
|
// SQL is the query that caused the error, populated for database errors
|
||||||
|
SQL string `json:"sql,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestMessage represents a client request
|
// RequestMessage represents a client request
|
||||||
|
|||||||
Reference in New Issue
Block a user