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 }