func Validate()

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
}