Description

Binding uuid.UUID using ShouldBind gives error ["45e1f85e-bca5-458d-bd9c-c56edd8f847b"] is not valid value for uuid.UUID

How to reproduce

Used Postman to make request: Content-Type: multipart/form-data Body with form-data: key: colorID value: 45e1f85e-bca5-458d-bd9c-c56edd8f847b

// Go model in which I want to map
type Test struct {
     colorID uuid.UUID `binding:"required" form:"colorID"`
}

func test(ctx *gin.Context) {
    t := Test{}
    err := ctx.ShouldBind(&t)
    if err != nil {
    panic(err)
    }
}

Actual result

["45e1f85e-bca5-458d-bd9c-c56edd8f847b"] is not valid value for uuid.UUID

Comment From: quentinvernot

I have the same issue when attempting to bind with url-safe base64 values, I couldn't find a way to define a custom type handle the binding and error handling on its own and had to basically decode the string and handle the error in every handler. it's possible to use custom types when using json.Unmarshal, but, even though ShouldBind falls back to using enconding/json as well, the input value isn't json in those cases (it's a string for uuid.UUID and my case), and the binding fails.

Maybe TextUnmarshaler could be used as another fallback? uuid.UUID implements the UnmarshalText method, and it would be easy to do for custom types too.

It could be seen as API-breaking though, since TextUnmarshaler is a pretty common interface, people using ShouldBind to bind to structs with non-primitive fields may have surprises.

Comment From: vivl4725

I have this problem too, but I found solution!

You can change uuid.UUID to string and place in binding uuid tag!

Comment From: damarowen

If you use github.com/google/uuid package, you can use uuid.Parse

example: _ID , err := uuid.Parse(dto.ID) c, err := s.CampaignRepository.FindById(_ID)

reference : https://stackoverflow.com/questions/62952279/how-to-convert-a-uuid-string-into-uuid-type

Comment From: hungtcs

+1. Any way to serialization and deserialization uri bindding or query bindding?

Comment From: AnatolyUA

This function can be used to try to assign UUID to reflect.Value.

package binding

import (
    uuid "github.com/satori/go.uuid"
    "github.com/stretchr/testify/assert"
    "reflect"
    "testing"
)

func tryAssignUUID(s string, value reflect.Value) bool {
    l := len(s)
    if value.Kind() == reflect.Array &&
        value.Len() == 16 &&
        value.Index(0).Kind() == reflect.Uint8 &&
        (l == 32 || l == 36 || l == 38 || l == 41 || l == 45) {
        if uid, uErr := uuid.FromString(s); uErr == nil {
            value.Set(reflect.ValueOf(uid))
            return true
        }
    }
    return false
}

func Test_tryAssignUUID(t *testing.T) {
    mm := map[string][]string{
        "id":  []string{"df67786a-a543-4958-87ca-426954c626d5"},
        "ids": []string{"df67786a-a543-4958-87ca-426954c626d5", "68dc3815-6ab5-4883-81a1-96eff25b659f"},
    }
    type UUIDs struct {
        Id  uuid.UUID   `uri:"Id"`
        Ids []uuid.UUID `uri:"Ids"`
    }

    var received UUIDs
    if err := mapFormByTag(&received, mm, "uri"); err != nil {
        t.Error(err)
    }

    expected := UUIDs{
        Id: uuid.FromStringOrNil("df67786a-a543-4958-87ca-426954c626d5"),
        Ids: []uuid.UUID{
            uuid.FromStringOrNil("df67786a-a543-4958-87ca-426954c626d5"),
            uuid.FromStringOrNil("68dc3815-6ab5-4883-81a1-96eff25b659f"),
        },
    }

    assert.Equal(t, received, expected)
}

To make it work with arrays I added a case to setWithProperType like:

    case reflect.Array:
        if tryAssignUUID(val, value) {
            return nil
        }
        return errUnknownType

And modification for setByForm:

    case reflect.Array:
        if !ok {
            vs = []string{opt.defaultValue}
        }
        if len(vs) != value.Len() {
            if len(vs) == 1 && tryAssignUUID(vs[0], value) {
                return true, nil
            }
            return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
        }
        return true, setArray(vs, value, field)

Comment From: AnatolyUA

It would be preferable to merge #3045, though.