Description

So I'm testing my edit functionality, when I got the error I mentioned above. IIRC, that feature was already working properly before, but I might have made some changes down the line that broke it. The tables that are relevant to this error has a struct that looks like this:

type ActuatorDeviceInfo struct {
    gorm.Model
    ID                 string         `gorm:"primarykey; size:40;"`
    DeviceName         string         `json:"device_name"`
    TypeId             string         `gorm:"size:40; index; not null" json:"type_id"`
    Type               ActuatorType   `gorm:"foreignKey:TypeId; constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"type"`
    Status             bool           `gorm:"default:false; column:statusz"`
    ParentEdgeDeviceId *string        `gorm:"size:40; index; not null" json:"parent_edge_device_id" binding:"required"`
    ParentEdgeDevice   EdgeDeviceInfo `gorm:"foreignKey:ParentEdgeDeviceId; constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"parent_edge_device"`
}

type EdgeDeviceInfo struct {
    gorm.Model
    ID          string  `gorm:"primarykey; size:40;"`
    Name        *string `gorm:"not null" binding:"required" json:"name"`
    Description string  `json:"description"`
    Status      bool    `gorm:"default:false; column:statusz"`
    APIKey      string  `json:"api_key"`
    Passcode    string  `gorm:"default:0000"`
}

The field that is getting mentioned in the error is EdgeDeviceInfo's Name field, and the error is thrown by a call to *gin.Context.ShouldBind(). Which I call like this:

var actuator_device_info models.ActuatorDeviceInfo
err := ctx.ShouldBind(&actuator_device_info)
if err != nil {
    ctx.JSON(http.StatusBadRequest, gin.H{
        "error2": err.Error(),
    })
    return
} 

I already saw this issue that's why I also added a pointer and binding:required to ParentEdgeDeviceId, but it didn't work. Why does gin need to check the fields of the referenced table?

Environment

  • go version: go version go1.20.4 windows/amd64
  • gin version (or commit ref): v1.7.7
  • operating system: Windows 10 64bit

Comment From: VarusHsu

To use ShouldBind(), you should special "content-type:application/json" on your http request headers. This is my code:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

type ActuatorDeviceInfo struct {
    gorm.Model
    ID         string `gorm:"primarykey; size:40;"`
    DeviceName string `json:"device_name"`
    TypeId     string `gorm:"size:40; index; not null" json:"type_id"`
    //Type               ActuatorType   `gorm:"foreignKey:TypeId; constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"type"`
    Status             bool           `gorm:"default:false; column:statusz"`
    ParentEdgeDeviceId *string        `gorm:"size:40; index; not null" json:"parent_edge_device_id" binding:"required"`
    ParentEdgeDevice   EdgeDeviceInfo `gorm:"foreignKey:ParentEdgeDeviceId; constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"parent_edge_device"`
}

type EdgeDeviceInfo struct {
    gorm.Model
    ID          string  `gorm:"primarykey; size:40;"`
    Name        *string `gorm:"not null" binding:"required" json:"name"`
    Description string  `json:"description"`
    Status      bool    `gorm:"default:false; column:statusz"`
    APIKey      string  `json:"api_key"`
    Passcode    string  `gorm:"default:0000"`
}

func main() {
    e := gin.Default()
    e.POST("/actuator_device", func(ctx *gin.Context) {
        var actuatorDeviceInfo ActuatorDeviceInfo
        err := ctx.ShouldBind(&actuatorDeviceInfo)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error2": err.Error(),
            })
            return
        }
    })
    http.ListenAndServe(":8080", e)
}
go mod init demo
go mod tidy
go run main.go

Then, just run curl in another terminal.

curl --location 'localhost:8080/actuator_device' \
--header 'Content-Type: application/json' \
--data '{
    "parent_edge_device":{
        "name": "123"
    },
    "parent_edge_device_id": "abc"
}'

Hope my solution helpful to you.

Comment From: VarusHsu

By the way, The field with the tag binding:"required" must be provide in your json.Otherwise,ShouldBind will throw error. So in your case, I guess may you didn't special 'content-type' :)

Comment From: VarusHsu

This is comment of ShouldBind https://github.com/gin-gonic/gin/blob/c2ba8f19ec19914b73290c53a32de479cd463555/context.go#L685

Comment From: rickyManalo

Well, all my other endpoints are working just fine, so I don't think I'm really missing anything. I call my APIs using Retrofit on Android, not cURL. "The field with the tag binding:"required" must be provide in your json" - Why? That just doesn't make sense. Good thing that required field is just one level above in the relationship, what if that required field was like 3 levels above? And how would I even send that thing?

Comment From: rickyManalo

Anyone who has any hint for the solution to this?