github.com/newrelic/go-agent@v3.26.0+incompatible/internal/segment_terms.go (about)

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