From 47dcc3bbb4d5d4e696654cc35ac7d37911ffbed5 Mon Sep 17 00:00:00 2001 From: iamshivamnanda Date: Fri, 13 Sep 2024 16:13:34 +0530 Subject: [PATCH] Yaml Value Node handling --- decode.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++-- validator.go | 7 ++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/decode.go b/decode.go index 044b284a..1cd35306 100644 --- a/decode.go +++ b/decode.go @@ -23,6 +23,7 @@ import ( "math" "reflect" "strconv" + "strings" "time" ) @@ -38,6 +39,21 @@ type parser struct { textless bool } +// YAMLValue is a generic struct that holds both the typed value and the YAML Node +type YAMLValue[T any] struct { + Value T + Node *Node +} + +// Helper function to check if a type is YAMLValue or YAMLValue[T] +func isYAMLValueType(t reflect.Type) bool { + if t.Kind() != reflect.Struct { + return false + } + + return strings.Contains(t.String(), "YAMLValue") +} + func newParser(b []byte) *parser { p := parser{} if !yaml_parser_initialize(&p.parser) { @@ -489,20 +505,53 @@ func (d *decoder) unmarshal(node *Node, out reflect.Value) (good bool) { if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { failf("document contains excessive aliasing") } - if out.Type() == nodeType { - out.Set(reflect.ValueOf(node).Elem()) + + // Check if the out value is of YAMLValue type + if isYAMLValueType(out.Type()) { + valueField := out.FieldByName("Value") + nodeField := out.FieldByName("Node") + + if !valueField.IsValid() || !nodeField.IsValid() { + failf("YAMLValue struct is missing Value or Node field") + } + + // Create a new value to unmarshal into + innerValue := reflect.New(valueField.Type()).Elem() + good = d.unmarshal(node, innerValue) + if good { + // Set the Value and Node fields of YAMLValue + valueField.Set(innerValue) + nodeField.Set(reflect.ValueOf(node)) + // Validate the inner value + errors := ValidateStruct(innerValue, node) + if len(errors) > 0 { + for _, e := range errors { + d.terrors = append(d.terrors, e.Error()) + } + return false + } + } + return good + } + + // Check if out type is *yaml.Node + if out.Type() == reflect.TypeOf(&Node{}) { + out.Set(reflect.ValueOf(node)) return true } + switch node.Kind { case DocumentNode: return d.document(node, out) case AliasNode: return d.alias(node, out) } + out, unmarshaled, good := d.prepare(node, out) if unmarshaled { return good } + switch node.Kind { case ScalarNode: good = d.scalar(node, out) diff --git a/validator.go b/validator.go index c9fee44c..e5d5fbda 100644 --- a/validator.go +++ b/validator.go @@ -13,6 +13,7 @@ var validators = make(map[string]Validator) // validateStruct performs validation based on struct tags func ValidateStruct(out reflect.Value, node *Node) []error { + var errors []error t := out.Type() if t.Kind() != reflect.Struct { @@ -40,7 +41,11 @@ func ValidateStruct(out reflect.Value, node *Node) []error { yamlTag = field.Name } if childNode, exists := fieldMap[yamlTag]; exists { - errors = append(errors, applyValidationRules(out.Field(i), tag, yamlTag, childNode)...) + fieldValue := out.Field(i) + if isYAMLValueType(field.Type) { + fieldValue = fieldValue.FieldByName("Value") + } + errors = append(errors, applyValidationRules(fieldValue, tag, yamlTag, childNode)...) } else { if tagContainsRequired(tag) { errors = append(errors, CustomError{