Files
2026-07-20 04:55:20 +00:00

61 lines
1.7 KiB
Go

package workflowpolicy
import (
"bytes"
"errors"
"fmt"
"io"
"gopkg.in/yaml.v3"
)
func parseWorkflow(source string, data []byte) (*yaml.Node, error) {
decoder := yaml.NewDecoder(bytes.NewReader(data))
var document yaml.Node
if err := decoder.Decode(&document); err != nil {
return nil, &ParseError{Source: source, Cause: err}
}
if len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode {
return nil, &ParseError{Source: source, Cause: errors.New("workflow root must be a mapping")}
}
if err := validateYAMLNode(document.Content[0]); err != nil {
return nil, &ParseError{Source: source, Cause: err}
}
var trailing yaml.Node
err := decoder.Decode(&trailing)
if err == nil && len(trailing.Content) > 0 {
return nil, &ParseError{Source: source, Cause: errors.New("multiple YAML documents are not allowed")}
}
if err != nil && !errors.Is(err, io.EOF) {
return nil, &ParseError{Source: source, Cause: err}
}
return document.Content[0], nil
}
func validateYAMLNode(node *yaml.Node) error {
if node.Kind == yaml.AliasNode {
return fmt.Errorf("YAML aliases are not allowed at line %d", node.Line)
}
if node.Anchor != "" {
return fmt.Errorf("YAML aliases are not allowed; YAML anchors are not allowed at line %d", node.Line)
}
if node.Kind == yaml.MappingNode {
seen := make(map[string]struct{}, len(node.Content)/2)
for index := 0; index < len(node.Content); index += 2 {
key := node.Content[index]
identity := key.Tag + "\x00" + key.Value
if _, exists := seen[identity]; exists {
return fmt.Errorf("duplicate key %q at line %d", key.Value, key.Line)
}
seen[identity] = struct{}{}
}
}
for _, child := range node.Content {
if err := validateYAMLNode(child); err != nil {
return err
}
}
return nil
}