monadicstack/frodo

Param assignment should support embedded structs

Closed this issue · 4 comments

For example, a service interface like this...

// PUT /services/:Parent.ID
Update(context.Context, *Child) (*Child, error)

...with a struct defined to embed a "parent" like this...

type Parent struct {
  ID uint
}

type Child struct {
  Parent
  Name string
}

Looks like a case I just missed when creating the rpc.jsonBinder. I'll need to do some more triage to determine the actual fix. Under the hood, I'm just relying on the standard library's JSON encoding/decoding to handle this stuff. In your example, the binder takes the param data and builds the equivalent JSON structure:

# /services/12345 gives the following param map value
"Parent.ID"=12345

# Frodo converts it to the following JSON
{ "Parent": { "ID": 12345 } }

With that JSON in hand, Frodo feeds that a standard JSON decoder and lets it do its thing. I need to see if I'm building the wrong JSON or what. Frodo uses some reflection to determine if the right-hand-side should be an object/string/bool/etc, so I'm probably flubbing that check on embedded fields.

Ok, but know that the JSON marshaler does not include the "Parent" key when it serializes an embedded struct: https://play.golang.org/p/dNCU_MW5Rkz

Unmarshal won't deal with the content you suggest, either: https://play.golang.org/p/di4uL_H_E6e

Yeah, once I did some poking I noticed the same thing. In your example the proper path should be

// PUT /services/:ID

The JSON decoder will understand that it needs to update "Parent.ID" on the &out value. As Frodo follows the semantics of the standard library, that's what I'm going to aim for.

That being said, :ID didn't work either. I found the issue and have a fix ready to merge in. One unit of work the binder does is look at your binding value - let's pretend User.Profile.Name=Rob. It has to recursively traverse the fields/types of your request struct in order to determine the proper data type of "Name". Since we're converting the parameter value to JSON, I need to know if Bob should be quoted or unquoted in the final JSON. Name is a string so it will be quoted, but if we were looking at User.Profile.Age=40 then I'd see that the Age field is a uint. The JSON value should be unquoted in that case:

{ "User": { "Profile": { "Name": "Rob" } } }
{ "User": { "Profile": { "Age": 40 } } }

In your example, we were only looking for ID in the request struct; not any of the embedded fields. I added some checks in the field iteration that recursively checks embedded fields as well. As a result, :ID will work as expected for your parameter, but :Parent.ID will still not work - but that's how the standard library works.

👍 that sounds good. FWIW, I did expect that it'd work with just .ID at first, so I think you're fix is better than expecting folks to use Parent.ID for embedded structs.