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 }