github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/configs/configschema/internal_validate.go (about) 1 package configschema 2 3 import ( 4 "fmt" 5 "regexp" 6 7 "github.com/zclconf/go-cty/cty" 8 9 multierror "github.com/hashicorp/go-multierror" 10 ) 11 12 var validName = regexp.MustCompile(`^[a-z0-9_]+$`) 13 14 // InternalValidate returns an error if the receiving block and its child 15 // schema definitions have any consistencies with the documented rules for 16 // valid schema. 17 // 18 // This is intended to be used within unit tests to detect when a given 19 // schema is invalid. 20 func (b *Block) InternalValidate() error { 21 if b == nil { 22 return fmt.Errorf("top-level block schema is nil") 23 } 24 return b.internalValidate("", nil) 25 26 } 27 28 func (b *Block) internalValidate(prefix string, err error) error { 29 for name, attrS := range b.Attributes { 30 if attrS == nil { 31 err = multierror.Append(err, fmt.Errorf("%s%s: attribute schema is nil", prefix, name)) 32 continue 33 } 34 if !validName.MatchString(name) { 35 err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name)) 36 } 37 if attrS.Optional == false && attrS.Required == false && attrS.Computed == false { 38 err = multierror.Append(err, fmt.Errorf("%s%s: must set Optional, Required or Computed", prefix, name)) 39 } 40 if attrS.Optional && attrS.Required { 41 err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Optional and Required", prefix, name)) 42 } 43 if attrS.Computed && attrS.Required { 44 err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Computed and Required", prefix, name)) 45 } 46 if attrS.Type == cty.NilType { 47 err = multierror.Append(err, fmt.Errorf("%s%s: Type must be set to something other than cty.NilType", prefix, name)) 48 } 49 } 50 51 for name, blockS := range b.BlockTypes { 52 if blockS == nil { 53 err = multierror.Append(err, fmt.Errorf("%s%s: block schema is nil", prefix, name)) 54 continue 55 } 56 57 if _, isAttr := b.Attributes[name]; isAttr { 58 err = multierror.Append(err, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name)) 59 } else if !validName.MatchString(name) { 60 err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name)) 61 } 62 63 if blockS.MinItems < 0 || blockS.MaxItems < 0 { 64 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name)) 65 } 66 67 switch blockS.Nesting { 68 case NestingSingle: 69 switch { 70 case blockS.MinItems != blockS.MaxItems: 71 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name)) 72 case blockS.MinItems < 0 || blockS.MinItems > 1: 73 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name)) 74 } 75 case NestingGroup: 76 if blockS.MinItems != 0 || blockS.MaxItems != 0 { 77 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems cannot be used in NestingGroup mode", prefix, name)) 78 } 79 case NestingList, NestingSet: 80 if blockS.MinItems > blockS.MaxItems && blockS.MaxItems != 0 { 81 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, blockS.Nesting)) 82 } 83 if blockS.Nesting == NestingSet { 84 ety := blockS.Block.ImpliedType() 85 if ety.HasDynamicTypes() { 86 // This is not permitted because the HCL (cty) set implementation 87 // needs to know the exact type of set elements in order to 88 // properly hash them, and so can't support mixed types. 89 err = multierror.Append(err, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name)) 90 } 91 } 92 case NestingMap: 93 if blockS.MinItems != 0 || blockS.MaxItems != 0 { 94 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name)) 95 } 96 default: 97 err = multierror.Append(err, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, blockS.Nesting)) 98 } 99 100 subPrefix := prefix + name + "." 101 err = blockS.Block.internalValidate(subPrefix, err) 102 } 103 104 return err 105 }