package cty // TestConformance recursively walks the receiver and the given other type and // returns nil if the receiver *conforms* to the given type. // // Type conformance is similar to type equality but has one crucial difference: // PseudoTypeDynamic can be used within the given type to represent that // *any* type is allowed. // // If any non-conformities are found, the returned slice will be non-nil and // contain at least one error value. It will be nil if the type is entirely // conformant. // // Note that the special behavior of PseudoTypeDynamic is the *only* exception // to normal type equality. Calling applications may wish to apply their own // automatic conversion logic to the given data structure to create a more // liberal notion of conformance to a type. // // Returned errors are usually (but not always) PathError instances that // indicate where in the structure the error was found. If a returned error // is of that type then the error message is written for (English-speaking) // end-users working within the cty type system, not mentioning any Go-oriented // implementation details. func (t Type) TestConformance(other Type) []error { path := make(Path, 0) var errs []error testConformance(t, other, path, &errs) return errs } func testConformance(given Type, want Type, path Path, errs *[]error) { if want.Equals(DynamicPseudoType) { // anything goes! return } if given.Equals(want) { // Any equal types are always conformant return } // The remainder of this function is concerned with detecting // and reporting the specific non-conformance, since we wouldn't // have got here if the types were not divergent. // We treat compound structures as special so that we can report // specifically what is non-conforming, rather than simply returning // the entire type names and letting the user puzzle it out. if given.IsObjectType() && want.IsObjectType() { givenAttrs := given.AttributeTypes() wantAttrs := want.AttributeTypes() for k := range givenAttrs { if _, exists := wantAttrs[k]; !exists { *errs = append( *errs, errorf(path, "unsupported attribute %q", k), ) } } for k := range wantAttrs { if _, exists := givenAttrs[k]; !exists { *errs = append( *errs, errorf(path, "missing required attribute %q", k), ) } } path = append(path, nil) pathIdx := len(path) - 1 for k, wantAttrType := range wantAttrs { if givenAttrType, exists := givenAttrs[k]; exists { path[pathIdx] = GetAttrStep{Name: k} testConformance(givenAttrType, wantAttrType, path, errs) } } path = path[0:pathIdx] return } if given.IsTupleType() && want.IsTupleType() { givenElems := given.TupleElementTypes() wantElems := want.TupleElementTypes() if len(givenElems) != len(wantElems) { *errs = append( *errs, errorf(path, "%d elements are required, but got %d", len(wantElems), len(givenElems)), ) return } path = append(path, nil) pathIdx := len(path) - 1 for i, wantElemType := range wantElems { givenElemType := givenElems[i] path[pathIdx] = IndexStep{Key: NumberIntVal(int64(i))} testConformance(givenElemType, wantElemType, path, errs) } path = path[0:pathIdx] return } if given.IsListType() && want.IsListType() { path = append(path, IndexStep{Key: UnknownVal(Number)}) pathIdx := len(path) - 1 testConformance(given.ElementType(), want.ElementType(), path, errs) path = path[0:pathIdx] return } if given.IsMapType() && want.IsMapType() { path = append(path, IndexStep{Key: UnknownVal(String)}) pathIdx := len(path) - 1 testConformance(given.ElementType(), want.ElementType(), path, errs) path = path[0:pathIdx] return } if given.IsSetType() && want.IsSetType() { path = append(path, IndexStep{Key: UnknownVal(given.ElementType())}) pathIdx := len(path) - 1 testConformance(given.ElementType(), want.ElementType(), path, errs) path = path[0:pathIdx] return } *errs = append( *errs, errorf(path, "%s required, but received %s", want.FriendlyName(), given.FriendlyName()), ) }