sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/simplifypath/simplify.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package simplifypath
    18  
    19  import (
    20  	"strings"
    21  
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  const unmatchedPath = "unmatched"
    26  
    27  // Simplifier knows how to simplify a path
    28  type Simplifier interface {
    29  	Simplify(path string) string
    30  }
    31  
    32  // NewSimplifier builds a new simplifier for the tree
    33  func NewSimplifier(tree Node) Simplifier {
    34  	return &simplifier{
    35  		tree: tree,
    36  	}
    37  }
    38  
    39  type simplifier struct {
    40  	tree Node
    41  }
    42  
    43  // Simplify returns a variable-free path that can be used as label for prometheus metrics
    44  func (s *simplifier) Simplify(path string) string {
    45  	splitPath := strings.Split(path, "/")
    46  	resolvedPath, matches := resolve(s.tree, splitPath)
    47  	if !matches {
    48  		logrus.WithField("path", path).Debug("Path not handled. This is a bug, please open an issue against the kubernetes/test-infra repository with this error message.")
    49  		return unmatchedPath
    50  	}
    51  	return resolvedPath
    52  }
    53  
    54  type Node struct {
    55  	PathFragment
    56  	children []Node
    57  	// Greedy makes the node match all remnaining path elements as well
    58  	Greedy bool
    59  }
    60  
    61  // PathFragment Interface for tree leafs to help resolve paths
    62  type PathFragment interface {
    63  	Matches(part string) bool
    64  	Represent() string
    65  }
    66  
    67  type literal string
    68  
    69  func (l literal) Matches(part string) bool {
    70  	return string(l) == part
    71  }
    72  
    73  func (l literal) Represent() string {
    74  	return string(l)
    75  }
    76  
    77  type variable string
    78  
    79  func (v variable) Matches(part string) bool {
    80  	return true
    81  }
    82  
    83  func (v variable) Represent() string {
    84  	return ":" + string(v)
    85  }
    86  
    87  func L(fragment string, children ...Node) Node {
    88  	return Node{
    89  		PathFragment: literal(fragment),
    90  		children:     children,
    91  	}
    92  }
    93  
    94  func VGreedy(fragment string) Node {
    95  	return Node{
    96  		PathFragment: variable(fragment),
    97  		Greedy:       true,
    98  	}
    99  }
   100  
   101  func V(fragment string, children ...Node) Node {
   102  	return Node{
   103  		PathFragment: variable(fragment),
   104  		children:     children,
   105  	}
   106  }
   107  
   108  func resolve(parent Node, path []string) (string, bool) {
   109  	if !parent.Matches(path[0]) {
   110  		return "", false
   111  	}
   112  	representation := parent.Represent()
   113  	if len(path) == 1 || parent.Greedy {
   114  		return representation, true
   115  	}
   116  	for _, child := range parent.children {
   117  		suffix, matched := resolve(child, path[1:])
   118  		if matched {
   119  			return strings.Join([]string{representation, suffix}, "/"), true
   120  		}
   121  	}
   122  	return "", false
   123  }