kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 schema
    15  // definitions have any inconsistencies with the documented rules for valid
    16  // schema.
    17  //
    18  // This can be used within unit tests to detect when a given schema is invalid,
    19  // and is run when terraform loads provider schemas during NewContext.
    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("")
    25  }
    26  
    27  func (b *Block) internalValidate(prefix string) error {
    28  	var multiErr *multierror.Error
    29  
    30  	for name, attrS := range b.Attributes {
    31  		if attrS == nil {
    32  			multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
    33  			continue
    34  		}
    35  		multiErr = multierror.Append(multiErr, attrS.internalValidate(name, prefix))
    36  	}
    37  
    38  	for name, blockS := range b.BlockTypes {
    39  		if blockS == nil {
    40  			multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: block schema is nil", prefix, name))
    41  			continue
    42  		}
    43  
    44  		if _, isAttr := b.Attributes[name]; isAttr {
    45  			multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name))
    46  		} else if !validName.MatchString(name) {
    47  			multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
    48  		}
    49  
    50  		if blockS.MinItems < 0 || blockS.MaxItems < 0 {
    51  			multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name))
    52  		}
    53  
    54  		switch blockS.Nesting {
    55  		case NestingSingle:
    56  			switch {
    57  			case blockS.MinItems != blockS.MaxItems:
    58  				multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name))
    59  			case blockS.MinItems < 0 || blockS.MinItems > 1:
    60  				multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name))
    61  			}
    62  		case NestingGroup:
    63  			if blockS.MinItems != 0 || blockS.MaxItems != 0 {
    64  				multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems cannot be used in NestingGroup mode", prefix, name))
    65  			}
    66  		case NestingList, NestingSet:
    67  			if blockS.MinItems > blockS.MaxItems && blockS.MaxItems != 0 {
    68  				multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, blockS.Nesting))
    69  			}
    70  			if blockS.Nesting == NestingSet {
    71  				ety := blockS.Block.ImpliedType()
    72  				if ety.HasDynamicTypes() {
    73  					// This is not permitted because the HCL (cty) set implementation
    74  					// needs to know the exact type of set elements in order to
    75  					// properly hash them, and so can't support mixed types.
    76  					multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name))
    77  				}
    78  			}
    79  		case NestingMap:
    80  			if blockS.MinItems != 0 || blockS.MaxItems != 0 {
    81  				multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name))
    82  			}
    83  		default:
    84  			multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, blockS.Nesting))
    85  		}
    86  
    87  		subPrefix := prefix + name + "."
    88  		multiErr = multierror.Append(multiErr, blockS.Block.internalValidate(subPrefix))
    89  	}
    90  
    91  	return multiErr.ErrorOrNil()
    92  }
    93  
    94  // InternalValidate returns an error if the receiving attribute and its child
    95  // schema definitions have any inconsistencies with the documented rules for
    96  // valid schema.
    97  func (a *Attribute) InternalValidate(name string) error {
    98  	if a == nil {
    99  		return fmt.Errorf("attribute schema is nil")
   100  	}
   101  	return a.internalValidate(name, "")
   102  }
   103  
   104  func (a *Attribute) internalValidate(name, prefix string) error {
   105  	var err *multierror.Error
   106  
   107  	/* FIXME: this validation breaks certain existing providers and cannot be enforced without coordination.
   108  	if !validName.MatchString(name) {
   109  		err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
   110  	}
   111  	*/
   112  	if !a.Optional && !a.Required && !a.Computed {
   113  		err = multierror.Append(err, fmt.Errorf("%s%s: must set Optional, Required or Computed", prefix, name))
   114  	}
   115  	if a.Optional && a.Required {
   116  		err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Optional and Required", prefix, name))
   117  	}
   118  	if a.Computed && a.Required {
   119  		err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Computed and Required", prefix, name))
   120  	}
   121  
   122  	if a.Type == cty.NilType && a.NestedType == nil {
   123  		err = multierror.Append(err, fmt.Errorf("%s%s: either Type or NestedType must be defined", prefix, name))
   124  	}
   125  
   126  	if a.Type != cty.NilType {
   127  		if a.NestedType != nil {
   128  			err = multierror.Append(fmt.Errorf("%s: Type and NestedType cannot both be set", name))
   129  		}
   130  	}
   131  
   132  	if a.NestedType != nil {
   133  		switch a.NestedType.Nesting {
   134  		case NestingSingle:
   135  			switch {
   136  			case a.NestedType.MinItems != a.NestedType.MaxItems:
   137  				err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name))
   138  			case a.NestedType.MinItems < 0 || a.NestedType.MinItems > 1:
   139  				err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name))
   140  			}
   141  		case NestingList, NestingSet:
   142  			if a.NestedType.MinItems > a.NestedType.MaxItems && a.NestedType.MaxItems != 0 {
   143  				err = multierror.Append(err, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, a.NestedType.Nesting))
   144  			}
   145  			if a.NestedType.Nesting == NestingSet {
   146  				ety := a.NestedType.ImpliedType()
   147  				if ety.HasDynamicTypes() {
   148  					// This is not permitted because the HCL (cty) set implementation
   149  					// needs to know the exact type of set elements in order to
   150  					// properly hash them, and so can't support mixed types.
   151  					err = multierror.Append(err, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name))
   152  				}
   153  			}
   154  		case NestingMap:
   155  			if a.NestedType.MinItems != 0 || a.NestedType.MaxItems != 0 {
   156  				err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name))
   157  			}
   158  		default:
   159  			err = multierror.Append(err, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, a.NestedType.Nesting))
   160  		}
   161  		for name, attrS := range a.NestedType.Attributes {
   162  			if attrS == nil {
   163  				err = multierror.Append(err, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
   164  				continue
   165  			}
   166  			err = multierror.Append(err, attrS.internalValidate(name, prefix))
   167  		}
   168  	}
   169  
   170  	return err.ErrorOrNil()
   171  }