Skip to content

Commit c2ceb32

Browse files
committed
feat: enhance error handling with repository error mapping and detailed responses
1 parent f397390 commit c2ceb32

4 files changed

Lines changed: 108 additions & 7 deletions

File tree

internal/core/errors.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"log"
66
"net/http"
77
"runtime"
8+
9+
"github.com/UltimateForm/gopen-api/internal/repository"
810
)
911

1012
type ErrorResponse struct {
@@ -42,12 +44,27 @@ func RespondKnownError(res http.ResponseWriter, err ErrorResponse) {
4244
json.NewEncoder(res).Encode(err)
4345
}
4446

47+
func mapRepoError(err repository.RepoError) ErrorResponse {
48+
switch err.Code {
49+
case repository.EmptyCollectionRepoError:
50+
return NotFound()
51+
case repository.ValidationError:
52+
return BadRequest("Db validation error occured, if you were trying to create something it's likely it already exists")
53+
case repository.UnknownRepoError:
54+
fallthrough
55+
default:
56+
return InternalServerError()
57+
}
58+
}
59+
4560
func RespondError(res http.ResponseWriter, req *http.Request, err error) {
4661
switch bindErr := err.(type) {
4762
case ErrorResponse:
4863
RespondKnownError(res, bindErr)
64+
case repository.RepoError:
65+
RespondKnownError(res, mapRepoError(bindErr))
4966
default:
50-
log.Printf("Non HTTP error occured: %v", err)
67+
log.Printf("Non HTTP error of type %T occured: %v", err, err)
5168
RespondKnownError(res, InternalServerError())
5269

5370
}

internal/repository/charrep/main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,17 @@ func txReadOneCharacter(ctx context.Context, id string) neo4j.ManagedTransaction
6262
`
6363
MATCH (character:Character {id: $id})
6464
RETURN character
65+
LIMIT 1
6566
`)
6667
return func(tx neo4j.ManagedTransaction) (Character, error) {
6768
res, err := query.Execute(tx, ctx)
6869
if err != nil {
6970
return Character{}, err
7071
}
71-
72-
record, singleErr := res.Single(ctx)
73-
if singleErr != nil {
74-
return Character{}, singleErr
72+
var record *neo4j.Record
73+
hasNext := res.NextRecord(ctx, &record)
74+
if !hasNext {
75+
return Character{}, repository.RepoError{Code: repository.EmptyCollectionRepoError}
7576
}
7677

7778
return extractCharacterFromRecord(record)

internal/repository/main.go

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,79 @@ import (
88
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
99
)
1010

11+
func mapNeo4JToRepo(err *neo4j.Neo4jError) RepoError {
12+
switch err.Code {
13+
case "Neo.ClientError.Statement.EntityNotFound":
14+
return RepoError{Code: UnknownRepoError}
15+
case "Neo.ClientError.Schema.ConstraintValidationFailed":
16+
return RepoError{Code: ValidationError}
17+
case "Neo.ClientError.Statement.ArgumentError":
18+
return RepoError{Code: ValidationError}
19+
case "Neo.ClientError.Statement.InvalidArgument":
20+
return RepoError{Code: ValidationError}
21+
case "Neo.ClientError.General.InvalidConfiguration":
22+
return RepoError{Code: UnknownRepoError}
23+
case "Neo.ClientError.Statement.SyntaxError":
24+
return RepoError{Code: ValidationError}
25+
case "Neo.ClientError.Transaction.InvalidOperation":
26+
return RepoError{Code: UnknownRepoError}
27+
case "Neo.ClientError.Statement.ParameterError":
28+
return RepoError{Code: ValidationError}
29+
case "Neo.ClientError.Security.Unauthorized":
30+
return RepoError{Code: UnknownRepoError}
31+
case "Neo.ClientError.Statement.TypeError":
32+
return RepoError{Code: ValidationError}
33+
case "Neo.ClientError.Statement.ValueError":
34+
return RepoError{Code: ValidationError}
35+
case "Neo.ClientError.General.NotSupported":
36+
return RepoError{Code: UnknownRepoError}
37+
case "Neo.ClientError.Statement.ArithmeticError":
38+
return RepoError{Code: ValidationError}
39+
case "Neo.ClientError.Security.AuthenticationFailed":
40+
return RepoError{Code: UnknownRepoError}
41+
case "Neo.ClientError.Security.AuthorizationExpired":
42+
return RepoError{Code: UnknownRepoError}
43+
case "Neo.ClientError.Security.Forbidden":
44+
return RepoError{Code: UnknownRepoError}
45+
case "Neo.ClientError.Transaction.LockClientStopped":
46+
return RepoError{Code: UnknownRepoError}
47+
case "Neo.TransientError.General.DatabaseUnavailable":
48+
return RepoError{Code: UnknownRepoError}
49+
case "Neo.ClientError.Transaction.InvalidState":
50+
return RepoError{Code: UnknownRepoError}
51+
case "Neo.TransientError.Transaction.Terminated":
52+
return RepoError{Code: UnknownRepoError}
53+
case "Neo.ClientError.Statement.NoSuchEntity":
54+
return RepoError{Code: EmptyCollectionRepoError}
55+
default:
56+
return RepoError{Code: UnknownRepoError}
57+
}
58+
}
59+
1160
func ExecuteReadWithDriver[T any](ctx context.Context, tx neo4j.ManagedTransactionWorkT[T]) (T, error) {
1261
session := db.Driver.NewSession(ctx, neo4j.SessionConfig{})
1362
defer session.Close(ctx)
14-
return neo4j.ExecuteRead(ctx, session, tx, func(config *neo4j.TransactionConfig) {
63+
result, err := neo4j.ExecuteRead(ctx, session, tx, func(config *neo4j.TransactionConfig) {
1564
config.Timeout = time.Second * 10
1665
})
66+
neo4jError, isNeo4jError := err.(*neo4j.Neo4jError)
67+
if isNeo4jError {
68+
return result, mapNeo4JToRepo(neo4jError)
69+
}
70+
return result, err
1771
}
1872

1973
func ExecuteWriteWithDriver[T any](ctx context.Context, tx neo4j.ManagedTransactionWorkT[T]) (T, error) {
2074
session := db.Driver.NewSession(ctx, neo4j.SessionConfig{})
2175
defer session.Close(ctx)
22-
return neo4j.ExecuteWrite(ctx, session, tx, func(config *neo4j.TransactionConfig) {
76+
result, err := neo4j.ExecuteWrite(ctx, session, tx, func(config *neo4j.TransactionConfig) {
2377
config.Timeout = time.Second * 10
2478
})
79+
neo4jError, isNeo4jError := err.(*neo4j.Neo4jError)
80+
if isNeo4jError {
81+
return result, mapNeo4JToRepo(neo4jError)
82+
}
83+
return result, err
2584
}
2685

2786
func NewQuery(data map[string]any, query string) QueryRunner {

internal/repository/types.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,27 @@ func (src QueryRunner) Execute(tx neo4j.ManagedTransaction, ctx context.Context)
1515
type IQueryParams interface {
1616
ToParams() map[string]any
1717
}
18+
type RepoErrorCode int
19+
20+
const (
21+
UnknownRepoError RepoErrorCode = iota
22+
EmptyCollectionRepoError
23+
ValidationError
24+
)
25+
26+
type RepoError struct {
27+
Code RepoErrorCode
28+
}
29+
30+
func (src RepoError) Error() string {
31+
switch src.Code {
32+
case EmptyCollectionRepoError:
33+
return "Empty collection"
34+
case ValidationError:
35+
return "Conflicting records"
36+
case UnknownRepoError:
37+
fallthrough
38+
default:
39+
return "Unknown repo error"
40+
}
41+
}

0 commit comments

Comments
 (0)