github.com/avenga/couper@v1.12.2/config/sequence/sequence.go (about)

     1  package sequence
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  )
     9  
    10  type List []*Item
    11  
    12  func NewBackendItem(name string) *Item {
    13  	return &Item{
    14  		Name:    name,
    15  		backend: true,
    16  	}
    17  }
    18  
    19  type Item struct {
    20  	BodyRange *hcl.Range
    21  	Name      string
    22  	backend   bool
    23  	deps      List
    24  	parent    *Item
    25  }
    26  
    27  func (s *Item) Add(ref *Item) *Item {
    28  	if strings.TrimSpace(ref.Name) == "" {
    29  		return s
    30  	}
    31  
    32  	ref.parent = s
    33  
    34  	if s.hasAncestor(ref.Name) { // collect names to populate error message
    35  		refs := []string{ref.Name}
    36  		p := s.parent
    37  		for p != s {
    38  			if p.parent == nil {
    39  				break
    40  			}
    41  
    42  			p = p.parent
    43  			name := p.Name
    44  			deps := p.Deps()
    45  			if len(deps) > 0 {
    46  				name = deps[0].Name
    47  			}
    48  			refs = append(refs, name)
    49  		}
    50  
    51  		err := &hcl.Diagnostic{
    52  			Detail: fmt.Sprintf("circular sequence reference: %s",
    53  				strings.Join(append(refs, refs[0]), ",")),
    54  			Severity: hcl.DiagError,
    55  			Subject:  s.BodyRange,
    56  			Summary:  "configuration error",
    57  		}
    58  		panic(err)
    59  	}
    60  
    61  	s.deps = append(s.deps, ref)
    62  	return s
    63  }
    64  
    65  // Deps returns sequence dependency in reversed order since they have to be solved first.
    66  func (s *Item) Deps() List {
    67  	if len(s.deps) < 2 {
    68  		return s.deps
    69  	}
    70  
    71  	var revert List
    72  	for i := len(s.deps); i > 0; i-- {
    73  		revert = append(revert, s.deps[i-1])
    74  	}
    75  	return revert
    76  }
    77  
    78  func (s *Item) HasParent() bool {
    79  	return s != nil && s.parent != nil
    80  }
    81  
    82  func (s *Item) hasAncestor(name string) bool {
    83  	if s == nil {
    84  		return false
    85  	}
    86  
    87  	if !s.HasParent() {
    88  		return false
    89  	}
    90  
    91  	if s.parent.Name == name {
    92  		return true
    93  	}
    94  
    95  	return s.parent.hasAncestor(name)
    96  }
    97  
    98  func resolveSequence(item *Item, resolved, seen *[]string) {
    99  	name := item.Name
   100  	*seen = append(*seen, name)
   101  	for _, dep := range item.Deps() {
   102  		if !containsString(resolved, dep.Name) {
   103  			if !containsString(seen, dep.Name) {
   104  				resolveSequence(dep, resolved, seen)
   105  				continue
   106  			}
   107  		}
   108  	}
   109  
   110  	*resolved = append(*resolved, name)
   111  }
   112  
   113  // Dependencies just collects the deps for filtering purposes.
   114  func Dependencies(items List) (allDeps [][]string) {
   115  	for _, item := range items {
   116  		deps := make([]string, 0)
   117  		seen := make([]string, 0)
   118  		resolveSequence(item, &deps, &seen)
   119  		allDeps = append(allDeps, deps)
   120  	}
   121  	return allDeps
   122  }
   123  
   124  func containsString(slice *[]string, needle string) bool {
   125  	for _, n := range *slice {
   126  		if n == needle {
   127  			return true
   128  		}
   129  	}
   130  	return false
   131  }