github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/kv/record_match.go (about) 1 package kv 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "strings" 8 9 "google.golang.org/protobuf/proto" 10 "google.golang.org/protobuf/reflect/protoreflect" 11 ) 12 13 type Record struct { 14 Partition string `json:"partition"` 15 Key string `json:"key"` 16 Value interface{} `json:"value"` 17 } 18 19 type MatchRecord struct { 20 PartitionPattern string 21 PathPattern string 22 MessageType protoreflect.MessageType 23 } 24 25 var recordsMatches []MatchRecord 26 27 var ErrPatternAlreadyRegistered = errors.New("pattern already registered") 28 29 // RegisterType - Register a pb message type to parse the data, according to a path regex 30 // All objects which match the path regex will be parsed as that type 31 // A nil type parses the value as a plain string 32 func RegisterType(partitionPattern, pathPattern string, mt protoreflect.MessageType) error { 33 // verify we are not trying to add the same record twice 34 for _, rec := range recordsMatches { 35 if rec.PartitionPattern == partitionPattern && rec.PathPattern == pathPattern { 36 return ErrPatternAlreadyRegistered 37 } 38 } 39 40 // add the new record 41 recordsMatches = append(recordsMatches, MatchRecord{ 42 PartitionPattern: partitionPattern, 43 PathPattern: pathPattern, 44 MessageType: mt, 45 }) 46 47 // make sure that we keep the longest pattern first 48 sort.Slice(recordsMatches, func(i, j int) bool { 49 if recordsMatches[i].PartitionPattern == recordsMatches[j].PartitionPattern { 50 return recordsMatches[i].PathPattern > recordsMatches[j].PathPattern 51 } 52 return recordsMatches[i].PartitionPattern > recordsMatches[j].PartitionPattern 53 }) 54 return nil 55 } 56 57 func MustRegisterType(partitionPattern, pathPattern string, mt protoreflect.MessageType) { 58 err := RegisterType(partitionPattern, pathPattern, mt) 59 if err != nil { 60 panic(fmt.Errorf("%w: partition '%s', path '%s'", err, partitionPattern, pathPattern)) 61 } 62 } 63 64 // FindMessageTypeRecord lookup proto message type based on the partition and path. 65 // 66 // Can return nil in case the value is not matched 67 func FindMessageTypeRecord(partition, path string) protoreflect.MessageType { 68 for _, r := range recordsMatches { 69 partitionMatch := patternMatch(r.PartitionPattern, partition) 70 if !partitionMatch { 71 continue 72 } 73 74 pathMatch := patternMatch(r.PathPattern, path) 75 if pathMatch { 76 return r.MessageType 77 } 78 } 79 return nil 80 } 81 82 // patternMatch reports str matches a pattern. The pattern uses '/' as separator of each part. Part can match specific value or any ('*'). 83 // Match works as prefix match as it will verify the given pattern and return success when pattern is completed no matter of the value holds more parts. 84 // 85 // Examples: 86 // 87 // patternMatch("repo", "repo") // true 88 // patternMatch("repo", "repo/something") // true 89 // patternMatch("repo", "repository") // false 90 // patternMatch("*", "repository") // true 91 // patternMatch("repo/*/branches", "repo") // false 92 // patternMatch("repo/*/branches", "repo/branch1") // false 93 // patternMatch("repo/*/branches", "repo/branch1/branches") // true 94 // patternMatch("repo/*/branches", "repo/branch1/branches/objects") // true 95 func patternMatch(pattern string, str string) bool { 96 pp := strings.Split(pattern, "/") 97 v := strings.SplitN(str, "/", len(pp)+1) 98 if len(v) < len(pp) { 99 // value doesn't hold enough parts 100 return false 101 } 102 // match each part to the pattern 103 for i, p := range pp { 104 if p != "*" && p != v[i] { 105 return false 106 } 107 } 108 return true 109 } 110 111 func NewRecord(partition, path string, rawValue []byte) (*Record, error) { 112 mt := FindMessageTypeRecord(partition, path) 113 114 var value interface{} 115 if mt == nil { 116 value = rawValue 117 } else { 118 msg := mt.New().Interface() 119 err := proto.Unmarshal(rawValue, msg) 120 if err != nil { 121 return nil, err 122 } 123 value = msg 124 } 125 126 return &Record{ 127 Partition: partition, 128 Key: path, 129 Value: value, 130 }, nil 131 }