github.com/Jeffail/benthos/v3@v3.65.0/internal/docs/yaml_path.go (about)

     1  package docs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  
     8  	"gopkg.in/yaml.v3"
     9  )
    10  
    11  func getFieldFromMapping(name string, createMissing bool, node *yaml.Node) (*yaml.Node, error) {
    12  	node.Kind = yaml.MappingNode
    13  	var foundNode *yaml.Node
    14  	for i := 0; i < len(node.Content)-1; i += 2 {
    15  		if node.Content[i].Value == name {
    16  			foundNode = node.Content[i+1]
    17  			break
    18  		}
    19  	}
    20  	if foundNode == nil {
    21  		if !createMissing {
    22  			return nil, fmt.Errorf("%v: key not found in mapping", name)
    23  		}
    24  		var keyNode yaml.Node
    25  		if err := keyNode.Encode(name); err != nil {
    26  			return nil, fmt.Errorf("%v: failed to encode key: %w", name, err)
    27  		}
    28  		node.Content = append(node.Content, &keyNode)
    29  
    30  		foundNode = &yaml.Node{}
    31  		node.Content = append(node.Content, foundNode)
    32  	}
    33  	return foundNode, nil
    34  }
    35  
    36  func getIndexFromSequence(name string, allowAppend bool, node *yaml.Node) (*yaml.Node, error) {
    37  	node.Kind = yaml.SequenceNode
    38  	var foundNode *yaml.Node
    39  	if name != "-" {
    40  		index, err := strconv.Atoi(name)
    41  		if err != nil {
    42  			return nil, fmt.Errorf("%v: failed to parse path segment as array index: %w", name, err)
    43  		}
    44  		if len(node.Content) <= index {
    45  			return nil, fmt.Errorf("%v: target index greater than array length", name)
    46  		}
    47  		foundNode = node.Content[index]
    48  	} else {
    49  		if !allowAppend {
    50  			return nil, fmt.Errorf("%v: append directive not allowed", name)
    51  		}
    52  		foundNode = &yaml.Node{}
    53  		node.Content = append(node.Content, foundNode)
    54  	}
    55  	return foundNode, nil
    56  }
    57  
    58  // SetYAMLPath sets the value of a node within a YAML document identified by a
    59  // path to a value.
    60  func (f FieldSpecs) SetYAMLPath(docsProvider Provider, root, value *yaml.Node, path ...string) error {
    61  	root = unwrapDocumentNode(root)
    62  	value = unwrapDocumentNode(value)
    63  
    64  	var foundSpec FieldSpec
    65  	for _, spec := range f {
    66  		if spec.Name == path[0] {
    67  			foundSpec = spec
    68  			break
    69  		}
    70  	}
    71  	if foundSpec.Name == "" {
    72  		return fmt.Errorf("%v: field not recognised", path[0])
    73  	}
    74  
    75  	foundNode, err := getFieldFromMapping(path[0], true, root)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	if err := foundSpec.SetYAMLPath(docsProvider, foundNode, value, path[1:]...); err != nil {
    81  		return fmt.Errorf("%v.%w", path[0], err)
    82  	}
    83  	return nil
    84  }
    85  
    86  func setYAMLPathCore(docsProvider Provider, coreType Type, root, value *yaml.Node, path ...string) error {
    87  	if docsProvider == nil {
    88  		docsProvider = globalProvider
    89  	}
    90  	foundNode, err := getFieldFromMapping(path[0], true, root)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if f, exists := reservedFieldsByType(coreType)[path[0]]; exists {
    95  		if err = f.SetYAMLPath(docsProvider, foundNode, value, path[1:]...); err != nil {
    96  			return fmt.Errorf("%v.%w", path[0], err)
    97  		}
    98  		return nil
    99  	}
   100  	cSpec, exists := GetDocs(docsProvider, path[0], coreType)
   101  	if !exists {
   102  		return fmt.Errorf("%v: field not recognised", path[0])
   103  	}
   104  	if err = cSpec.Config.SetYAMLPath(docsProvider, foundNode, value, path[1:]...); err != nil {
   105  		return fmt.Errorf("%v.%w", path[0], err)
   106  	}
   107  	return nil
   108  }
   109  
   110  // SetYAMLPath sets the value of a node within a YAML document identified by a
   111  // path to a value.
   112  func (f FieldSpec) SetYAMLPath(docsProvider Provider, root, value *yaml.Node, path ...string) error {
   113  	root = unwrapDocumentNode(root)
   114  	value = unwrapDocumentNode(value)
   115  
   116  	switch f.Kind {
   117  	case Kind2DArray:
   118  		if len(path) == 0 {
   119  			if value.Kind == yaml.SequenceNode {
   120  				*root = *value
   121  			} else {
   122  				root.Kind = yaml.SequenceNode
   123  				root.Content = []*yaml.Node{{
   124  					Kind:    yaml.SequenceNode,
   125  					Content: []*yaml.Node{value},
   126  				}}
   127  			}
   128  			return nil
   129  		}
   130  		target, err := getIndexFromSequence(path[0], true, root)
   131  		if err != nil {
   132  			return err
   133  		}
   134  		if err = f.Array().SetYAMLPath(docsProvider, target, value, path[1:]...); err != nil {
   135  			return fmt.Errorf("%v.%w", path[0], err)
   136  		}
   137  		return nil
   138  	case KindArray:
   139  		if len(path) == 0 {
   140  			if value.Kind == yaml.SequenceNode {
   141  				*root = *value
   142  			} else {
   143  				root.Kind = yaml.SequenceNode
   144  				root.Content = []*yaml.Node{value}
   145  			}
   146  			return nil
   147  		}
   148  		target, err := getIndexFromSequence(path[0], true, root)
   149  		if err != nil {
   150  			return err
   151  		}
   152  		if err = f.Scalar().SetYAMLPath(docsProvider, target, value, path[1:]...); err != nil {
   153  			return fmt.Errorf("%v.%w", path[0], err)
   154  		}
   155  		return nil
   156  	case KindMap:
   157  		if len(path) == 0 {
   158  			return errors.New("cannot set map directly")
   159  		}
   160  		target, err := getFieldFromMapping(path[0], true, root)
   161  		if err != nil {
   162  			return err
   163  		}
   164  		if err = f.Scalar().SetYAMLPath(docsProvider, target, value, path[1:]...); err != nil {
   165  			return fmt.Errorf("%v.%w", path[0], err)
   166  		}
   167  		return nil
   168  	}
   169  	if len(path) == 0 {
   170  		*root = *value
   171  		return nil
   172  	}
   173  	if coreType, isCore := f.Type.IsCoreComponent(); isCore {
   174  		if len(path) == 0 {
   175  			return fmt.Errorf("(%v): cannot set core type directly", coreType)
   176  		}
   177  		return setYAMLPathCore(docsProvider, coreType, root, value, path...)
   178  	}
   179  	if len(f.Children) > 0 {
   180  		return f.Children.SetYAMLPath(docsProvider, root, value, path...)
   181  	}
   182  	return fmt.Errorf("%v: field not recognised", path[0])
   183  }
   184  
   185  // GetYAMLPath attempts to obtain a specific value within a YAML tree by
   186  // following a sequence of path identifiers.
   187  func GetYAMLPath(root *yaml.Node, path ...string) (*yaml.Node, error) {
   188  	root = unwrapDocumentNode(root)
   189  
   190  	if len(path) == 0 {
   191  		return root, nil
   192  	}
   193  
   194  	if root.Kind == yaml.SequenceNode {
   195  		newRoot, err := getIndexFromSequence(path[0], false, root)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  		if newRoot, err = GetYAMLPath(newRoot, path[1:]...); err != nil {
   200  			return nil, fmt.Errorf("%v.%w", path[0], err)
   201  		}
   202  		return newRoot, nil
   203  	}
   204  
   205  	newRoot, err := getFieldFromMapping(path[0], false, root)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	if newRoot, err = GetYAMLPath(newRoot, path[1:]...); err != nil {
   210  		return nil, fmt.Errorf("%v.%w", path[0], err)
   211  	}
   212  	return newRoot, nil
   213  }
   214  
   215  //------------------------------------------------------------------------------
   216  
   217  // YAMLLabelsToPaths walks a YAML tree using a field spec as a reference point.
   218  // When a component of the YAML tree has a label field it is added to the
   219  // provided labelsToPaths map with the path to the component.
   220  func (f FieldSpecs) YAMLLabelsToPaths(docsProvider Provider, node *yaml.Node, labelsToPaths map[string][]string, path []string) {
   221  	node = unwrapDocumentNode(node)
   222  
   223  	fieldMap := map[string]FieldSpec{}
   224  	for _, spec := range f {
   225  		fieldMap[spec.Name] = spec
   226  	}
   227  
   228  	for i := 0; i < len(node.Content)-1; i += 2 {
   229  		key := node.Content[i].Value
   230  		if spec, exists := fieldMap[key]; exists {
   231  			spec.YAMLLabelsToPaths(docsProvider, node.Content[i+1], labelsToPaths, append(path, key))
   232  		}
   233  	}
   234  }
   235  
   236  // YAMLLabelsToPaths walks a YAML tree using a field spec as a reference point.
   237  // When a component of the YAML tree has a label field it is added to the
   238  // provided labelsToPaths map with the path to the component.
   239  func (f FieldSpec) YAMLLabelsToPaths(docsProvider Provider, node *yaml.Node, labelsToPaths map[string][]string, path []string) {
   240  	node = unwrapDocumentNode(node)
   241  
   242  	switch f.Kind {
   243  	case Kind2DArray:
   244  		nextSpec := f.Array()
   245  		for i, child := range node.Content {
   246  			nextSpec.YAMLLabelsToPaths(docsProvider, child, labelsToPaths, append(path, strconv.Itoa(i)))
   247  		}
   248  	case KindArray:
   249  		nextSpec := f.Scalar()
   250  		for i, child := range node.Content {
   251  			nextSpec.YAMLLabelsToPaths(docsProvider, child, labelsToPaths, append(path, strconv.Itoa(i)))
   252  		}
   253  	case KindMap:
   254  		nextSpec := f.Scalar()
   255  		for i, child := range node.Content {
   256  			nextSpec.YAMLLabelsToPaths(docsProvider, child, labelsToPaths, append(path, strconv.Itoa(i)))
   257  		}
   258  		for i := 0; i < len(node.Content)-1; i += 2 {
   259  			key := node.Content[i].Value
   260  			nextSpec.YAMLLabelsToPaths(docsProvider, node.Content[i+1], labelsToPaths, append(path, key))
   261  		}
   262  	default:
   263  		if coreType, isCore := f.Type.IsCoreComponent(); isCore {
   264  			if docsProvider == nil {
   265  				docsProvider = globalProvider
   266  			}
   267  			coreFields := FieldSpecs{}
   268  			for _, f := range reservedFieldsByType(coreType) {
   269  				coreFields = append(coreFields, f)
   270  			}
   271  			if inferred, cSpec, err := GetInferenceCandidateFromYAML(docsProvider, coreType, "", node); err == nil {
   272  				conf := cSpec.Config
   273  				conf.Name = inferred
   274  				coreFields = append(coreFields, conf)
   275  			}
   276  			coreFields.YAMLLabelsToPaths(docsProvider, node, labelsToPaths, path)
   277  		} else if len(f.Children) > 0 {
   278  			f.Children.YAMLLabelsToPaths(docsProvider, node, labelsToPaths, path)
   279  		} else if f.Name == labelField.Name && f.Description == labelField.Description {
   280  			pathCopy := make([]string, len(path)-1)
   281  			copy(pathCopy, path[:len(path)-1])
   282  			labelsToPaths[node.Value] = pathCopy // Add path to the parent node
   283  		}
   284  	}
   285  }