2018-05-22 19:43:36 +00:00
|
|
|
// Copyright 2016-2018, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2017-01-14 15:40:13 +00:00
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
package mapper
|
2017-01-14 15:40:13 +00:00
|
|
|
|
|
|
|
import (
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
"reflect"
|
2017-01-14 15:40:13 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
2022-06-16 22:43:23 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2017-01-14 15:40:13 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type bag struct {
|
|
|
|
Bool bool
|
|
|
|
BoolP *bool
|
|
|
|
String string
|
|
|
|
StringP *string
|
|
|
|
Float64 float64
|
|
|
|
Float64P *float64
|
2017-01-14 17:42:05 +00:00
|
|
|
Strings []string
|
|
|
|
StringsP *[]string
|
2017-01-14 15:40:13 +00:00
|
|
|
}
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
func TestFieldMapper(t *testing.T) {
|
2017-06-01 21:01:26 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
md := New(nil)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
tree := map[string]interface{}{
|
2017-01-14 17:42:05 +00:00
|
|
|
"b": true,
|
|
|
|
"s": "hello",
|
|
|
|
"f": float64(3.14159265359),
|
|
|
|
"ss": []string{"a", "b", "c"},
|
|
|
|
}
|
2017-01-14 15:40:13 +00:00
|
|
|
|
|
|
|
// Try some simple primitive decodes.
|
|
|
|
var s bag
|
Tidy up more lint
This change fixes a few things:
* Most importantly, we need to place a leading "." in the paths
to Gometalinter, otherwise some sub-linters just silently skip
the directory altogether. errcheck is one such linter, which
is a very important one!
* Use an explicit Gometalinter.json file to configure the various
settings. This flips on a few additional linters that aren't
on by default (line line length checking). Sadly, a few that
I'd like to enable take waaaay too much time, so in the future
we may consider a nightly job (this includes code similarity,
unused parameters, unused functions, and others that generally
require global analysis).
* Now that we're running more, however, linting takes a while!
The core Lumi project now takes 26 seconds to lint on my laptop.
That's not terrible, but it's long enough that we don't want to
do the silly "run them twice" thing our Makefiles were previously
doing. Instead, we shall deploy some $$($${PIPESTATUS[1]}-1))-fu
to rely on the fact that grep returns 1 on "zero lines".
* Finally, fix the many issues that this turned up.
I think(?) we are done, except, of course, for needing to drive
down some of the cyclomatic complexity issues (which I'm possibly
going to punt on; see pulumi/lumi#259 for more details).
2017-06-22 19:09:46 +00:00
|
|
|
err := md.DecodeValue(tree, reflect.TypeOf(bag{}), "b", &s.Bool, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, tree["b"], s.Bool)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "b", &s.BoolP, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, tree["b"], *s.BoolP)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "s", &s.String, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, tree["s"], s.String)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "s", &s.StringP, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, tree["s"], *s.StringP)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "f", &s.Float64, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, tree["f"], s.Float64)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "f", &s.Float64P, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, tree["f"], *s.Float64P)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "ss", &s.Strings, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, tree["ss"], s.Strings)
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "ss", &s.StringsP, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, tree["ss"], *s.StringsP)
|
|
|
|
|
|
|
|
// Ensure interface{} conversions work:
|
|
|
|
var sif string
|
Tidy up more lint
This change fixes a few things:
* Most importantly, we need to place a leading "." in the paths
to Gometalinter, otherwise some sub-linters just silently skip
the directory altogether. errcheck is one such linter, which
is a very important one!
* Use an explicit Gometalinter.json file to configure the various
settings. This flips on a few additional linters that aren't
on by default (line line length checking). Sadly, a few that
I'd like to enable take waaaay too much time, so in the future
we may consider a nightly job (this includes code similarity,
unused parameters, unused functions, and others that generally
require global analysis).
* Now that we're running more, however, linting takes a while!
The core Lumi project now takes 26 seconds to lint on my laptop.
That's not terrible, but it's long enough that we don't want to
do the silly "run them twice" thing our Makefiles were previously
doing. Instead, we shall deploy some $$($${PIPESTATUS[1]}-1))-fu
to rely on the fact that grep returns 1 on "zero lines".
* Finally, fix the many issues that this turned up.
I think(?) we are done, except, of course, for needing to drive
down some of the cyclomatic complexity issues (which I'm possibly
going to punt on; see pulumi/lumi#259 for more details).
2017-06-22 19:09:46 +00:00
|
|
|
err = md.DecodeValue(map[string]interface{}{"x": interface{}("hello")},
|
|
|
|
reflect.TypeOf(bag{}), "x", &sif, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, "hello", sif)
|
|
|
|
|
|
|
|
var sifs []string
|
Tidy up more lint
This change fixes a few things:
* Most importantly, we need to place a leading "." in the paths
to Gometalinter, otherwise some sub-linters just silently skip
the directory altogether. errcheck is one such linter, which
is a very important one!
* Use an explicit Gometalinter.json file to configure the various
settings. This flips on a few additional linters that aren't
on by default (line line length checking). Sadly, a few that
I'd like to enable take waaaay too much time, so in the future
we may consider a nightly job (this includes code similarity,
unused parameters, unused functions, and others that generally
require global analysis).
* Now that we're running more, however, linting takes a while!
The core Lumi project now takes 26 seconds to lint on my laptop.
That's not terrible, but it's long enough that we don't want to
do the silly "run them twice" thing our Makefiles were previously
doing. Instead, we shall deploy some $$($${PIPESTATUS[1]}-1))-fu
to rely on the fact that grep returns 1 on "zero lines".
* Finally, fix the many issues that this turned up.
I think(?) we are done, except, of course, for needing to drive
down some of the cyclomatic complexity issues (which I'm possibly
going to punt on; see pulumi/lumi#259 for more details).
2017-06-22 19:09:46 +00:00
|
|
|
err = md.DecodeValue(map[string]interface{}{"arr": []interface{}{"a", "b", "c"}},
|
|
|
|
reflect.TypeOf(bag{}), "arr", &sifs, false)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, []string{"a", "b", "c"}, sifs)
|
2017-01-14 15:40:13 +00:00
|
|
|
|
|
|
|
// Ensure missing optional fields are ignored:
|
|
|
|
s.String = "x"
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "missing", &s.String, true)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, "x", s.String)
|
|
|
|
|
|
|
|
// Try some error conditions; first, wrong type:
|
|
|
|
s.String = "x"
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "b", &s.String, false)
|
2023-12-08 06:40:14 +00:00
|
|
|
assert.EqualError(t, err, "Field 'b' on 'mapper.bag' must be a 'string'; got 'bool' instead")
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, "x", s.String)
|
|
|
|
|
|
|
|
// Next, missing required field:
|
|
|
|
s.String = "x"
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.DecodeValue(tree, reflect.TypeOf(bag{}), "missing", &s.String, false)
|
2023-12-08 06:40:14 +00:00
|
|
|
assert.EqualError(t, err, "Missing required field 'missing' on 'mapper.bag'")
|
2017-01-14 15:40:13 +00:00
|
|
|
assert.Equal(t, "x", s.String)
|
|
|
|
}
|
2017-01-14 17:42:05 +00:00
|
|
|
|
|
|
|
type bagtag struct {
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
String string `pulumi:"s"`
|
|
|
|
StringSkip string `pulumi:"sc,skip"`
|
|
|
|
StringOpt string `pulumi:"so,optional"`
|
|
|
|
StringSkipOpt string `pulumi:"sco,skip,optional"`
|
|
|
|
MapOpt map[string]interface{} `pulumi:"mo,optional"`
|
2017-01-14 17:42:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-16 22:43:23 +00:00
|
|
|
type AnInterface interface {
|
|
|
|
isAnInterface()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapperEncode(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
bag := bagtag{
|
|
|
|
String: "something",
|
|
|
|
StringOpt: "ohmv",
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
MapOpt: map[string]interface{}{
|
|
|
|
"a": "something",
|
|
|
|
"b": nil,
|
|
|
|
},
|
2022-06-16 22:43:23 +00:00
|
|
|
}
|
|
|
|
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
md := &mapper{}
|
|
|
|
var err error
|
|
|
|
var m map[string]interface{}
|
|
|
|
|
|
|
|
// Nils
|
|
|
|
m, err = md.Encode(nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, m, 0)
|
|
|
|
|
|
|
|
// Nil (interface)
|
|
|
|
m, err = md.Encode((AnInterface)(nil))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, m, 0)
|
|
|
|
|
|
|
|
// Structs
|
|
|
|
m, err = md.encode(reflect.ValueOf(bag))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "something", m["s"])
|
|
|
|
assert.Equal(t, "ohmv", m["so"])
|
|
|
|
assert.Equal(t, map[string]interface{}{"a": "something", "b": nil}, m["mo"])
|
|
|
|
|
|
|
|
// Pointers
|
|
|
|
m, err = md.encode(reflect.Zero(reflect.TypeOf(&bag)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, m)
|
|
|
|
m, err = md.encode(reflect.ValueOf(&bag))
|
2023-10-13 09:46:07 +00:00
|
|
|
require.NoError(t, err)
|
2022-06-16 22:43:23 +00:00
|
|
|
assert.Equal(t, "something", m["s"])
|
|
|
|
assert.Equal(t, "ohmv", m["so"])
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
assert.Equal(t, map[string]interface{}{"a": "something", "b": nil}, m["mo"])
|
|
|
|
}
|
2022-06-16 22:43:23 +00:00
|
|
|
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
func TestMapperEncodeValue(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
strdata := "something"
|
|
|
|
bag := bagtag{
|
|
|
|
String: "something",
|
|
|
|
StringOpt: "ohmv",
|
|
|
|
}
|
|
|
|
slice := []string{"something"}
|
|
|
|
mapdata := map[string]interface{}{
|
|
|
|
"a": "something",
|
|
|
|
"b": nil,
|
|
|
|
}
|
|
|
|
anyType := reflect.TypeOf((*any)(nil)).Elem()
|
|
|
|
assert.Equal(t, reflect.Interface, anyType.Kind())
|
2022-06-16 22:43:23 +00:00
|
|
|
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
md := &mapper{}
|
|
|
|
var err error
|
|
|
|
var v any
|
|
|
|
|
|
|
|
// Nils
|
|
|
|
v, err = md.EncodeValue(nil)
|
2023-10-13 09:46:07 +00:00
|
|
|
require.NoError(t, err)
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
assert.Nil(t, v)
|
|
|
|
|
|
|
|
// Bools
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(true))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, true, v)
|
|
|
|
|
|
|
|
// Ints
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(int(1)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, float64(1), v)
|
|
|
|
|
|
|
|
// Uints
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(uint(1)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, float64(1), v)
|
|
|
|
|
|
|
|
// Floats
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(float32(1.0)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, float64(1.0), v)
|
|
|
|
|
|
|
|
// Pointers
|
|
|
|
v, err = md.encodeValue(reflect.Zero(reflect.TypeOf(&strdata)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, v)
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(&strdata))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "something", v)
|
|
|
|
|
|
|
|
// Slices
|
|
|
|
v, err = md.encodeValue(reflect.Zero(reflect.TypeOf(slice)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, v)
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(slice))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []interface{}{"something"}, v)
|
|
|
|
|
|
|
|
// Maps
|
|
|
|
v, err = md.encodeValue(reflect.Zero(reflect.TypeOf(mapdata)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, v)
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(mapdata))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, map[string]interface{}{"a": "something", "b": nil}, v)
|
|
|
|
|
|
|
|
// Structs
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf(bag))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, map[string]interface{}{"s": "something", "so": "ohmv"}, v)
|
|
|
|
|
|
|
|
// Interfaces
|
|
|
|
v, err = md.encodeValue(reflect.Zero(anyType))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, v)
|
|
|
|
v, err = md.encodeValue(reflect.ValueOf("something").Convert(anyType))
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "something", v)
|
2022-06-16 22:43:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapperDecode(t *testing.T) {
|
2017-06-01 21:01:26 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2017-01-14 17:42:05 +00:00
|
|
|
var err error
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
md := New(nil)
|
2017-01-14 17:42:05 +00:00
|
|
|
|
|
|
|
// First, test the fully populated case.
|
|
|
|
var b1 bagtag
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.Decode(map[string]interface{}{
|
2017-01-14 17:42:05 +00:00
|
|
|
"s": "something",
|
|
|
|
"sc": "nothing",
|
|
|
|
"so": "ohmy",
|
|
|
|
"sco": "ohmynada",
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
"mo": map[string]interface{}{
|
|
|
|
"a": "something",
|
|
|
|
"b": nil,
|
|
|
|
},
|
2017-01-14 17:42:05 +00:00
|
|
|
}, &b1)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, "something", b1.String)
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
assert.Equal(t, "", b1.StringSkip)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, "ohmy", b1.StringOpt)
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
assert.Equal(t, "", b1.StringSkipOpt)
|
Fix panic in mapper_encode (#14498)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
Fixes a panic that occurs when attempting to encode certain values, for
example a map that contains a nil value:
```go
bag := bagtag{
String: "something",
StringOpt: "ohmv",
MapOpt: map[string]interface{}{
"a": "something",
"b": nil, // this reflect.Value is of type Interface and IsNil is true.
},
}
_ = resource.NewPropertyMap(bag) // panic here
```
New tests are provided to cover the above scenario and to cover each
type of value, including nil cases where appropriate.
Fixes: https://github.com/pulumi/pulumi-kubernetes/issues/2622
Follow-up to: https://github.com/pulumi/pulumi/pull/9810
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-04 18:31:22 +00:00
|
|
|
assert.Equal(t, map[string]interface{}{"a": "something", "b": nil}, b1.MapOpt)
|
2017-01-14 17:42:05 +00:00
|
|
|
|
|
|
|
// Now let optional fields go missing.
|
|
|
|
var b2 bagtag
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.Decode(map[string]interface{}{
|
2017-01-14 17:42:05 +00:00
|
|
|
"s": "something",
|
|
|
|
"sc": "nothing",
|
|
|
|
}, &b2)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, "something", b2.String)
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
assert.Equal(t, "", b2.StringSkip)
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, "", b2.StringOpt)
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
assert.Equal(t, "", b2.StringSkipOpt)
|
2017-01-14 17:42:05 +00:00
|
|
|
|
|
|
|
// Try some error conditions; first, wrong type:
|
|
|
|
var b3 bagtag
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.Decode(map[string]interface{}{
|
2017-01-14 17:42:05 +00:00
|
|
|
"s": true,
|
|
|
|
"sc": "",
|
|
|
|
}, &b3)
|
2023-12-08 06:40:14 +00:00
|
|
|
assert.EqualError(t, err, "1 failures decoding:\n"+
|
|
|
|
"\ts: Field 's' on 'mapper.bagtag' must be a 'string'; got 'bool' instead")
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, "", b3.String)
|
|
|
|
|
|
|
|
// Next, missing required field:
|
|
|
|
var b4 bagtag
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err = md.Decode(map[string]interface{}{}, &b4)
|
2023-12-08 06:40:14 +00:00
|
|
|
assert.EqualError(t, err, "1 failures decoding:\n"+
|
|
|
|
"\ts: Missing required field 's' on 'mapper.bagtag'")
|
2017-01-14 17:42:05 +00:00
|
|
|
assert.Equal(t, "", b4.String)
|
|
|
|
}
|
2017-01-14 18:06:55 +00:00
|
|
|
|
|
|
|
type bog struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
Boggy bogger `pulumi:"boggy"`
|
|
|
|
BoggyP *bogger `pulumi:"boggyp"`
|
|
|
|
Boggers []bogger `pulumi:"boggers"`
|
|
|
|
BoggersP *[]*bogger `pulumi:"boggersp"`
|
2017-01-14 18:06:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type bogger struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
Num float64 `pulumi:"num"`
|
2017-01-14 18:06:55 +00:00
|
|
|
}
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
func TestNestedMapper(t *testing.T) {
|
2017-06-01 21:01:26 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
md := New(nil)
|
|
|
|
|
2017-01-15 21:50:00 +00:00
|
|
|
// Test one level deep nesting (fields, arrays, pointers).
|
2017-01-14 18:06:55 +00:00
|
|
|
var b bog
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err := md.Decode(map[string]interface{}{
|
|
|
|
"boggy": map[string]interface{}{"num": float64(99)},
|
|
|
|
"boggyp": map[string]interface{}{"num": float64(180)},
|
|
|
|
"boggers": []map[string]interface{}{
|
2017-01-14 18:06:55 +00:00
|
|
|
{"num": float64(1)},
|
|
|
|
{"num": float64(2)},
|
|
|
|
{"num": float64(42)},
|
|
|
|
},
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
"boggersp": []map[string]interface{}{
|
2017-01-15 21:50:00 +00:00
|
|
|
{"num": float64(4)},
|
|
|
|
{"num": float64(8)},
|
|
|
|
{"num": float64(84)},
|
|
|
|
},
|
2017-01-14 18:06:55 +00:00
|
|
|
}, &b)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-15 21:50:00 +00:00
|
|
|
assert.Equal(t, float64(99), b.Boggy.Num)
|
|
|
|
assert.NotNil(t, b.BoggyP)
|
|
|
|
assert.Equal(t, float64(180), b.BoggyP.Num)
|
2017-01-14 18:06:55 +00:00
|
|
|
assert.Equal(t, 3, len(b.Boggers))
|
|
|
|
assert.Equal(t, float64(1), b.Boggers[0].Num)
|
|
|
|
assert.Equal(t, float64(2), b.Boggers[1].Num)
|
|
|
|
assert.Equal(t, float64(42), b.Boggers[2].Num)
|
2017-01-15 21:50:00 +00:00
|
|
|
assert.NotNil(t, b.BoggersP)
|
|
|
|
assert.Equal(t, 3, len(*b.BoggersP))
|
|
|
|
assert.NotNil(t, (*b.BoggersP)[0])
|
|
|
|
assert.Equal(t, float64(4), (*b.BoggersP)[0].Num)
|
|
|
|
assert.NotNil(t, (*b.BoggersP)[1])
|
|
|
|
assert.Equal(t, float64(8), (*b.BoggersP)[1].Num)
|
|
|
|
assert.NotNil(t, (*b.BoggersP)[2])
|
|
|
|
assert.Equal(t, float64(84), (*b.BoggersP)[2].Num)
|
|
|
|
}
|
|
|
|
|
|
|
|
type boggerdybogger struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
Bogs map[string]bog `pulumi:"bogs"`
|
|
|
|
BogsP *map[string]*bog `pulumi:"bogsp"`
|
2017-01-15 21:50:00 +00:00
|
|
|
}
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
func TestMultiplyNestedMapper(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
md := New(nil)
|
|
|
|
|
2017-01-15 21:50:00 +00:00
|
|
|
// Test multilevel nesting (maps, fields, arrays, pointers).
|
|
|
|
var ber boggerdybogger
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err := md.Decode(map[string]interface{}{
|
|
|
|
"bogs": map[string]interface{}{
|
|
|
|
"a": map[string]interface{}{
|
|
|
|
"boggy": map[string]interface{}{"num": float64(99)},
|
|
|
|
"boggyp": map[string]interface{}{"num": float64(180)},
|
|
|
|
"boggers": []map[string]interface{}{
|
2017-01-15 21:50:00 +00:00
|
|
|
{"num": float64(1)},
|
|
|
|
{"num": float64(2)},
|
|
|
|
{"num": float64(42)},
|
|
|
|
},
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
"boggersp": []map[string]interface{}{
|
2017-01-15 21:50:00 +00:00
|
|
|
{"num": float64(4)},
|
|
|
|
{"num": float64(8)},
|
|
|
|
{"num": float64(84)},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
"bogsp": map[string]interface{}{
|
|
|
|
"z": map[string]interface{}{
|
|
|
|
"boggy": map[string]interface{}{"num": float64(188)},
|
|
|
|
"boggyp": map[string]interface{}{"num": float64(360)},
|
|
|
|
"boggers": []map[string]interface{}{
|
2017-01-15 21:50:00 +00:00
|
|
|
{"num": float64(2)},
|
|
|
|
{"num": float64(4)},
|
|
|
|
{"num": float64(84)},
|
|
|
|
},
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
"boggersp": []map[string]interface{}{
|
2017-01-15 21:50:00 +00:00
|
|
|
{"num": float64(8)},
|
|
|
|
{"num": float64(16)},
|
|
|
|
{"num": float64(168)},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, &ber)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-15 21:50:00 +00:00
|
|
|
|
|
|
|
assert.Equal(t, 1, len(ber.Bogs))
|
|
|
|
b := ber.Bogs["a"]
|
|
|
|
assert.Equal(t, float64(99), b.Boggy.Num)
|
|
|
|
assert.NotNil(t, b.BoggyP)
|
|
|
|
assert.Equal(t, float64(180), b.BoggyP.Num)
|
|
|
|
assert.Equal(t, 3, len(b.Boggers))
|
|
|
|
assert.Equal(t, float64(1), b.Boggers[0].Num)
|
|
|
|
assert.Equal(t, float64(2), b.Boggers[1].Num)
|
|
|
|
assert.Equal(t, float64(42), b.Boggers[2].Num)
|
|
|
|
assert.NotNil(t, b.BoggersP)
|
|
|
|
assert.Equal(t, 3, len(*b.BoggersP))
|
|
|
|
assert.NotNil(t, (*b.BoggersP)[0])
|
|
|
|
assert.Equal(t, float64(4), (*b.BoggersP)[0].Num)
|
|
|
|
assert.NotNil(t, (*b.BoggersP)[1])
|
|
|
|
assert.Equal(t, float64(8), (*b.BoggersP)[1].Num)
|
|
|
|
assert.NotNil(t, (*b.BoggersP)[2])
|
|
|
|
assert.Equal(t, float64(84), (*b.BoggersP)[2].Num)
|
|
|
|
|
|
|
|
assert.NotNil(t, ber.BogsP)
|
|
|
|
assert.Equal(t, 1, len(*ber.BogsP))
|
|
|
|
p := (*ber.BogsP)["z"]
|
|
|
|
assert.NotNil(t, p)
|
|
|
|
assert.Equal(t, float64(188), p.Boggy.Num)
|
|
|
|
assert.NotNil(t, p.BoggyP)
|
|
|
|
assert.Equal(t, float64(360), p.BoggyP.Num)
|
|
|
|
assert.Equal(t, 3, len(p.Boggers))
|
|
|
|
assert.Equal(t, float64(2), p.Boggers[0].Num)
|
|
|
|
assert.Equal(t, float64(4), p.Boggers[1].Num)
|
|
|
|
assert.Equal(t, float64(84), p.Boggers[2].Num)
|
|
|
|
assert.NotNil(t, p.BoggersP)
|
|
|
|
assert.Equal(t, 3, len(*p.BoggersP))
|
|
|
|
assert.NotNil(t, (*p.BoggersP)[0])
|
|
|
|
assert.Equal(t, float64(8), (*p.BoggersP)[0].Num)
|
|
|
|
assert.NotNil(t, (*p.BoggersP)[1])
|
|
|
|
assert.Equal(t, float64(16), (*p.BoggersP)[1].Num)
|
|
|
|
assert.NotNil(t, (*p.BoggersP)[2])
|
|
|
|
assert.Equal(t, float64(168), (*p.BoggersP)[2].Num)
|
2017-01-14 18:06:55 +00:00
|
|
|
}
|
2017-01-14 19:21:27 +00:00
|
|
|
|
|
|
|
type hasmap struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
Entries map[string]mapentry `pulumi:"entries"`
|
|
|
|
EntriesP map[string]*mapentry `pulumi:"entriesp"`
|
2017-01-14 19:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type mapentry struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
Title string `pulumi:"title"`
|
2017-01-14 19:21:27 +00:00
|
|
|
}
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
func TestMapMapper(t *testing.T) {
|
2017-06-01 21:01:26 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
md := New(nil)
|
|
|
|
|
2017-01-14 19:21:27 +00:00
|
|
|
// Ensure we can decode both maps of structs and maps of pointers to structs.
|
|
|
|
var hm hasmap
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err := md.Decode(map[string]interface{}{
|
|
|
|
"entries": map[string]interface{}{
|
|
|
|
"a": map[string]interface{}{"title": "first"},
|
|
|
|
"b": map[string]interface{}{"title": "second"},
|
2017-01-14 19:21:27 +00:00
|
|
|
},
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
"entriesp": map[string]interface{}{
|
|
|
|
"x": map[string]interface{}{"title": "firstp"},
|
|
|
|
"y": map[string]interface{}{"title": "secondp"},
|
2017-01-14 19:21:27 +00:00
|
|
|
},
|
|
|
|
}, &hm)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2017-01-14 19:21:27 +00:00
|
|
|
assert.Equal(t, 2, len(hm.Entries))
|
|
|
|
assert.Equal(t, "first", hm.Entries["a"].Title)
|
|
|
|
assert.Equal(t, "second", hm.Entries["b"].Title)
|
|
|
|
assert.Equal(t, 2, len(hm.EntriesP))
|
|
|
|
assert.NotNil(t, hm.EntriesP["x"])
|
|
|
|
assert.NotNil(t, hm.EntriesP["y"])
|
|
|
|
assert.Equal(t, "firstp", hm.EntriesP["x"].Title)
|
|
|
|
assert.Equal(t, "secondp", hm.EntriesP["y"].Title)
|
|
|
|
}
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
|
|
|
|
type wrap struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
C customStruct `pulumi:"c"`
|
|
|
|
CI customInterface `pulumi:"ci"`
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type customInterface interface {
|
|
|
|
GetX() float64
|
|
|
|
GetY() float64
|
|
|
|
}
|
|
|
|
|
|
|
|
type customStruct struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
X float64 `pulumi:"x"`
|
|
|
|
Y float64 `pulumi:"y"`
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *customStruct) GetX() float64 { return s.X }
|
|
|
|
func (s *customStruct) GetY() float64 { return s.Y }
|
|
|
|
|
|
|
|
func TestCustomMapper(t *testing.T) {
|
2017-06-01 21:01:26 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
Tidy up more lint
This change fixes a few things:
* Most importantly, we need to place a leading "." in the paths
to Gometalinter, otherwise some sub-linters just silently skip
the directory altogether. errcheck is one such linter, which
is a very important one!
* Use an explicit Gometalinter.json file to configure the various
settings. This flips on a few additional linters that aren't
on by default (line line length checking). Sadly, a few that
I'd like to enable take waaaay too much time, so in the future
we may consider a nightly job (this includes code similarity,
unused parameters, unused functions, and others that generally
require global analysis).
* Now that we're running more, however, linting takes a while!
The core Lumi project now takes 26 seconds to lint on my laptop.
That's not terrible, but it's long enough that we don't want to
do the silly "run them twice" thing our Makefiles were previously
doing. Instead, we shall deploy some $$($${PIPESTATUS[1]}-1))-fu
to rely on the fact that grep returns 1 on "zero lines".
* Finally, fix the many issues that this turned up.
I think(?) we are done, except, of course, for needing to drive
down some of the cyclomatic complexity issues (which I'm possibly
going to punt on; see pulumi/lumi#259 for more details).
2017-06-22 19:09:46 +00:00
|
|
|
md := New(&Opts{
|
2017-04-08 14:44:02 +00:00
|
|
|
CustomDecoders: Decoders{
|
|
|
|
reflect.TypeOf((*customInterface)(nil)).Elem(): decodeCustomInterface,
|
|
|
|
reflect.TypeOf(customStruct{}): decodeCustomStruct,
|
|
|
|
},
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
var w wrap
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
err := md.Decode(map[string]interface{}{
|
|
|
|
"c": map[string]interface{}{
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
"x": float64(-99.2),
|
|
|
|
"y": float64(127.127),
|
|
|
|
},
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
"ci": map[string]interface{}{
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
"x": float64(42.6),
|
|
|
|
"y": float64(247.9),
|
|
|
|
},
|
|
|
|
}, &w)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
assert.Equal(t, float64(-99.2), w.C.X)
|
|
|
|
assert.Equal(t, float64(127.127), w.C.Y)
|
|
|
|
assert.NotNil(t, w.CI)
|
|
|
|
assert.Equal(t, float64(42.6), w.CI.GetX())
|
|
|
|
assert.Equal(t, float64(247.9), w.CI.GetY())
|
|
|
|
}
|
|
|
|
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
func decodeCustomInterface(m Mapper, tree map[string]interface{}) (interface{}, error) {
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
var s customStruct
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
if err := m.DecodeValue(tree, reflect.TypeOf(s), "x", &s.X, false); err != nil {
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
if err := m.DecodeValue(tree, reflect.TypeOf(s), "y", &s.Y, false); err != nil {
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return customInterface(&s), nil
|
|
|
|
}
|
|
|
|
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
func decodeCustomStruct(m Mapper, tree map[string]interface{}) (interface{}, error) {
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
var s customStruct
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
if err := m.DecodeValue(tree, reflect.TypeOf(s), "x", &s.X, false); err != nil {
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
if err := m.DecodeValue(tree, reflect.TypeOf(s), "y", &s.Y, false); err != nil {
|
Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding. Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.
First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper. Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders. This
is a map from target type to a function that can decode objects into it.
Second, the AST-specific decoding logic is rewritten to use it. All AST nodes
are now supported, including definitions, statements, and expressions. The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position. The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results. This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type. Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 17:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return s, nil
|
|
|
|
}
|
Unmap properties on the receive side
This change makes progress on a few things with respect to properly
receiving properties on the engine side, coming from the provider side,
of the RPC boundary. The issues here are twofold:
1. Properties need to get unmapped using a JSON-tag-sensitive
marshaler, so that they are cased properly, etc. For that, we
have a new mapper.Unmap function (which is ultra lame -- see
pulumi/lumi#138).
2. We have the reverse problem with respect to resource IDs: on
the send side, we must translate from URNs (which the engine
knows about) and provider IDs (which the provider knows about);
similarly, then, on the receive side, we must translate from
provider IDs back into URNs.
As a result of these getting fixed, we can now properly marshal the
resulting properties back into the resource object during the plan
execution, alongside propagating and memoizing its ID.
2017-05-31 18:51:46 +00:00
|
|
|
|
|
|
|
type outer struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
Inners *[]inner `pulumi:"inners,optional"`
|
Unmap properties on the receive side
This change makes progress on a few things with respect to properly
receiving properties on the engine side, coming from the provider side,
of the RPC boundary. The issues here are twofold:
1. Properties need to get unmapped using a JSON-tag-sensitive
marshaler, so that they are cased properly, etc. For that, we
have a new mapper.Unmap function (which is ultra lame -- see
pulumi/lumi#138).
2. We have the reverse problem with respect to resource IDs: on
the send side, we must translate from URNs (which the engine
knows about) and provider IDs (which the provider knows about);
similarly, then, on the receive side, we must translate from
provider IDs back into URNs.
As a result of these getting fixed, we can now properly marshal the
resulting properties back into the resource object during the plan
execution, alongside propagating and memoizing its ID.
2017-05-31 18:51:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type inner struct {
|
2017-09-22 02:18:21 +00:00
|
|
|
A string `pulumi:"a"`
|
|
|
|
B *string `pulumi:"b,optional"`
|
|
|
|
C *string `pulumi:"c,optional"`
|
|
|
|
D float64 `pulumi:"d"`
|
|
|
|
E *float64 `pulumi:"e,optional"`
|
|
|
|
F *float64 `pulumi:"f,optional"`
|
|
|
|
G *inner `pulumi:"g,optional"`
|
|
|
|
H *[]inner `pulumi:"h,optional"`
|
Unmap properties on the receive side
This change makes progress on a few things with respect to properly
receiving properties on the engine side, coming from the provider side,
of the RPC boundary. The issues here are twofold:
1. Properties need to get unmapped using a JSON-tag-sensitive
marshaler, so that they are cased properly, etc. For that, we
have a new mapper.Unmap function (which is ultra lame -- see
pulumi/lumi#138).
2. We have the reverse problem with respect to resource IDs: on
the send side, we must translate from URNs (which the engine
knows about) and provider IDs (which the provider knows about);
similarly, then, on the receive side, we must translate from
provider IDs back into URNs.
As a result of these getting fixed, we can now properly marshal the
resulting properties back into the resource object during the plan
execution, alongside propagating and memoizing its ID.
2017-05-31 18:51:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestBasicUnmap(t *testing.T) {
|
2017-06-01 21:01:26 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
Unmap properties on the receive side
This change makes progress on a few things with respect to properly
receiving properties on the engine side, coming from the provider side,
of the RPC boundary. The issues here are twofold:
1. Properties need to get unmapped using a JSON-tag-sensitive
marshaler, so that they are cased properly, etc. For that, we
have a new mapper.Unmap function (which is ultra lame -- see
pulumi/lumi#138).
2. We have the reverse problem with respect to resource IDs: on
the send side, we must translate from URNs (which the engine
knows about) and provider IDs (which the provider knows about);
similarly, then, on the receive side, we must translate from
provider IDs back into URNs.
As a result of these getting fixed, we can now properly marshal the
resulting properties back into the resource object during the plan
execution, alongside propagating and memoizing its ID.
2017-05-31 18:51:46 +00:00
|
|
|
v2 := "v2"
|
|
|
|
v5 := float64(5)
|
|
|
|
i1v2 := "i1v2"
|
|
|
|
i1v5 := float64(15)
|
|
|
|
i2v2 := "i2v2"
|
|
|
|
i2v5 := float64(25)
|
|
|
|
i3v2 := "i3v2"
|
|
|
|
i3v5 := float64(35)
|
|
|
|
o := outer{
|
|
|
|
Inners: &[]inner{
|
|
|
|
{
|
|
|
|
A: "v1",
|
|
|
|
B: &v2,
|
|
|
|
C: nil,
|
|
|
|
D: float64(4),
|
|
|
|
E: &v5,
|
|
|
|
F: nil,
|
|
|
|
G: &inner{
|
|
|
|
A: "i1v1",
|
|
|
|
B: &i1v2,
|
|
|
|
C: nil,
|
|
|
|
D: float64(14),
|
|
|
|
E: &i1v5,
|
|
|
|
F: nil,
|
|
|
|
G: nil,
|
|
|
|
H: nil,
|
|
|
|
},
|
|
|
|
H: &[]inner{
|
|
|
|
{
|
|
|
|
A: "i2v1",
|
|
|
|
B: &i2v2,
|
|
|
|
C: nil,
|
|
|
|
D: float64(24),
|
|
|
|
E: &i2v5,
|
|
|
|
F: nil,
|
|
|
|
G: nil,
|
|
|
|
H: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
A: "i3v1",
|
|
|
|
B: &i3v2,
|
|
|
|
C: nil,
|
|
|
|
D: float64(34),
|
|
|
|
E: &i3v5,
|
|
|
|
F: nil,
|
|
|
|
G: nil,
|
|
|
|
H: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmap returns a JSON-like dictionary object representing the above structure.
|
|
|
|
for _, e := range []interface{}{o, &o} {
|
Implement mapper.Encode "for real"
This change implements `mapper.Encode` "for real" (that is, in a way
that isn't a complete embarrassment). It uses the obvious reflection
trickery to encode a tagged struct and its values as a JSON-like
in-memory map and collection of keyed values.
During this, I took the opportunity to also clean up a few other things
that had been bugging me. Namely, the presence of `mapper.Object` was
always error prone, since it isn't a true "typedef" in the sence that
it carries extra RTTI. Instead of doing that, let's just use the real
`map[string]interface{}` "JSON-map-like" object type. Even better, we
no longer require resource providers to deal with the mapper
infrastructure. Instead, the `Check` function can simply return an
array of errors. It's still best practice to return field-specific errors
to facilitate better diagnostics, but it's no longer required; and I've
added `resource.NewFieldError` to eliminate the need to import mapper.
As of this change, we can also consistently emit RPC structs with `lumi`
tags, rather than `lumi` tags on the way in and `json` on the way out.
This completes pulumi/lumi#183.
2017-06-06 00:43:52 +00:00
|
|
|
um, err := Unmap(e)
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
Unmap properties on the receive side
This change makes progress on a few things with respect to properly
receiving properties on the engine side, coming from the provider side,
of the RPC boundary. The issues here are twofold:
1. Properties need to get unmapped using a JSON-tag-sensitive
marshaler, so that they are cased properly, etc. For that, we
have a new mapper.Unmap function (which is ultra lame -- see
pulumi/lumi#138).
2. We have the reverse problem with respect to resource IDs: on
the send side, we must translate from URNs (which the engine
knows about) and provider IDs (which the provider knows about);
similarly, then, on the receive side, we must translate from
provider IDs back into URNs.
As a result of these getting fixed, we can now properly marshal the
resulting properties back into the resource object during the plan
execution, alongside propagating and memoizing its ID.
2017-05-31 18:51:46 +00:00
|
|
|
assert.NotNil(t, um)
|
|
|
|
|
|
|
|
// check outer:
|
|
|
|
assert.NotNil(t, um["inners"])
|
|
|
|
arr := um["inners"].([]interface{})
|
|
|
|
assert.Equal(t, len(arr), 1)
|
|
|
|
|
|
|
|
// check outer.inner:
|
|
|
|
inn := arr[0].(map[string]interface{})
|
|
|
|
assert.Equal(t, inn["a"], "v1")
|
|
|
|
assert.Equal(t, inn["b"], "v2")
|
|
|
|
_, hasc := inn["c"]
|
|
|
|
assert.False(t, hasc)
|
|
|
|
assert.Equal(t, inn["d"], float64(4))
|
|
|
|
assert.Equal(t, inn["e"], float64(5))
|
|
|
|
_, hasf := inn["f"]
|
|
|
|
assert.False(t, hasf)
|
|
|
|
assert.NotNil(t, inn["g"])
|
|
|
|
|
|
|
|
// check outer.inner.inner:
|
|
|
|
inng := inn["g"].(map[string]interface{})
|
|
|
|
assert.Equal(t, inng["a"], "i1v1")
|
|
|
|
assert.Equal(t, inng["b"], "i1v2")
|
|
|
|
_, hasgc := inng["c"]
|
|
|
|
assert.False(t, hasgc)
|
|
|
|
assert.Equal(t, inng["d"], float64(14))
|
|
|
|
assert.Equal(t, inng["e"], float64(15))
|
|
|
|
_, hasgf := inng["f"]
|
|
|
|
assert.False(t, hasgf)
|
|
|
|
_, hasgg := inng["g"]
|
|
|
|
assert.False(t, hasgg)
|
|
|
|
_, hasgh := inng["h"]
|
|
|
|
assert.False(t, hasgh)
|
|
|
|
|
|
|
|
// check outer.inner.inners[0]:
|
|
|
|
innh := inn["h"].([]interface{})
|
|
|
|
assert.Equal(t, len(innh), 2)
|
|
|
|
innh0 := innh[0].(map[string]interface{})
|
|
|
|
assert.Equal(t, innh0["a"], "i2v1")
|
|
|
|
assert.Equal(t, innh0["b"], "i2v2")
|
|
|
|
_, hash0c := inng["c"]
|
|
|
|
assert.False(t, hash0c)
|
|
|
|
assert.Equal(t, innh0["d"], float64(24))
|
|
|
|
assert.Equal(t, innh0["e"], float64(25))
|
|
|
|
_, hash0f := inng["f"]
|
|
|
|
assert.False(t, hash0f)
|
|
|
|
_, hash0g := inng["g"]
|
|
|
|
assert.False(t, hash0g)
|
|
|
|
_, hash0h := inng["h"]
|
|
|
|
assert.False(t, hash0h)
|
|
|
|
|
|
|
|
// check outer.inner.inners[1]:
|
|
|
|
innh1 := innh[1].(map[string]interface{})
|
|
|
|
assert.Equal(t, innh1["a"], "i3v1")
|
|
|
|
assert.Equal(t, innh1["b"], "i3v2")
|
|
|
|
_, hash1c := inng["c"]
|
|
|
|
assert.False(t, hash1c)
|
|
|
|
assert.Equal(t, innh1["d"], float64(34))
|
|
|
|
assert.Equal(t, innh1["e"], float64(35))
|
|
|
|
_, hash1f := inng["f"]
|
|
|
|
assert.False(t, hash1f)
|
|
|
|
_, hash1g := inng["g"]
|
|
|
|
assert.False(t, hash1g)
|
|
|
|
_, hash1h := inng["h"]
|
|
|
|
assert.False(t, hash1h)
|
|
|
|
}
|
|
|
|
}
|
2022-12-05 20:08:35 +00:00
|
|
|
|
|
|
|
func TestReproduceMapStringPointerTurnaroundIssue(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
type X struct {
|
|
|
|
Args map[string]*string `pulumi:"args,optional"`
|
|
|
|
}
|
|
|
|
|
|
|
|
xToMap := func(build X) (map[string]interface{}, error) {
|
|
|
|
m, err := New(nil).Encode(build)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
xFromMap := func(pm map[string]interface{}) (X, error) {
|
|
|
|
var build X
|
|
|
|
err := New(nil).Decode(pm, &build)
|
|
|
|
if err != nil {
|
|
|
|
return X{}, err
|
|
|
|
}
|
|
|
|
return build, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
value := "value"
|
|
|
|
expected := X{
|
|
|
|
Args: map[string]*string{
|
|
|
|
"key": &value,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMap, err := xToMap(expected)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("encodedMap: %v", encodedMap)
|
|
|
|
|
|
|
|
back, err2 := xFromMap(encodedMap)
|
|
|
|
require.NoError(t, err2)
|
|
|
|
|
|
|
|
require.Equal(t, expected, back)
|
|
|
|
}
|