@@ -18,94 +18,58 @@ package loader
1818
1919import (
2020 "fmt"
21- "strconv"
2221 "strings"
2322
2423 "github.com/compose-spec/compose-go/v2/tree"
25- "gopkg.in/ yaml.v3 "
24+ "github.com/goccy/go- yaml/ast "
2625)
2726
2827type ResetProcessor struct {
29- target interface {}
30- paths []tree.Path
31- visitedNodes map [* yaml.Node ][]string
28+ paths []tree.Path
3229}
3330
34- // UnmarshalYAML implement yaml.Unmarshaler
35- func (p * ResetProcessor ) UnmarshalYAML (value * yaml.Node ) error {
36- p .visitedNodes = make (map [* yaml.Node ][]string )
37- resolved , err := p .resolveReset (value , tree .NewPath ())
38- p .visitedNodes = nil
39- if err != nil {
40- return err
41- }
42- return resolved .Decode (p .target )
31+ func NewResetProcessor (doc * ast.DocumentNode ) PostProcessor {
32+ r := & ResetProcessor {}
33+ r .parse (doc .Body )
34+ return r
4335}
4436
45- // resolveReset detects `!reset` tag being set on yaml nodes and record position in the yaml tree
46- func (p * ResetProcessor ) resolveReset (node * yaml.Node , path tree.Path ) (* yaml.Node , error ) {
47- pathStr := path .String ()
48- // If the path contains "<<", removing the "<<" element and merging the path
49- if strings .Contains (pathStr , ".<<" ) {
50- path = tree .NewPath (strings .Replace (pathStr , ".<<" , "" , 1 ))
51- }
52-
53- // If the node is an alias, We need to process the alias field in order to consider the !override and !reset tags
54- if node .Kind == yaml .AliasNode {
55- if err := p .checkForCycle (node .Alias , path ); err != nil {
56- return nil , err
37+ func (p * ResetProcessor ) parse (n ast.Node ) bool {
38+ switch n .Type () {
39+ case ast .TagType :
40+ t := n .(* ast.TagNode )
41+ tag := t .Start .Value
42+ if tag == "!reset" {
43+ p .paths = append (p .paths , tree .Path (strings .TrimPrefix (n .GetPath (), "$." )))
44+ return true
45+ }
46+ if tag == "!override" {
47+ p .paths = append (p .paths , tree .Path (strings .TrimPrefix (n .GetPath (), "$." )))
48+ return false
49+ }
50+ case ast .MappingType :
51+ node := n .(* ast.MappingNode )
52+ for _ , value := range node .Values {
53+ if p .parse (value .Value ) {
54+ node .Values = removeMapping (node .Values , value .Key .String ())
55+ }
56+ }
57+ case ast .SequenceType :
58+ for _ , value := range n .(* ast.SequenceNode ).Values {
59+ p .parse (value )
5760 }
58-
59- return p .resolveReset (node .Alias , path )
6061 }
6162
62- if node .Tag == "!reset" {
63- p .paths = append (p .paths , path )
64- return nil , nil
65- }
66- if node .Tag == "!override" {
67- p .paths = append (p .paths , path )
68- return node , nil
69- }
63+ return false
64+ }
7065
71- keys := map [string ]int {}
72- switch node .Kind {
73- case yaml .SequenceNode :
74- var nodes []* yaml.Node
75- for idx , v := range node .Content {
76- next := path .Next (strconv .Itoa (idx ))
77- resolved , err := p .resolveReset (v , next )
78- if err != nil {
79- return nil , err
80- }
81- if resolved != nil {
82- nodes = append (nodes , resolved )
83- }
66+ func removeMapping (nodes []* ast.MappingValueNode , key string ) []* ast.MappingValueNode {
67+ for i , node := range nodes {
68+ if node .Key .String () == key {
69+ return append (nodes [:i ], nodes [i + 1 :]... )
8470 }
85- node .Content = nodes
86- case yaml .MappingNode :
87- var key string
88- var nodes []* yaml.Node
89- for idx , v := range node .Content {
90- if idx % 2 == 0 {
91- key = v .Value
92- if line , seen := keys [key ]; seen {
93- return nil , fmt .Errorf ("line %d: mapping key %#v already defined at line %d" , v .Line , key , line )
94- }
95- keys [key ] = v .Line
96- } else {
97- resolved , err := p .resolveReset (v , path .Next (key ))
98- if err != nil {
99- return nil , err
100- }
101- if resolved != nil {
102- nodes = append (nodes , node .Content [idx - 1 ], resolved )
103- }
104- }
105- }
106- node .Content = nodes
10771 }
108- return node , nil
72+ return nodes
10973}
11074
11175// Apply finds the go attributes matching recorded paths and reset them to zero value
@@ -149,48 +113,3 @@ func (p *ResetProcessor) applyNullOverrides(target any, path tree.Path) error {
149113 }
150114 return nil
151115}
152-
153- func (p * ResetProcessor ) checkForCycle (node * yaml.Node , path tree.Path ) error {
154- paths := p .visitedNodes [node ]
155- pathStr := path .String ()
156-
157- for _ , prevPath := range paths {
158- // If we're visiting the exact same path, it's not a cycle
159- if pathStr == prevPath {
160- continue
161- }
162-
163- // If either path is using a merge key, it's legitimate YAML merging
164- if strings .Contains (prevPath , "<<" ) || strings .Contains (pathStr , "<<" ) {
165- continue
166- }
167-
168- // Only consider it a cycle if one path is contained within the other
169- // and they're not in different service definitions
170- if (strings .HasPrefix (pathStr , prevPath + "." ) ||
171- strings .HasPrefix (prevPath , pathStr + "." )) &&
172- ! areInDifferentServices (pathStr , prevPath ) {
173- return fmt .Errorf ("cycle detected: node at path %s references node at path %s" , pathStr , prevPath )
174- }
175- }
176-
177- p .visitedNodes [node ] = append (paths , pathStr )
178- return nil
179- }
180-
181- // areInDifferentServices checks if two paths are in different service definitions
182- func areInDifferentServices (path1 , path2 string ) bool {
183- // Split paths into components
184- parts1 := strings .Split (path1 , "." )
185- parts2 := strings .Split (path2 , "." )
186-
187- // Look for the services component and compare the service names
188- for i := 0 ; i < len (parts1 ) && i < len (parts2 ); i ++ {
189- if parts1 [i ] == "services" && i + 1 < len (parts1 ) &&
190- parts2 [i ] == "services" && i + 1 < len (parts2 ) {
191- // If they're different services, it's not a cycle
192- return parts1 [i + 1 ] != parts2 [i + 1 ]
193- }
194- }
195- return false
196- }
0 commit comments