Return postgres errors in api response

Viewed 309

I have two simple api methods in my code. Method with endpoind /api/user/create creates user. Field username is unique. When i trying to create user with same username that already exists in database, i have an error in console:

(/home/andrej/go/src/go_contacts/models/users.go:19) 
[2020-12-23 22:03:10]  pq: duplicate key value violates unique constraint "users_username_key"

I want to show this error in response to user, or somehow identify type of error in my code, to show different error messages for user. I know only that if i have error user returns me id=0. But it doesnt seems like a good message for user.

main.go

package main

import (
    "fmt"
    "go_contacts/controllers"
    "net/http"
    "os"

    "github.com/gorilla/mux"
    "github.com/joho/godotenv"
)

func main() {
    godotenv.Load(".env")


    router := mux.NewRouter()
    router.HandleFunc("/", controllers.ReturnHello).Methods("GET")
    router.HandleFunc("/api/user/create", controllers.CreateUser).Methods("POST")

    port := os.Getenv("PORT")
    if port == "" {
        port = "8000"
    }

    err := http.ListenAndServe(":"+port, router)
    if err != nil {
        fmt.Print(err)
    }
}

models.go with user struct:

package models

import (
    u "go_contacts/utils"

    "github.com/jinzhu/gorm"
)

// User base model
type User struct {
    gorm.Model
    Username string `json:"username" gorm:"unique"`
    Password string `json:"password"`
    Email    string `json:"email"`
}

// Create new user
func (user *User) Create() map[string]interface{} {
    GetDB().Create(user)

    if user.ID <= 0 {
        return u.Message(false, "Failed to create user, connection error.")
    }

    response := u.Message(true, "Account has been created")
    response["user"] = user
    return response
}
2 Answers

As per the comments gorm provides access to database errors from the Create record function as follows:

result := GetDB().Create(user)
if result.Error != nil {
   // Do something with the error
}

Please note that checking the error string is likely to make your application database specific (but this may not be an issue for you). The answers to this question may provide further assistance.

As for pq v1.5.2 and gorm v1.9.12

First, you need to identify whether create method call returns error or not. Like so

err := GetDB().Create(user).Error
if err != nil {
    // code to handle error
}

Then the pq package has special type which maps to postgres server error. It contains fields which can help you to identify the error severity, it's code, table which related to error etc.

Full list of psql fields identifiers can be found here in

Error and Notice Message Fields

https://www.postgresql.org/docs/current/protocol-error-fields.html

To resolve your issue as an option we can use field

Code

Which is a string type representation of error code. But firstly, we need to cast an error and check type cast was successful. Like so

err := GetDB().Create(user).Error
if err != nil {
    pqErr, ok := err.(pq.Error)
    if !ok {
        log.Fatal(err)
    }

    // code to handle specific error code
}

List of error codes can be found both in the official postgresql docs https://www.postgresql.org/docs/9.3/errcodes-appendix.html

And actual go pq specific mappings to the error in the github.com/lib/pq/error.go as for pq library

And finally we can handle error as duplicate by code

err := GetDB().Create(user).Error
if err != nil {
    pqErr, ok := err.(pq.Error)
    if !ok {
        log.Fatal(err)
    }

    // note that type casting here is redundant and used for showing specific 
    // string type from pq library
    if pqErr.Code == pq.ErrorCode("23505") { // 23505 is unique_violation error code for psql
        // now you can create error and write it to a message to return it for 
        // a client
    }
}
Related