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 }