diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip |
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go')
-rw-r--r-- | vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go b/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go new file mode 100644 index 0000000..581304e --- /dev/null +++ b/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go | |||
@@ -0,0 +1,220 @@ | |||
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 | } | ||