Idiomatic way to update with GORM and echo-framework - go

Hi new in golang and trying to make rest API
I want to updating entity passing only needed datas with GORM and golang echo framework.
And I get this error :
"strconv.ParseUint: parsing \"ea78944c-a2a9-4813-86e7-10b199d0f002\": invalid syntax"
I use echo.Bind() func to bind formdata (I use postman) with my Club struct.
PS : I use xid to external id and keep my int id to interne work.
Expected :
I'm gonna find Club in my database by id (get with url param) with GORM and set it up in a new club struct.
After that, I bind my club struct with my formdata.
And finally save it with Save() GORM func. => get error
Reality :
I'm gonna find Club in my database by id (write hardcoding string id) with GORM and set it up in a new club struct.
After that, I bind my club struct with my formdata.
And finally save it with Save() GORM func. => works
Conclusion :
I don't know how to pass url parameter AND formdata in PUT method using echo.Bind()
Here my Club struct :
// Club struct
type Club struct {
Base
Name string `json:"name;" gorm:"type:varchar(255);not null" validate:"required"`
Slug string `json:"slug;" gorm:"type:varchar(255);unique;not null"`
Website string `json:"website;" gorm:"type:varchar(255)"`
Lat float32 `json:"lat;" gorm:"lat;" sql:"type:decimal(8,6);" validate:"required,numeric"`
Lng float32 `json:"lng;" gorm:"lng;" sql:"type:decimal(9,6);" validate:"required,numeric"`
Logo string `json:"logo;" gorm:"type:varchar(100)" validate:"required"`
Phone string `json:"phone;" gorm:"type:varchar(20)" validate:"required"`
}
My FindById func :
// GetClubByXID get club by xid
func GetClubByXID(c echo.Context, xid string) (*schemas.Club, error) {
club := new(schemas.Club)
if result := db.Where("xid = ?", xid).First(&club); result.Error != nil {
return nil, result.Error
}
return club, nil
}
And here my updating func :
func UpdateClub(c echo.Context) error {
xid := c.Param("id") // => doesn't work
// xid := "ea78944c-a2a9-4813-86e7-10b199d0f002" // => work
club, err := models.GetClubByXID(c, xid)
if err != nil {
return c.JSON(http.StatusNotFound, err)
}
if err := c.Bind(club); err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
db.Save(&club)
return c.JSON(http.StatusOK, echo.Map{
"club": &club,
})
}
My updating route :
API.PUT("/updateClub/:id", handlers.UpdateClub) // => doesn't work
// API.PUT("/updateClub", handlers.UpdateClub) // => work
When I write with hardcoding my xid ea78944c-a2a9-4813-86e7-10b199d0f002 in my updating func it's work like a charm but I can't combine my url and my formdata with echo.Bind()
my err if I try http://localhost:1323/api/updateClub/ea78944c-a2a9-4813-86e7-10b199d0f002:
"strconv.ParseUint: parsing \"ea78944c-a2a9-4813-86e7-10b199d0f002\": invalid syntax"
Thanks for reading hope someone can help me :)

Can you try to c.Param("xid") instead of c.Param("id") and change the routing parameter as API.PUT("/updateClub/:xid", handlers.UpdateClub)

Related

Error while trying to fetch queryresult.KV object in JSON.Unmarshal

I am a little bit confused here and although I have searched a lot on this, something is clearly missing from my knowledge and I am asking your help.
I have created a Hyperledger Fabric Network and installed a chaincode in it. And I want to make a function that retrieves all the World State inputs about the Keys. I have done it already with the bytes.Buffer and it worked. But what I want to do is to do it with a struct.
So, I created the following struct that has only the key:
type WSKeys struct {
Key string `json: "key"`
Namespace string `json: "Namespace"`
}
And this is my code function:
func (s *SmartContract) getAllWsDataStruct(APIstub shim.ChaincodeStubInterface , args []string) sc.Response {
var keyArrayStr []WSKeys
resultsIterator, err := APIstub.GetQueryResult("{\"selector\":{\"_id\":{\"$ne\": null }} }")
if err != nil {
return shim.Error("Error occured when trying to fetch data: "+err.Error())
}
for resultsIterator.HasNext() {
// Get the next record
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(queryResponse)
var qry_key_json WSKeys
json.Unmarshal([]byte(queryResponse), &qry_key_json)
keyArray = append(keyArray, qry_key_json)
}
defer resultsIterator.Close()
all_bytes, _ := json.Marshal(keyArray)
fmt.Println(keyArray)
return shim.Success(all_bytes)
}
When executing the above I get the following error:
cannot convert queryResponse (type *queryresult.KV) to type []byte
I can get the results correctly if I, for example do this:
func (s *SmartContract) getAllWsDataStruct(APIstub shim.ChaincodeStubInterface , args []string) sc.Response {
var keyArray []string
resultsIterator, err := APIstub.GetQueryResult("{\"selector\":{\"_id\":{\"$ne\": null }} }")
if err != nil {
return shim.Error("Error occured when trying to fetch data: "+err.Error())
}
for resultsIterator.HasNext() {
// Get the next record
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(queryResponse)
keyArray = append(keyArray, queryResponse.Key)
}
defer resultsIterator.Close()
all_bytes, _ := json.Marshal(keyArray)
fmt.Println(keyArray)
return shim.Success(all_bytes)
}
But, why I get the above error when trying to add the queryResponse into a custom struct?
Do I need to add it to a struct that is only its type?
Please someone can explain what I am missing here?
The error statement is verbose enough to indicate, that your []byte conversion failed for the type queryResponse which, with a bit of lookup seems to be a struct type. In Go you cannot natively convert a struct instance to its constituent bytes without encoding using gob or other means.
Perhaps your intention was to use the Key record in the struct for un-marshalling
json.Unmarshal([]byte(queryResponse.Key), &qry_key_json)

Go-Gin binding data with one-to-many relationship

I'm new to Golang and Gin framework, I have created two models
type Product struct {
gorm.Model
Name string
Media []Media
}
type Media struct {
gorm.Model
URI string
ProductID uint
}
and I send a POST request to save a new product, the body was:
{
"name": "Product1",
"media": [
"https://server.com/image1",
"https://server.com/image2",
"https://server.com/image3",
"https://server.com/video1",
"https://server.com/video2"
]
}
And I save a new product using this code
product := Product{}
if err := context.ShouldBindJSON(product); err != nil { // <-- here the error
context.String(http.StatusBadRequest, fmt.Sprintf("err: %s", err.Error()))
return
}
tx := DB.Create(&product)
if tx.Error != nil {
context.String(http.StatusBadRequest, fmt.Sprintf("err: %s", tx.Error))
return
}
the return error message is
err: json: cannot unmarshal string into Go struct field Product.Media of type models.Media
I know that ShouldBindJSON can't convert media-string to media-object, but what is the best practice to do this?
Your payload doesn't match the model. In the JSON body, media is an array of strings, whereas in the model it's a struct with two fields and the embedded gorm model.
If you can't change anything of your current setup, implement UnmarshalJSON on Media and set the URI field from the raw bytes. In the same method you may also initialize ProductID to something (if needed).
func (m *Media) UnmarshalJSON(b []byte) error {
m.URI = string(b)
return nil
}
Then the binding will work as expected:
product := Product{}
// pass a pointer to product
if err := context.ShouldBindJSON(&product); err != nil {
// handle err ...
return
}
fmt.Println(product) // {Product1 [{"https://server.com/image1" 0} ... }

Partial updates of objects

I want to enable update functionality for my User object in my fiber/gorm backend. It works fine when I update all fields together using the Save function. However, when I do not have all fields present in the update request (for example only the Birthday field but not the Phone field) it overwrites the rest of the fields with their respective null values.
func UserUpdateByID(c *fiber.Ctx) error {
db := database.DBConn
// Parse the body to fit user entity
user := entities.User{}
if err := c.BodyParser(&user); err != nil {
return c.Status(500).SendString(err.Error())
}
// Update record
record := db.Save(&user)
if record.Error != nil {
return c.Status(500).SendString(record.Error.Error())
}
return c.JSON(record.Value)
When I change the line with record := db.Save(&user) to
mappedData, _ := StructToMap(user)
record := db.Model(&entities.User{}).Update(mappedData)
I receive the error that Update can not handle map of interfaces: sql: converting argument $10 type: unsupported type map[string]interface {}, a map
Update 1:
The mentioned StructToMap function looks like this:
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
data, err := json.Marshal(obj)
if err != nil {
return
}
err = json.Unmarshal(data, &newMap) // Convert to a map
return
}
Update 2:
The User object looks like:
type User struct {
gorm.Model
Identity string
Birthday time.Time
Phone string
City string
...
ActivityData []Activity
}
Looking, on gorm doc(https://gorm.io/docs/update.html), you can do something like this :
Use the Updates instead of Update.
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
You can also use a db.Debug, to show the final query that gorm made, and see if matches with what are you expecting.

Reuse or cast structs for validation, API results, and DB saves in Go

I'm trying to write an API that will do different things with the data depending on its purpose.
API results - The API should expose certain fields to the user
Validation - Validation should handle different schemas e.g. login form doesn't require Name, but register does
Database - The database should save everything, including password etc. - if I try using the same struct for the API and DB with json:"-" or un-exporting the field the database save also ignores the field
Some sample code is below with a couple of comments in capitals to show where I ideally need to type cast to change the data. As they are different structs, they cannot be type cast, so I get an error. How can I fix this?
Alternatively, what is a better way of doing different things with the data without having lots of similar structs?
// IDValidation for uuids
type IDValidation struct {
ID string `json:"id" validate:"required,uuid"`
}
// RegisterValidation for register form
type RegisterValidation struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
// UserModel to save in DB
type UserModel struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
Active bool `json:"active,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
jwt.StandardClaims
}
// UserAPI data to display to user
type UserAPI struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Email string `json:"email"`
Active bool `json:"active,omitempty"`
}
// register a user
func register(c echo.Context) error {
u := new(UserModel)
if err := c.Bind(u); err != nil {
return err
}
// NEED TO CAST TO RegisterValidation HERE?
if err := c.Validate(u); err != nil {
return err
}
token, err := u.Register()
if err != nil {
return err
}
return c.JSON(http.StatusOK, lib.JSON{"token": token})
}
// retrieve a user
func retrieve(c echo.Context) error {
u := IDValidation{
ID: c.Param("id"),
}
if err := c.Validate(u); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// NEED TO CAST USER TO UserAPI?
user, err := userModel.GetByID(u.ID)
if err != nil {
return err
}
return c.JSON(200, lib.JSON{"message": "User found", "user": user})
}

How to use Go's type alias to make own models work with protobufs?

I've got some REST API with my models defined as Go structs.
type User struct {
FirstName string
LastName string
}
Then I've got my database methods for getting data.
GetUserByID(id int) (*User, error)
Now I'd like to replace my REST API with https://github.com/twitchtv/twirp .
Therefore I started defining my models inside .proto files.
message User {
string first_name = 2;
string last_name = 3;
}
Now I've got two User types. Let's call them the native and the proto type.
I've also got a service defined in my .proto file which returns a user to the frontend.
service Users {
rpc GetUser(Id) returns (User);
}
This generates an interface that I have to fill in.
func (s *Server) GetUser(context.Context, id) (*User, error) {
// i'd like to reuse my existing database methods
u, err := db.GetUserByID(id)
// handle error
// do more stuff
return u, nil
}
Unfortunately this does not work. My database returns a native User but the interface requires a proto user.
Is there an easy way to make it work? Maybe using type aliases?
Thanks a lot!
One way you can solve your problem is by doing the conversion manually.
type User struct {
FirstName string
LastName string
}
type protoUser struct {
firstName string
lastName string
}
func main() {
u := db() // Retrieve a user from a mocked db
fmt.Println("Before:")
fmt.Printf("%#v\n", *u) // What db returns (*protoUser)
fmt.Println("After:")
fmt.Printf("%#v\n", u.AsUser()) // What conversion returns (User)
}
// Mocked db that returns pointer to protoUser
func db() *protoUser {
pu := protoUser{"John", "Dough"}
return &pu
}
// Conversion method (converts protoUser into a User)
func (pu *protoUser) AsUser() User {
return User{pu.firstName, pu.lastName}
}
The key part is the AsUser method on the protoUser struct.
There we simply write our custom logic for converting a protoUser into a User type we want to be working with.
Working Example
As #Peter mentioned in the comment section.
I've seen a project which made it with a custom Convert function. It converts the Protobuf to local struct via json.Unmarshal, not sure how's the performance but it's a way to go.
Preview Code PLAYGROUND
// Convert converts the in struct to out struct via `json.Unmarshal`
func Convert(in interface{}, out interface{}) error {
j, err := json.Marshal(in)
if err != nil {
return err
}
err = json.Unmarshal(j, &out)
if err != nil {
return err
}
return nil
}
func main() {
// Converts the protobuf struct to local struct via json.Unmarshal
var localUser User
if err := convert(protoUser, &localUser); err != nil {
panic(err)
}
}
Output
Before:
main.ProtoUser{FirstName:"John", LastName:"Dough"}
After:
main.User{FirstName:"John", LastName:"Dough"}
Program exited.

Resources