Repository navigation
Problem with Unmarshalling Extensions #300
Description
I wanted to add the port_security_enabled extension and ran into some problems...
The extension adds support for extra fields in the request and extra fields in the results. I wanted to tackle the latter first, thinking it would be as simple as the other extensions (volume tenants, server availability zones) that were recently added. Reconfiguring the networks package to account for the modified Extract functions/methods caused other network extension packages to break.
Things were going well with fixing the broken extensions until I ran into the provider extension which requires UnmarshalJSON to smooth over the SegmentationID field.
It looks like there's difficulties triggering UnmarshalJSON on anonymous/nested structs:
- golang github issue 17877
- this might also be relevant.
The result is that the following pattern no longer works:
type NetworkWithExt struct {
networks.Network
provider.NetworkProviderExt
}UnmarshalJSON is triggered, but it strips out the Network struct. This can be worked around by embedding networks.Network in the provider.NetworkProviderExt struct, but that breaks the above pattern and might possibly break when someone wants to add another extension (portsbinding) to the mix.
The latest 4 commits of this branch are my attempt to cleanly show the problem. There are some notes in provider's unit tests with some additional information.
I'm not sure what the best solution would be here. I like the ability to compose structures with extended fields, but if it only works on extensions that don't require type modifications, that makes things a bit complicated.
I think the magical best solution would be if there was a way to retrieve a network or server result and it automatically included all of the extensions, but I have no idea how that would work. Each extension somehow register itself as an extension to its parent. When the parent is unmarshalled, it applies an extract on each registered extension. Since extract would only grab the fields configured in its struct, it would ignore all other fields, which is kind of what's done here. Of course, there's been the discovery of two resources that place arbitrary fields in the JSON body.
In any case, I'm stuck. It's entirely possible I'm overlooking something really simple, too.
Activity
jrperritt commentedon Mar 18, 2017
I'm not sure when I'd have time to look into this. My initial thought is that defining
UnmarshalJSONonNetworkshould fix it:jtopjian commentedon Mar 18, 2017
No worries.
When this is in place,
UnmarshalJSONon the provider struct isn't triggered:jrperritt commentedon Mar 18, 2017
Ah, I see. After reading the package documentation for
UnmarshalJSON, it sounds like your findings align with the intended behavior (namely, only one of theUnmarshalJSONmethods will get called). That is a bug (in Gophercloud), and I think it can be fixed by adding some logic in thegophercloudpackage'sresults.gofile. Since this is something that I've claimed should (currently) be workable, I'll prioritize it for Monday.jtopjian commentedon Mar 18, 2017
Sounds good and no problem if you can't get to this any time soon.
I was fiddling around and came up with the following:
Which I was invoking by:
I ran out of time and had to stop, but the idea was to inventory a struct, call Extract on each one, which should call
UnmarshalJSONon each, and then somehow stitch everything together as one result.jrperritt commentedon Mar 22, 2017
Go issue #19194 sounds like it's close to (if not exactly) this.
jtopjian commentedon Aug 3, 2017
I felt like taking a stab at this again tonight.
I came up with https://play.golang.org/p/5R7IJQaBWs and was floored when it actually worked. So I started reviewing #304 to see if I could implement it there. I backtracked to my branch here and changed
extractIntoPtrto this:And the
networking/v2/extensions/providerTestGet,TestCreate, andTestUpdatetests passed!I haven't run a full suite of tests yet. I need to look at the slice stuff, do a full review of that
networkv2-extensions-refactorbranch, probably rebase it, etc. But maybe this is going in the right direction?jrperritt commentedon Aug 3, 2017
Maybe! See where it takes you :)
jtopjian commentedon Aug 4, 2017
I got it! But I need some help.
Handling slices of a "composed" struct was much harder than a plain composed struct.
The core issue I came upon is that the pagination code triggers
IsEmpty, which (in the case of Networks), triggersExtractNetworks, which triggersExtractNetworksInto.ExtractNetworksIntois what we want to explicitly call when extracting a result into a composed slice struct.So pagination is sending the following to
ExtractNetworksInto:[]networks.NetworkWhile we want to also send:
When you start stripping the layers away to iterate on the fields, the former will iterate on the actual
networks.Networkfields and try to unmarshal the result into, saynetworks.Network.ID. What we want is to ignore that and only iterate on our composed struct.I couldn't think of a clean way to do that, so I hacked in a boolean just as a proof-of-concept.
Does that make sense?
I made a new commit to this branch.
You can check it out and do:
All tests will pass. It's now a matter of cleaning the implementation up.
jrperritt commentedon Aug 4, 2017
Maybe? It sounds like the part you want help with is getting rid of the
composeparameter (since leaving it in is probably a non-starter). To check for embedded fields, you can try something like this:https://github.com/gophercloud/gophercloud/pull/304/files#diff-6ccb1cf4a35d1aa16270b9ffeed1e74eR118
jtopjian commentedon Aug 4, 2017
Yes, exactly. And that was perfect. I just clobbered my last commit with a new solution using the
Anonymouscheck.All unit tests pass, but I realized this implementation is simply luck. The idea of using
NewDecodercame some example in the reflect package docs. It seems to be doing some kind of deferred unmarshalling or something. If I attempt to remove line 128, things blow up.I also got very tangled in reflect-land.
typeOfVwas most likely the result of throwing things at this until something stuck.In short, I have no idea what I just came up with :)
edit: bah. looks like there's one test in all of Gophercloud that fails... I'll look into that.
jtopjian commentedon Aug 7, 2017
Fixed by #443
Vpnaas: VPN service resource (gophercloud#300)