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  }