]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / zclconf / go-cty / cty / convert / mismatch_msg.go
1 package convert
2
3 import (
4 "bytes"
5 "fmt"
6 "sort"
7
8 "github.com/zclconf/go-cty/cty"
9 )
10
11 // MismatchMessage is a helper to return an English-language description of
12 // the differences between got and want, phrased as a reason why got does
13 // not conform to want.
14 //
15 // This function does not itself attempt conversion, and so it should generally
16 // be used only after a conversion has failed, to report the conversion failure
17 // to an English-speaking user. The result will be confusing got is actually
18 // conforming to or convertable to want.
19 //
20 // The shorthand helper function Convert uses this function internally to
21 // produce its error messages, so callers of that function do not need to
22 // also use MismatchMessage.
23 //
24 // This function is similar to Type.TestConformance, but it is tailored to
25 // describing conversion failures and so the messages it generates relate
26 // specifically to the conversion rules implemented in this package.
27 func MismatchMessage(got, want cty.Type) string {
28 switch {
29
30 case got.IsObjectType() && want.IsObjectType():
31 // If both types are object types then we may be able to say something
32 // about their respective attributes.
33 return mismatchMessageObjects(got, want)
34
35 case got.IsTupleType() && want.IsListType() && want.ElementType() == cty.DynamicPseudoType:
36 // If conversion from tuple to list failed then it's because we couldn't
37 // find a common type to convert all of the tuple elements to.
38 return "all list elements must have the same type"
39
40 case got.IsTupleType() && want.IsSetType() && want.ElementType() == cty.DynamicPseudoType:
41 // If conversion from tuple to set failed then it's because we couldn't
42 // find a common type to convert all of the tuple elements to.
43 return "all set elements must have the same type"
44
45 case got.IsObjectType() && want.IsMapType() && want.ElementType() == cty.DynamicPseudoType:
46 // If conversion from object to map failed then it's because we couldn't
47 // find a common type to convert all of the object attributes to.
48 return "all map elements must have the same type"
49
50 case (got.IsTupleType() || got.IsObjectType()) && want.IsCollectionType():
51 return mismatchMessageCollectionsFromStructural(got, want)
52
53 case got.IsCollectionType() && want.IsCollectionType():
54 return mismatchMessageCollectionsFromCollections(got, want)
55
56 default:
57 // If we have nothing better to say, we'll just state what was required.
58 return want.FriendlyNameForConstraint() + " required"
59 }
60 }
61
62 func mismatchMessageObjects(got, want cty.Type) string {
63 // Per our conversion rules, "got" is allowed to be a superset of "want",
64 // and so we'll produce error messages here under that assumption.
65 gotAtys := got.AttributeTypes()
66 wantAtys := want.AttributeTypes()
67
68 // If we find missing attributes then we'll report those in preference,
69 // but if not then we will report a maximum of one non-conforming
70 // attribute, just to keep our messages relatively terse.
71 // We'll also prefer to report a recursive type error from an _unsafe_
72 // conversion over a safe one, because these are subjectively more
73 // "serious".
74 var missingAttrs []string
75 var unsafeMismatchAttr string
76 var safeMismatchAttr string
77
78 for name, wantAty := range wantAtys {
79 gotAty, exists := gotAtys[name]
80 if !exists {
81 missingAttrs = append(missingAttrs, name)
82 continue
83 }
84
85 // We'll now try to convert these attributes in isolation and
86 // see if we have a nested conversion error to report.
87 // We'll try an unsafe conversion first, and then fall back on
88 // safe if unsafe is possible.
89
90 // If we already have an unsafe mismatch attr error then we won't bother
91 // hunting for another one.
92 if unsafeMismatchAttr != "" {
93 continue
94 }
95 if conv := GetConversionUnsafe(gotAty, wantAty); conv == nil {
96 unsafeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty))
97 }
98
99 // If we already have a safe mismatch attr error then we won't bother
100 // hunting for another one.
101 if safeMismatchAttr != "" {
102 continue
103 }
104 if conv := GetConversion(gotAty, wantAty); conv == nil {
105 safeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty))
106 }
107 }
108
109 // We should now have collected at least one problem. If we have more than
110 // one then we'll use our preference order to decide what is most important
111 // to report.
112 switch {
113
114 case len(missingAttrs) != 0:
115 sort.Strings(missingAttrs)
116 switch len(missingAttrs) {
117 case 1:
118 return fmt.Sprintf("attribute %q is required", missingAttrs[0])
119 case 2:
120 return fmt.Sprintf("attributes %q and %q are required", missingAttrs[0], missingAttrs[1])
121 default:
122 sort.Strings(missingAttrs)
123 var buf bytes.Buffer
124 for _, name := range missingAttrs[:len(missingAttrs)-1] {
125 fmt.Fprintf(&buf, "%q, ", name)
126 }
127 fmt.Fprintf(&buf, "and %q", missingAttrs[len(missingAttrs)-1])
128 return fmt.Sprintf("attributes %s are required", buf.Bytes())
129 }
130
131 case unsafeMismatchAttr != "":
132 return unsafeMismatchAttr
133
134 case safeMismatchAttr != "":
135 return safeMismatchAttr
136
137 default:
138 // We should never get here, but if we do then we'll return
139 // just a generic message.
140 return "incorrect object attributes"
141 }
142 }
143
144 func mismatchMessageCollectionsFromStructural(got, want cty.Type) string {
145 // First some straightforward cases where the kind is just altogether wrong.
146 switch {
147 case want.IsListType() && !got.IsTupleType():
148 return want.FriendlyNameForConstraint() + " required"
149 case want.IsSetType() && !got.IsTupleType():
150 return want.FriendlyNameForConstraint() + " required"
151 case want.IsMapType() && !got.IsObjectType():
152 return want.FriendlyNameForConstraint() + " required"
153 }
154
155 // If the kinds are matched well enough then we'll move on to checking
156 // individual elements.
157 wantEty := want.ElementType()
158 switch {
159 case got.IsTupleType():
160 for i, gotEty := range got.TupleElementTypes() {
161 if gotEty.Equals(wantEty) {
162 continue // exact match, so no problem
163 }
164 if conv := getConversion(gotEty, wantEty, true); conv != nil {
165 continue // conversion is available, so no problem
166 }
167 return fmt.Sprintf("element %d: %s", i, MismatchMessage(gotEty, wantEty))
168 }
169
170 // If we get down here then something weird is going on but we'll
171 // return a reasonable fallback message anyway.
172 return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint())
173
174 case got.IsObjectType():
175 for name, gotAty := range got.AttributeTypes() {
176 if gotAty.Equals(wantEty) {
177 continue // exact match, so no problem
178 }
179 if conv := getConversion(gotAty, wantEty, true); conv != nil {
180 continue // conversion is available, so no problem
181 }
182 return fmt.Sprintf("element %q: %s", name, MismatchMessage(gotAty, wantEty))
183 }
184
185 // If we get down here then something weird is going on but we'll
186 // return a reasonable fallback message anyway.
187 return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint())
188
189 default:
190 // Should not be possible to get here since we only call this function
191 // with got as structural types, but...
192 return want.FriendlyNameForConstraint() + " required"
193 }
194 }
195
196 func mismatchMessageCollectionsFromCollections(got, want cty.Type) string {
197 // First some straightforward cases where the kind is just altogether wrong.
198 switch {
199 case want.IsListType() && !(got.IsListType() || got.IsSetType()):
200 return want.FriendlyNameForConstraint() + " required"
201 case want.IsSetType() && !(got.IsListType() || got.IsSetType()):
202 return want.FriendlyNameForConstraint() + " required"
203 case want.IsMapType() && !got.IsMapType():
204 return want.FriendlyNameForConstraint() + " required"
205 }
206
207 // If the kinds are matched well enough then we'll check the element types.
208 gotEty := got.ElementType()
209 wantEty := want.ElementType()
210 noun := "element type"
211 switch {
212 case want.IsListType():
213 noun = "list element type"
214 case want.IsSetType():
215 noun = "set element type"
216 case want.IsMapType():
217 noun = "map element type"
218 }
219 return fmt.Sprintf("incorrect %s: %s", noun, MismatchMessage(gotEty, wantEty))
220 }