in internal/langserver/handlers/validate/validate.go [113:286]
func Validate(hclNode *parser.HclNode, typeBase *types.TypeBase) hcl.Diagnostics {
if typeBase == nil || hclNode == nil {
return nil
}
diags := make([]*hcl.Diagnostic, 0)
switch t := (*typeBase).(type) {
case *types.ArrayType:
if !hclNode.IsValueArray() || t.ItemType == nil {
break
}
for _, child := range hclNode.Children {
diags = append(diags, Validate(child, t.ItemType.Type)...)
}
case *types.DiscriminatedObjectType:
if !hclNode.IsValueMap() {
break
}
// check base properties
otherProperties := make(map[string]*parser.HclNode)
for key, value := range hclNode.Children {
if def, ok := t.BaseProperties[key]; ok {
if def.IsReadOnly() {
diags = append(diags, newDiagnostic(ErrorShouldNotDefineReadOnly(key), value.KeyRange))
continue
}
if def.Type != nil {
diags = append(diags, Validate(value, def.Type.Type)...)
}
} else {
otherProperties[key] = value
}
}
// check required base properties
for key, value := range t.BaseProperties {
if value.IsRequired() && hclNode.Children[key] == nil {
diags = append(diags, newDiagnostic(ErrorShouldDefine(key), hclNode.KeyRange))
}
}
// check other properties which should be defined in discriminated objects
if _, ok := otherProperties[t.Discriminator]; !ok {
diags = append(diags, newDiagnostic(ErrorShouldDefine(t.Discriminator), hclNode.KeyRange))
break
}
discriminator := ""
discriminatorRange := hclNode.KeyRange
discriminatorProp := otherProperties[t.Discriminator]
if discriminatorProp != nil {
if discriminatorProp.Value != nil {
discriminator = strings.TrimPrefix(strings.TrimSuffix(strings.TrimSpace(*discriminatorProp.Value), `"`), `"`)
}
discriminatorRange = discriminatorProp.KeyRange
}
if len(discriminator) != 0 {
switch {
case t.Elements[discriminator] == nil:
options := make([]string, 0)
for key := range t.Elements {
options = append(options, key)
}
diags = append(diags, newDiagnostic(ErrorNotMatchAnyValues(t.Discriminator, discriminator, options), discriminatorProp.ValueRange))
case t.Elements[discriminator].Type != nil:
other := &parser.HclNode{
Key: hclNode.Key,
KeyRange: hclNode.KeyRange,
Children: otherProperties,
EqualRange: hclNode.EqualRange,
ValueRange: hclNode.ValueRange,
}
diags = append(diags, Validate(other, t.Elements[discriminator].Type)...)
}
} else {
diags = append(diags, newDiagnostic(ErrorMismatch(t.Discriminator, "string", fmt.Sprintf("%T", otherProperties[t.Discriminator])), discriminatorRange))
}
case *types.ObjectType:
if !hclNode.IsValueMap() {
break
}
// check properties defined in body, but not in schema
for key, value := range hclNode.Children {
if def, ok := t.Properties[key]; ok {
if def.IsReadOnly() {
diags = append(diags, newDiagnostic(ErrorShouldNotDefineReadOnly(key), value.KeyRange))
continue
}
if def.Type != nil {
diags = append(diags, Validate(value, def.Type.Type)...)
}
continue
}
if t.AdditionalProperties != nil {
diags = append(diags, Validate(value, t.AdditionalProperties.Type)...)
} else {
options := make([]string, 0)
for key := range t.Properties {
options = append(options, key)
}
diags = append(diags, newDiagnostic(ErrorShouldNotDefine(key, options), value.KeyRange))
}
}
// check properties required in schema, but not in body
for key, value := range t.Properties {
if value.IsRequired() && hclNode.Children[key] == nil {
// skip name in body
if hclNode.Key == "dummy" && (key == "name" || key == "location") {
continue
}
diags = append(diags, newDiagnostic(ErrorShouldDefine(key), hclNode.KeyRange))
}
}
case *types.ResourceType:
if t.Body != nil {
return Validate(hclNode, t.Body.Type)
}
case *types.ResourceFunctionType:
if t.Input != nil {
return Validate(hclNode, t.Input.Type)
}
case *types.AnyType:
case *types.BooleanType:
case *types.IntegerType:
// TODO: validate
case *types.StringType:
// TODO: validate
case *types.StringLiteralType:
if hclNode.Value != nil {
value := strings.TrimSpace(*hclNode.Value)
if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
value = strings.TrimPrefix(strings.TrimSuffix(value, `"`), `"`)
if value != t.Value {
diags = append(diags, newDiagnostic(ErrorMismatch(hclNode.Key, t.Value, value), hclNode.ValueRange))
}
}
}
case *types.UnionType:
valid := false
for _, element := range t.Elements {
if element.Type == nil {
continue
}
temp := Validate(hclNode, element.Type)
if len(temp) == 0 {
valid = true
break
}
}
if !valid {
options := make([]string, 0)
for _, element := range t.Elements {
if element.Type != nil {
if stringLiteralType, ok := (*element.Type).(*types.StringLiteralType); ok {
options = append(options, stringLiteralType.Value)
}
}
}
if len(options) == 0 {
diags = append(diags, newDiagnostic(ErrorNotMatchAny(hclNode.Key), hclNode.GetRange()))
} else {
value := ""
if hclNode.Value != nil {
value = *hclNode.Value
}
diags = append(diags, newDiagnostic(ErrorNotMatchAnyValues(hclNode.Key, value, options), hclNode.ValueRange))
}
}
}
return diags
}