github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/segment_terms.go (about)

     1  package internal
     2  
     3  // https://newrelic.atlassian.net/wiki/display/eng/Language+agent+transaction+segment+terms+rules
     4  
     5  import (
     6  	"encoding/json"
     7  	"strings"
     8  )
     9  
    10  const (
    11  	placeholder = "*"
    12  	separator   = "/"
    13  )
    14  
    15  type segmentRule struct {
    16  	Prefix   string   `json:"prefix"`
    17  	Terms    []string `json:"terms"`
    18  	TermsMap map[string]struct{}
    19  }
    20  
    21  // segmentRules is keyed by each segmentRule's Prefix field with any trailing
    22  // slash removed.
    23  type segmentRules map[string]*segmentRule
    24  
    25  func buildTermsMap(terms []string) map[string]struct{} {
    26  	m := make(map[string]struct{}, len(terms))
    27  	for _, t := range terms {
    28  		m[t] = struct{}{}
    29  	}
    30  	return m
    31  }
    32  
    33  func (rules *segmentRules) UnmarshalJSON(b []byte) error {
    34  	var raw []*segmentRule
    35  
    36  	if err := json.Unmarshal(b, &raw); nil != err {
    37  		return err
    38  	}
    39  
    40  	rs := make(map[string]*segmentRule)
    41  
    42  	for _, rule := range raw {
    43  		prefix := strings.TrimSuffix(rule.Prefix, "/")
    44  		if len(strings.Split(prefix, "/")) != 2 {
    45  			// TODO
    46  			// Warn("invalid segment term rule prefix",
    47  			// 	{"prefix": rule.Prefix})
    48  			continue
    49  		}
    50  
    51  		if nil == rule.Terms {
    52  			// TODO
    53  			// Warn("segment term rule has missing terms",
    54  			// 	{"prefix": rule.Prefix})
    55  			continue
    56  		}
    57  
    58  		rule.TermsMap = buildTermsMap(rule.Terms)
    59  
    60  		rs[prefix] = rule
    61  	}
    62  
    63  	*rules = rs
    64  	return nil
    65  }
    66  
    67  func (rule *segmentRule) apply(name string) string {
    68  	if !strings.HasPrefix(name, rule.Prefix) {
    69  		return name
    70  	}
    71  
    72  	s := strings.TrimPrefix(name, rule.Prefix)
    73  
    74  	leadingSlash := ""
    75  	if strings.HasPrefix(s, separator) {
    76  		leadingSlash = separator
    77  		s = strings.TrimPrefix(s, separator)
    78  	}
    79  
    80  	if "" != s {
    81  		segments := strings.Split(s, separator)
    82  
    83  		for i, segment := range segments {
    84  			_, whitelisted := rule.TermsMap[segment]
    85  			if whitelisted {
    86  				segments[i] = segment
    87  			} else {
    88  				segments[i] = placeholder
    89  			}
    90  		}
    91  
    92  		segments = collapsePlaceholders(segments)
    93  		s = strings.Join(segments, separator)
    94  	}
    95  
    96  	return rule.Prefix + leadingSlash + s
    97  }
    98  
    99  func (rules segmentRules) apply(name string) string {
   100  	if nil == rules {
   101  		return name
   102  	}
   103  
   104  	rule, ok := rules[firstTwoSegments(name)]
   105  	if !ok {
   106  		return name
   107  	}
   108  
   109  	return rule.apply(name)
   110  }
   111  
   112  func firstTwoSegments(name string) string {
   113  	firstSlashIdx := strings.Index(name, separator)
   114  	if firstSlashIdx == -1 {
   115  		return name
   116  	}
   117  
   118  	secondSlashIdx := strings.Index(name[firstSlashIdx+1:], separator)
   119  	if secondSlashIdx == -1 {
   120  		return name
   121  	}
   122  
   123  	return name[0 : firstSlashIdx+secondSlashIdx+1]
   124  }
   125  
   126  func collapsePlaceholders(segments []string) []string {
   127  	j := 0
   128  	prevStar := false
   129  	for i := 0; i < len(segments); i++ {
   130  		segment := segments[i]
   131  		if placeholder == segment {
   132  			if prevStar {
   133  				continue
   134  			}
   135  			segments[j] = placeholder
   136  			j++
   137  			prevStar = true
   138  		} else {
   139  			segments[j] = segment
   140  			j++
   141  			prevStar = false
   142  		}
   143  	}
   144  	return segments[0:j]
   145  }