]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package cty |
2 | ||
3 | // TestConformance recursively walks the receiver and the given other type and | |
4 | // returns nil if the receiver *conforms* to the given type. | |
5 | // | |
6 | // Type conformance is similar to type equality but has one crucial difference: | |
7 | // PseudoTypeDynamic can be used within the given type to represent that | |
8 | // *any* type is allowed. | |
9 | // | |
10 | // If any non-conformities are found, the returned slice will be non-nil and | |
11 | // contain at least one error value. It will be nil if the type is entirely | |
12 | // conformant. | |
13 | // | |
14 | // Note that the special behavior of PseudoTypeDynamic is the *only* exception | |
15 | // to normal type equality. Calling applications may wish to apply their own | |
16 | // automatic conversion logic to the given data structure to create a more | |
17 | // liberal notion of conformance to a type. | |
18 | // | |
19 | // Returned errors are usually (but not always) PathError instances that | |
20 | // indicate where in the structure the error was found. If a returned error | |
21 | // is of that type then the error message is written for (English-speaking) | |
22 | // end-users working within the cty type system, not mentioning any Go-oriented | |
23 | // implementation details. | |
24 | func (t Type) TestConformance(other Type) []error { | |
25 | path := make(Path, 0) | |
26 | var errs []error | |
27 | testConformance(t, other, path, &errs) | |
28 | return errs | |
29 | } | |
30 | ||
31 | func testConformance(given Type, want Type, path Path, errs *[]error) { | |
32 | if want.Equals(DynamicPseudoType) { | |
33 | // anything goes! | |
34 | return | |
35 | } | |
36 | ||
37 | if given.Equals(want) { | |
38 | // Any equal types are always conformant | |
39 | return | |
40 | } | |
41 | ||
42 | // The remainder of this function is concerned with detecting | |
43 | // and reporting the specific non-conformance, since we wouldn't | |
44 | // have got here if the types were not divergent. | |
45 | // We treat compound structures as special so that we can report | |
46 | // specifically what is non-conforming, rather than simply returning | |
47 | // the entire type names and letting the user puzzle it out. | |
48 | ||
49 | if given.IsObjectType() && want.IsObjectType() { | |
50 | givenAttrs := given.AttributeTypes() | |
51 | wantAttrs := want.AttributeTypes() | |
52 | ||
107c1cdb ND |
53 | for k := range givenAttrs { |
54 | if _, exists := wantAttrs[k]; !exists { | |
55 | *errs = append( | |
56 | *errs, | |
57 | errorf(path, "unsupported attribute %q", k), | |
58 | ) | |
15c0b25d | 59 | } |
107c1cdb ND |
60 | } |
61 | for k := range wantAttrs { | |
62 | if _, exists := givenAttrs[k]; !exists { | |
63 | *errs = append( | |
64 | *errs, | |
65 | errorf(path, "missing required attribute %q", k), | |
66 | ) | |
15c0b25d AP |
67 | } |
68 | } | |
69 | ||
70 | path = append(path, nil) | |
71 | pathIdx := len(path) - 1 | |
72 | ||
73 | for k, wantAttrType := range wantAttrs { | |
74 | if givenAttrType, exists := givenAttrs[k]; exists { | |
75 | path[pathIdx] = GetAttrStep{Name: k} | |
76 | testConformance(givenAttrType, wantAttrType, path, errs) | |
77 | } | |
78 | } | |
79 | ||
80 | path = path[0:pathIdx] | |
81 | ||
82 | return | |
83 | } | |
84 | ||
85 | if given.IsTupleType() && want.IsTupleType() { | |
86 | givenElems := given.TupleElementTypes() | |
87 | wantElems := want.TupleElementTypes() | |
88 | ||
89 | if len(givenElems) != len(wantElems) { | |
90 | *errs = append( | |
91 | *errs, | |
92 | errorf(path, "%d elements are required, but got %d", len(wantElems), len(givenElems)), | |
93 | ) | |
94 | return | |
95 | } | |
96 | ||
97 | path = append(path, nil) | |
98 | pathIdx := len(path) - 1 | |
99 | ||
100 | for i, wantElemType := range wantElems { | |
101 | givenElemType := givenElems[i] | |
102 | path[pathIdx] = IndexStep{Key: NumberIntVal(int64(i))} | |
103 | testConformance(givenElemType, wantElemType, path, errs) | |
104 | } | |
105 | ||
106 | path = path[0:pathIdx] | |
107 | ||
108 | return | |
109 | } | |
110 | ||
111 | if given.IsListType() && want.IsListType() { | |
112 | path = append(path, IndexStep{Key: UnknownVal(Number)}) | |
113 | pathIdx := len(path) - 1 | |
114 | testConformance(given.ElementType(), want.ElementType(), path, errs) | |
115 | path = path[0:pathIdx] | |
116 | return | |
117 | } | |
118 | ||
119 | if given.IsMapType() && want.IsMapType() { | |
120 | path = append(path, IndexStep{Key: UnknownVal(String)}) | |
121 | pathIdx := len(path) - 1 | |
122 | testConformance(given.ElementType(), want.ElementType(), path, errs) | |
123 | path = path[0:pathIdx] | |
124 | return | |
125 | } | |
126 | ||
127 | if given.IsSetType() && want.IsSetType() { | |
128 | path = append(path, IndexStep{Key: UnknownVal(given.ElementType())}) | |
129 | pathIdx := len(path) - 1 | |
130 | testConformance(given.ElementType(), want.ElementType(), path, errs) | |
131 | path = path[0:pathIdx] | |
132 | return | |
133 | } | |
134 | ||
135 | *errs = append( | |
136 | *errs, | |
137 | errorf(path, "%s required, but received %s", want.FriendlyName(), given.FriendlyName()), | |
138 | ) | |
139 | } |