terraform-provider-google/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go
Paddy 961c878e0d Switch to using Go modules. (#2679)
Switch to using Go modules.

This migrates our vendor.json to use Go 1.11's modules system, and
replaces the vendor folder with the output of go mod vendor.

The vendored code should remain basically the same; I believe some
tree shaking of packages and support scripts/licenses/READMEs/etc.
happened.

This also fixes Travis and our Makefile to no longer use govendor.
2018-12-20 17:22:22 -08:00

221 lines
7.9 KiB
Go

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)
case got.IsTupleType() && want.IsListType() && want.ElementType() == cty.DynamicPseudoType:
// If conversion from tuple to list failed then it's because we couldn't
// find a common type to convert all of the tuple elements to.
return "all list elements must have the same type"
case got.IsTupleType() && want.IsSetType() && want.ElementType() == cty.DynamicPseudoType:
// If conversion from tuple to set failed then it's because we couldn't
// find a common type to convert all of the tuple elements to.
return "all set elements must have the same type"
case got.IsObjectType() && want.IsMapType() && want.ElementType() == cty.DynamicPseudoType:
// If conversion from object to map failed then it's because we couldn't
// find a common type to convert all of the object attributes to.
return "all map elements must have the same type"
case (got.IsTupleType() || got.IsObjectType()) && want.IsCollectionType():
return mismatchMessageCollectionsFromStructural(got, want)
case got.IsCollectionType() && want.IsCollectionType():
return mismatchMessageCollectionsFromCollections(got, want)
default:
// If we have nothing better to say, we'll just state what was required.
return want.FriendlyNameForConstraint() + " 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:
sort.Strings(missingAttrs)
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"
}
}
func mismatchMessageCollectionsFromStructural(got, want cty.Type) string {
// First some straightforward cases where the kind is just altogether wrong.
switch {
case want.IsListType() && !got.IsTupleType():
return want.FriendlyNameForConstraint() + " required"
case want.IsSetType() && !got.IsTupleType():
return want.FriendlyNameForConstraint() + " required"
case want.IsMapType() && !got.IsObjectType():
return want.FriendlyNameForConstraint() + " required"
}
// If the kinds are matched well enough then we'll move on to checking
// individual elements.
wantEty := want.ElementType()
switch {
case got.IsTupleType():
for i, gotEty := range got.TupleElementTypes() {
if gotEty.Equals(wantEty) {
continue // exact match, so no problem
}
if conv := getConversion(gotEty, wantEty, true); conv != nil {
continue // conversion is available, so no problem
}
return fmt.Sprintf("element %d: %s", i, MismatchMessage(gotEty, wantEty))
}
// If we get down here then something weird is going on but we'll
// return a reasonable fallback message anyway.
return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint())
case got.IsObjectType():
for name, gotAty := range got.AttributeTypes() {
if gotAty.Equals(wantEty) {
continue // exact match, so no problem
}
if conv := getConversion(gotAty, wantEty, true); conv != nil {
continue // conversion is available, so no problem
}
return fmt.Sprintf("element %q: %s", name, MismatchMessage(gotAty, wantEty))
}
// If we get down here then something weird is going on but we'll
// return a reasonable fallback message anyway.
return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint())
default:
// Should not be possible to get here since we only call this function
// with got as structural types, but...
return want.FriendlyNameForConstraint() + " required"
}
}
func mismatchMessageCollectionsFromCollections(got, want cty.Type) string {
// First some straightforward cases where the kind is just altogether wrong.
switch {
case want.IsListType() && !(got.IsListType() || got.IsSetType()):
return want.FriendlyNameForConstraint() + " required"
case want.IsSetType() && !(got.IsListType() || got.IsSetType()):
return want.FriendlyNameForConstraint() + " required"
case want.IsMapType() && !got.IsMapType():
return want.FriendlyNameForConstraint() + " required"
}
// If the kinds are matched well enough then we'll check the element types.
gotEty := got.ElementType()
wantEty := want.ElementType()
noun := "element type"
switch {
case want.IsListType():
noun = "list element type"
case want.IsSetType():
noun = "set element type"
case want.IsMapType():
noun = "map element type"
}
return fmt.Sprintf("incorrect %s: %s", noun, MismatchMessage(gotEty, wantEty))
}