mennanov / fieldmask-utils

Protobuf Field Mask Go utils
MIT License
229 stars 26 forks source link

question: Is it possible to map from a struct to an identical struct with pointer fields? #39

Closed Omar-V2 closed 6 months ago

Omar-V2 commented 8 months ago

Hi,

I'm wondering if its possible to apply a field mask and map to a destination struct where all fields are pointers and any fields excluded after applying the mask are set to nil - as shown in the code snippet below:


package main

import (
  "fmt"

  fmutils "github.com/mennanov/fieldmask-utils"
)

func main() {
    type Source struct {
        FieldOne string
        FieldTwo int
    }

    type Dest struct {
        FieldOne *string
        FieldTwo *int
    }

    source := Source{FieldOne: "hello", FieldTwo: 5}
    dest := new(Dest)

    mask := fmutils.MaskFromString("FieldTwo")
    err := fmutils.StructToStruct(mask, source, &dest)
    fmt.Println("error", err)
    fmt.Println(dest)
}

However, I encounter the following error: error src <int Value>, int is not addressable

My use case here is that I'm trying to implement an gRPC Update endpoint that supports partial updates and I'm using a SQL query builder (squirrel) so I need a way that I can dynamically construct the UPDATE query and only include fields that were contained in the update mask. So in the example above, it would be any non nil fields on the Dest struct.

The reason I don't just use an identical struct (no pointers), is because one wouldn't be able to distinguish between go zero values and values that a user actual explicitly provided in their update request (e.g. if they wanted to set an integer field to zero).

I did think about using the StructToMap method but then there is also some challenges with mapping between the field names in my proto message and the column names in my DB.

I also tried and saw your other repo https://github.com/mennanov/fmutils and previously I had pretty much the same pattern you had in this test but I didn't like the fact I had to do an extra request to fetch the existing resource in the database for every update request.

Do you know of any elegant ways to accomplish dynamic UPDATE query generation? I'd love to hear any thoughts you might have on this!

Also a little side question: in fmutils repo I can simply do

fmutils.Filter(protoMessage, req.Msg.UpdateMask.Paths) and everything works correctly, whereas in this repo it seems that one must apply camel casing first to the proto FieldMask?

Thanks!

mennanov commented 8 months ago

Are you using something like gorm for your SQL models? Having pointers to integers in structs seems weird because it requires dereferencing when the actual value of the integer is needed which may cause poor performance in case this operation is frequent. If you need to know whether an integer field has a value set or not i'd use something like:

type Dest struct {
  HasFieldOne bool
  FieldOne string

  HasFieldTwo bool
  FieldTwo int
}

Consider using smth like this https://github.com/moznion/go-optional

However, I encounter the following error: error src , int is not addressable

I don't think such a behavior is currently supported by this library at this moment. Contributions are welcome, but they should not break the current behavior of this library, only extend it.

Do you know of any elegant ways to accomplish dynamic UPDATE query generation? I'd love to hear any thoughts you might have on this!

Unfortunately i'm not aware of any well-known elegant way of doing this. Make sure that you validate/sanitize everything coming from the request when constructing a SQL query to avoid SQL injections: you will have to define a set of allowed UPDATE fields beforehand instead of generating a SQL query directly from the request.

fmutils.Filter(protoMessage, req.Msg.UpdateMask.Paths) and everything works correctly, whereas in this repo it seems that one must apply camel casing first to the proto FieldMask?

This is because this library is more generic: it allows working with arbitrary structs in go whereas fmutils is for protobufs only and protobuf API already takes care of the fields naming.