govendor fetch github.com/zclconf/go-cty/cty/...
This allows automatic conversions between different object types as long as the target type is a subset of the given type.
This commit is contained in:
parent
b1247bf7af
commit
93cda6dbd2
|
@ -57,6 +57,9 @@ func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
|
|||
}
|
||||
return nil
|
||||
|
||||
case out.IsObjectType() && in.IsObjectType():
|
||||
return conversionObjectToObject(in, out, unsafe)
|
||||
|
||||
case out.IsListType() && (in.IsListType() || in.IsSetType()):
|
||||
inEty := in.ElementType()
|
||||
outEty := out.ElementType()
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// conversionObjectToObject returns a conversion that will make the input
|
||||
// object type conform to the output object type, if possible.
|
||||
//
|
||||
// Conversion is possible only if the output type is a subset of the input
|
||||
// type, meaning that each attribute of the output type has a corresponding
|
||||
// attribute in the input type where a recursive conversion is available.
|
||||
//
|
||||
// Shallow object conversions work the same for both safe and unsafe modes,
|
||||
// but the safety flag is passed on to recursive conversions and may thus
|
||||
// limit the above definition of "subset".
|
||||
func conversionObjectToObject(in, out cty.Type, unsafe bool) conversion {
|
||||
inAtys := in.AttributeTypes()
|
||||
outAtys := out.AttributeTypes()
|
||||
attrConvs := make(map[string]conversion)
|
||||
|
||||
for name, outAty := range outAtys {
|
||||
inAty, exists := inAtys[name]
|
||||
if !exists {
|
||||
// No conversion is available, then.
|
||||
return nil
|
||||
}
|
||||
|
||||
if inAty.Equals(outAty) {
|
||||
// No conversion needed, but we'll still record the attribute
|
||||
// in our map for later reference.
|
||||
attrConvs[name] = nil
|
||||
continue
|
||||
}
|
||||
|
||||
attrConvs[name] = getConversion(inAty, outAty, unsafe)
|
||||
if attrConvs[name] == nil {
|
||||
// If a recursive conversion isn't available, then our top-level
|
||||
// configuration is impossible too.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here then a conversion is possible, using the attribute
|
||||
// conversions given in attrConvs.
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
attrVals := make(map[string]cty.Value, len(attrConvs))
|
||||
path = append(path, nil)
|
||||
pathStep := &path[len(path)-1]
|
||||
|
||||
for it := val.ElementIterator(); it.Next(); {
|
||||
nameVal, val := it.Element()
|
||||
var err error
|
||||
|
||||
name := nameVal.AsString()
|
||||
*pathStep = cty.GetAttrStep{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
conv, exists := attrConvs[name]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
if conv != nil {
|
||||
val, err = conv(val, path)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
}
|
||||
|
||||
attrVals[name] = val
|
||||
}
|
||||
|
||||
return cty.ObjectVal(attrVals), nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// MismatchMessage is a helper to return an English-language description of
|
||||
// the differences between got and want, phrased as a reason why got does
|
||||
// not conform to want.
|
||||
//
|
||||
// This function does not itself attempt conversion, and so it should generally
|
||||
// be used only after a conversion has failed, to report the conversion failure
|
||||
// to an English-speaking user. The result will be confusing got is actually
|
||||
// conforming to or convertable to want.
|
||||
//
|
||||
// The shorthand helper function Convert uses this function internally to
|
||||
// produce its error messages, so callers of that function do not need to
|
||||
// also use MismatchMessage.
|
||||
//
|
||||
// This function is similar to Type.TestConformance, but it is tailored to
|
||||
// describing conversion failures and so the messages it generates relate
|
||||
// specifically to the conversion rules implemented in this package.
|
||||
func MismatchMessage(got, want cty.Type) string {
|
||||
switch {
|
||||
|
||||
case got.IsObjectType() && want.IsObjectType():
|
||||
// If both types are object types then we may be able to say something
|
||||
// about their respective attributes.
|
||||
return mismatchMessageObjects(got, want)
|
||||
|
||||
default:
|
||||
// If we have nothing better to say, we'll just state what was required.
|
||||
return want.FriendlyName() + " required"
|
||||
}
|
||||
}
|
||||
|
||||
func mismatchMessageObjects(got, want cty.Type) string {
|
||||
// Per our conversion rules, "got" is allowed to be a superset of "want",
|
||||
// and so we'll produce error messages here under that assumption.
|
||||
gotAtys := got.AttributeTypes()
|
||||
wantAtys := want.AttributeTypes()
|
||||
|
||||
// If we find missing attributes then we'll report those in preference,
|
||||
// but if not then we will report a maximum of one non-conforming
|
||||
// attribute, just to keep our messages relatively terse.
|
||||
// We'll also prefer to report a recursive type error from an _unsafe_
|
||||
// conversion over a safe one, because these are subjectively more
|
||||
// "serious".
|
||||
var missingAttrs []string
|
||||
var unsafeMismatchAttr string
|
||||
var safeMismatchAttr string
|
||||
|
||||
for name, wantAty := range wantAtys {
|
||||
gotAty, exists := gotAtys[name]
|
||||
if !exists {
|
||||
missingAttrs = append(missingAttrs, name)
|
||||
continue
|
||||
}
|
||||
|
||||
// We'll now try to convert these attributes in isolation and
|
||||
// see if we have a nested conversion error to report.
|
||||
// We'll try an unsafe conversion first, and then fall back on
|
||||
// safe if unsafe is possible.
|
||||
|
||||
// If we already have an unsafe mismatch attr error then we won't bother
|
||||
// hunting for another one.
|
||||
if unsafeMismatchAttr != "" {
|
||||
continue
|
||||
}
|
||||
if conv := GetConversionUnsafe(gotAty, wantAty); conv == nil {
|
||||
unsafeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty))
|
||||
}
|
||||
|
||||
// If we already have a safe mismatch attr error then we won't bother
|
||||
// hunting for another one.
|
||||
if safeMismatchAttr != "" {
|
||||
continue
|
||||
}
|
||||
if conv := GetConversion(gotAty, wantAty); conv == nil {
|
||||
safeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty))
|
||||
}
|
||||
}
|
||||
|
||||
// We should now have collected at least one problem. If we have more than
|
||||
// one then we'll use our preference order to decide what is most important
|
||||
// to report.
|
||||
switch {
|
||||
|
||||
case len(missingAttrs) != 0:
|
||||
switch len(missingAttrs) {
|
||||
case 1:
|
||||
return fmt.Sprintf("attribute %q is required", missingAttrs[0])
|
||||
case 2:
|
||||
return fmt.Sprintf("attributes %q and %q are required", missingAttrs[0], missingAttrs[1])
|
||||
default:
|
||||
sort.Strings(missingAttrs)
|
||||
var buf bytes.Buffer
|
||||
for _, name := range missingAttrs[:len(missingAttrs)-1] {
|
||||
fmt.Fprintf(&buf, "%q, ", name)
|
||||
}
|
||||
fmt.Fprintf(&buf, "and %q", missingAttrs[len(missingAttrs)-1])
|
||||
return fmt.Sprintf("attributes %s are required", buf.Bytes())
|
||||
}
|
||||
|
||||
case unsafeMismatchAttr != "":
|
||||
return unsafeMismatchAttr
|
||||
|
||||
case safeMismatchAttr != "":
|
||||
return safeMismatchAttr
|
||||
|
||||
default:
|
||||
// We should never get here, but if we do then we'll return
|
||||
// just a generic message.
|
||||
return "incorrect object attributes"
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
@ -46,7 +46,7 @@ func Convert(in cty.Value, want cty.Type) (cty.Value, error) {
|
|||
|
||||
conv := GetConversionUnsafe(in.Type(), want)
|
||||
if conv == nil {
|
||||
return cty.NilVal, fmt.Errorf("incorrect type; %s required", want.FriendlyName())
|
||||
return cty.NilVal, errors.New(MismatchMessage(in.Type(), want))
|
||||
}
|
||||
return conv(in)
|
||||
}
|
||||
|
|
|
@ -2442,50 +2442,50 @@
|
|||
{
|
||||
"checksumSHA1": "4REWNRi5Dg7Kxj1US72+/oVii3Q=",
|
||||
"path": "github.com/zclconf/go-cty/cty",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "g3pPIVGKkD4gt8TasyLxSX+qdP0=",
|
||||
"checksumSHA1": "3bFMqSB6IxIwhzIw8hyLfd7ayrY=",
|
||||
"path": "github.com/zclconf/go-cty/cty/convert",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "MyyLCGg3RREMllTJyK6ehZl/dHk=",
|
||||
"path": "github.com/zclconf/go-cty/cty/function",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "kcTJOuL131/stXJ4U9tC3SASQLs=",
|
||||
"path": "github.com/zclconf/go-cty/cty/function/stdlib",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "tmCzwfNXOEB1sSO7TKVzilb2vjA=",
|
||||
"path": "github.com/zclconf/go-cty/cty/gocty",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "1ApmO+Q33+Oem/3f6BU6sztJWNc=",
|
||||
"path": "github.com/zclconf/go-cty/cty/json",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "55JE44+FFgAo5WZEC23gp4X5tak=",
|
||||
"path": "github.com/zclconf/go-cty/cty/msgpack",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "y5Sk+n6SOspFj8mlyb8swr4DMIs=",
|
||||
"path": "github.com/zclconf/go-cty/cty/set",
|
||||
"revision": "ba988ce11d9994867838957d4c40bb1ad78b433d",
|
||||
"revisionTime": "2018-05-24T00:26:36Z"
|
||||
"revision": "c96d660229f9ad0a16eb5869819ea51e1ed49896",
|
||||
"revisionTime": "2018-06-16T18:02:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "vE43s37+4CJ2CDU6TlOUOYE0K9c=",
|
||||
|
|
Loading…
Reference in New Issue