├── .codecov.yml ├── .github └── workflows │ └── build.yml ├── LICENSE ├── README.md ├── match.go └── match_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "90..100" 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | name: Install dependencies 7 | steps: 8 | - uses: actions/checkout@v3 9 | - uses: actions/setup-go@v3 10 | with: 11 | go-version: "1.13" 12 | - name: "Run and fetch Go data" 13 | run: | 14 | go get github.com/stretchr/testify 15 | go test -race -coverprofile=coverage.txt -covermode=atomic 16 | 17 | go test ./... -short 18 | bash <(curl -s https://codecov.io/bash) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexander Pantyukhin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go pattern matching 2 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/alexpantyukhin/go-pattern-match)](https://goreportcard.com/report/github.com/alexpantyukhin/go-pattern-match) 4 | [![build](https://github.com/alexpantyukhin/go-pattern-match/actions/workflows/build.yml/badge.svg)](https://github.com/alexpantyukhin/go-pattern-match/actions/workflows/build.yml) 5 | [![codecov](https://codecov.io/gh/alexpantyukhin/go-pattern-match/branch/master/graph/badge.svg)](https://codecov.io/gh/alexpantyukhin/go-pattern-match) 6 | [![GoDoc](https://godoc.org/alexpantyukhin/go-pattern-match?status.svg)](https://godoc.org/github.com/alexpantyukhin/go-pattern-match) 7 | [![LICENSE](https://img.shields.io/github/license/alexpantyukhin/go-pattern-match.svg)](https://github.com/alexpantyukhin/go-pattern-match/blob/master/LICENSE) 8 | 9 | It's just another implementation of pattern matching in Go. I have been inspired by [Python pattern matching](https://github.com/santinic/pampy), that's why I wanted to try writing something similar in Go :) 10 | For now the following matching are implemented : 11 | - [x] Simple types (like int, int64, float, float64, bool..). 12 | - [x] Struct type. 13 | - [x] Slices (with HEAD, TAIL, OneOf patterns). 14 | - [x] Dictionary (with ANY, OneOf pattern). 15 | - [x] Regexp. 16 | - [x] Additional custom matching (ability to add special matching for some, structs for example). 17 | 18 | # Usages 19 | 20 | ## Fibonacci example: 21 | 22 | ```go 23 | func fib(n int) int { 24 | _, res := match.Match(n). 25 | When(1, 1). 26 | When(2, 1). 27 | When(match.ANY, func() int { return fib(n-1) + fib(n-2) }). 28 | Result() 29 | 30 | return res.(int) 31 | } 32 | ``` 33 | 34 | ## Simple types: 35 | 36 | ```go 37 | isMatched, mr := match.Match(42). 38 | When(42, 10). 39 | Result() 40 | // isMatched - true, mr - 10 41 | ``` 42 | 43 | ## With Structs: 44 | - Simple check value by type 45 | ```go 46 | val := TestStruct{1} 47 | 48 | isMatched, _ := Match(val). 49 | When(func(TestStruct) {}, 1). 50 | Result() 51 | ``` 52 | 53 | - Check value by type and condition 54 | ```go 55 | val := TestStruct{1} 56 | 57 | isMatched, _ := Match(val). 58 | When(func(ts TestStruct) bool { return ts.value == 42 }, 1). 59 | When(func(ts AnotherStruct) bool { return ts.stringValue == "hello" }, 2). 60 | Result() 61 | ``` 62 | 63 | ## With Maps: 64 | ```go 65 | isMatched, mr := match.Match(map[string]int{ 66 | "rsc": 3711, 67 | "r": 2138, 68 | "gri": 1908, 69 | "adg": 912, 70 | }). 71 | When(map[string]interface{}{ 72 | "rsc": 3711, 73 | "r": 2138, 74 | "gri": 1908, 75 | "adg": match.ANY, 76 | }, true). 77 | Result() 78 | ``` 79 | 80 | ## With Slices: 81 | ```go 82 | isMatched, mr := match.Match([]int{1, 2, 3, 4, 5, 6}). 83 | When([]interface{}{match.HEAD, 3, match.OneOf(3, 4), 5, 6}, 125). 84 | Result() 85 | ``` 86 | 87 | ## With regexps: 88 | ```go 89 | isMatched, mr := match.Match("gophergopher"). 90 | When("gophergopher", func() interface{} { return true }). 91 | Result() 92 | ``` 93 | 94 | ## Without result: 95 | ```go 96 | func main() { 97 | Match(val). 98 | When(42, func() { fmt.Println("You found the answer to life, universe and everything!") }). 99 | When(ANY, func() { fmt.Println("No.. It's not an answer.") }). 100 | Result() 101 | } 102 | ``` 103 | 104 | # Installation 105 | Just `go get` this repository in the following way: 106 | 107 | ``` 108 | go get github.com/alexpantyukhin/go-pattern-match 109 | ``` 110 | 111 | # Full example 112 | ```go 113 | package main 114 | 115 | import ( 116 | "fmt" 117 | "github.com/alexpantyukhin/go-pattern-match" 118 | ) 119 | 120 | func main() { 121 | isMatched, mr := match.Match([]int{1, 2, 3}). 122 | When(42, false). 123 | When([]interface{}{match.HEAD, 2, 3}, true). 124 | Result() 125 | 126 | 127 | if isMatched { 128 | fmt.Println(mr) 129 | } 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /match.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | ) 7 | 8 | type matchKey int 9 | 10 | type matchItem struct { 11 | pattern interface{} 12 | action interface{} 13 | } 14 | 15 | // PatternChecker is func for checking pattern. 16 | type PatternChecker func(pattern interface{}, value interface{}) bool 17 | 18 | var ( 19 | registeredMatchers []PatternChecker 20 | ) 21 | 22 | const ( 23 | // ANY is the pattern which allows any value. 24 | ANY matchKey = 0 25 | // HEAD is the pattern for start element of silce. 26 | HEAD matchKey = 1 27 | // TAIL is the pattern for end element(s) of slice. 28 | TAIL matchKey = 2 29 | ) 30 | 31 | // MatchItem defines a matched item value. 32 | type MatchItem struct { 33 | value interface{} 34 | valueAsSlice []interface{} 35 | } 36 | 37 | type oneOfContainer struct { 38 | items []interface{} 39 | } 40 | 41 | // OneOf defines the pattern where at least one item matches. 42 | func OneOf(items ...interface{}) oneOfContainer { 43 | return oneOfContainer{items} 44 | } 45 | 46 | // Matcher struct 47 | type Matcher struct { 48 | value interface{} 49 | matchItems []matchItem 50 | } 51 | 52 | // Match function takes a value for matching and 53 | func Match(val interface{}) *Matcher { 54 | matchItems := []matchItem{} 55 | return &Matcher{val, matchItems} 56 | } 57 | 58 | // When function adds new pattern for checking matching. 59 | // If pattern matched with value the func will be called. 60 | func (matcher *Matcher) When(val interface{}, fun interface{}) *Matcher { 61 | newMatchItem := matchItem{val, fun} 62 | matcher.matchItems = append(matcher.matchItems, newMatchItem) 63 | 64 | return matcher 65 | } 66 | 67 | // RegisterMatcher register custom pattern. 68 | func RegisterMatcher(pattern PatternChecker) { 69 | registeredMatchers = append(registeredMatchers, pattern) 70 | } 71 | 72 | // Result returns the result value of matching process. 73 | func (matcher *Matcher) Result() (bool, interface{}) { 74 | for _, mi := range matcher.matchItems { 75 | matchedItems, matched := matchValue(mi.pattern, matcher.value) 76 | if matched { 77 | miActionType := reflect.TypeOf(mi.action) 78 | if miActionType.Kind() == reflect.Func { 79 | numberOfArgs := miActionType.NumIn() 80 | lenMatchedItems := len(matchedItems) 81 | if numberOfArgs > lenMatchedItems { 82 | for i := lenMatchedItems; i < numberOfArgs; i++ { 83 | matchedItems = append(matchedItems, MatchItem{value: nil}) 84 | } 85 | } else if lenMatchedItems > numberOfArgs { 86 | matchedItems = matchedItems[:numberOfArgs] 87 | } 88 | 89 | var params []reflect.Value 90 | for i := 0; i < len(matchedItems); i++ { 91 | params = append(params, reflect.ValueOf(matchedItems[i])) 92 | } 93 | 94 | funcRes := reflect.ValueOf(mi.action).Call(params) 95 | if (len(funcRes)) > 0 { 96 | return true, funcRes[0].Interface() 97 | } 98 | 99 | return true, nil 100 | } 101 | 102 | return true, mi.action 103 | } 104 | } 105 | 106 | return false, nil 107 | } 108 | 109 | func matchValue(pattern interface{}, value interface{}) ([]MatchItem, bool) { 110 | if pattern == ANY { 111 | return nil, true 112 | } 113 | 114 | for _, registerMatcher := range registeredMatchers { 115 | if registerMatcher(pattern, value) { 116 | return nil, true 117 | } 118 | } 119 | 120 | // Handle the case when value has simple type 121 | simpleTypes := []reflect.Kind{reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, 122 | reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, 123 | reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, 124 | reflect.Complex64, reflect.Complex128, 125 | } 126 | 127 | valueKind := reflect.TypeOf(value).Kind() 128 | valueIsSimpleType := containsKind(simpleTypes, valueKind) 129 | 130 | if (valueIsSimpleType) && value == pattern { 131 | return nil, true 132 | } 133 | 134 | // Handle the case when value has slice or array type 135 | patternType := reflect.TypeOf(pattern) 136 | patternKind := patternType.Kind() 137 | 138 | if (valueKind == reflect.Slice || valueKind == reflect.Array) && 139 | patternKind == reflect.Slice { 140 | 141 | matchedItems, isMatched := matchSlice(pattern, value) 142 | if isMatched { 143 | return matchedItems, isMatched 144 | } 145 | } 146 | 147 | // Handle the case when pattern has func type for calculating some extra conditions for matching 148 | if patternKind == reflect.Func && patternType.NumIn() == 1 { 149 | if patternType.NumOut() == 0 && matchStruct(patternType.In(0), value) { 150 | return nil, true 151 | } 152 | 153 | if patternType.NumOut() == 1 && patternType.Out(0).Kind() == reflect.Bool { 154 | funcRes := reflect.ValueOf(pattern).Call([]reflect.Value{reflect.ValueOf(value)}) 155 | return nil, funcRes[0].Interface().(bool) 156 | } 157 | } 158 | 159 | // Handle the case when value has map type 160 | if valueKind == reflect.Map && 161 | patternKind == reflect.Map && 162 | matchMap(pattern, value) { 163 | 164 | return nil, true 165 | } 166 | 167 | // Handle the case when value has string type 168 | if valueKind == reflect.String { 169 | // When pattern is string 170 | if patternKind == reflect.String && pattern == value { 171 | return nil, true 172 | } 173 | 174 | // When pattern is regexp 175 | reg, ok := pattern.(*regexp.Regexp) 176 | if ok { 177 | if matchRegexp(reg, value) { 178 | return nil, true 179 | } 180 | } 181 | } 182 | 183 | if valueKind == reflect.Struct && patternKind == reflect.Struct && value == pattern { 184 | return nil, true 185 | } 186 | 187 | return nil, false 188 | } 189 | 190 | func matchSlice(pattern interface{}, value interface{}) ([]MatchItem, bool) { 191 | patternSlice := reflect.ValueOf(pattern) 192 | patternSliceLen := patternSlice.Len() 193 | 194 | valueSlice := reflect.ValueOf(value) 195 | valueSliceLen := valueSlice.Len() 196 | 197 | if patternSliceLen > 0 && patternSlice.Index(0).Interface() == HEAD { 198 | if valueSliceLen == 0 { 199 | return nil, false 200 | } 201 | 202 | patternSliceVal := patternSlice.Slice(1, patternSliceLen) 203 | patternSliceLen = patternSliceVal.Len() 204 | patternSliceInterface := patternSliceVal.Interface() 205 | 206 | for i := 0; i < valueSliceLen-patternSliceLen+1; i++ { 207 | matchedItems, isMatched := matchSubSlice(patternSliceInterface, valueSlice.Slice(i, valueSliceLen).Interface()) 208 | resMatchedItems := append([]MatchItem{{valueAsSlice: sliceValueToSliceOfInterfaces(valueSlice.Slice(0, i))}}, matchedItems...) 209 | if isMatched { 210 | return resMatchedItems, true 211 | } 212 | } 213 | 214 | return nil, false 215 | } 216 | 217 | return matchSubSlice(pattern, value) 218 | } 219 | 220 | func matchSubSlice(pattern interface{}, value interface{}) ([]MatchItem, bool) { 221 | patternSlice := reflect.ValueOf(pattern) 222 | valueSlice := reflect.ValueOf(value) 223 | 224 | patternSliceLength := patternSlice.Len() 225 | valueSliceLength := valueSlice.Len() 226 | 227 | matchedItems := make([]MatchItem, 0) 228 | 229 | if patternSliceLength == 0 || valueSliceLength == 0 { 230 | if patternSliceLength == valueSliceLength { 231 | return nil, true 232 | } 233 | 234 | return nil, false 235 | } 236 | 237 | patternSliceMaxIndex := patternSliceLength - 1 238 | valueSliceMaxIndex := valueSliceLength - 1 239 | oneOfContainerType := reflect.TypeOf(oneOfContainer{}) 240 | 241 | for i := 0; i < max(patternSliceLength, valueSliceLength); i++ { 242 | currPatternIndex := min(i, patternSliceMaxIndex) 243 | currValueIndex := min(i, valueSliceMaxIndex) 244 | 245 | currPattern := patternSlice.Index(currPatternIndex).Interface() 246 | currValue := valueSlice.Index(currValueIndex).Interface() 247 | 248 | if currPattern == HEAD { 249 | panic("HEAD can only be in first position of a pattern.") 250 | } else if currPattern == TAIL { 251 | if patternSliceMaxIndex > i { 252 | panic("TAIL must me in last position of the pattern.") 253 | } else { 254 | matchedItems = append(matchedItems, MatchItem{valueAsSlice: sliceValueToSliceOfInterfaces(valueSlice.Slice(i, valueSliceMaxIndex+1))}) 255 | break 256 | } 257 | } else if reflect.TypeOf(currPattern).AssignableTo(oneOfContainerType) { 258 | if !oneOfContainerPatternMatch(currPattern, currValue) { 259 | return matchedItems, false 260 | } 261 | } else if currPattern == ANY { 262 | matchedItems = append(matchedItems, MatchItem{value: currValue}) 263 | continue 264 | } else { 265 | isMatched := matchValueBool(currPattern, currValue) 266 | 267 | if !isMatched { 268 | return matchedItems, false 269 | } 270 | } 271 | } 272 | 273 | return matchedItems, true 274 | } 275 | 276 | func sliceValueToSliceOfInterfaces(val reflect.Value) []interface{} { 277 | var res []interface{} 278 | for i := 0; i < val.Len(); i++ { 279 | res = append(res, val.Index(i).Interface()) 280 | } 281 | 282 | return res 283 | } 284 | 285 | func matchStruct(patternType reflect.Type, value interface{}) bool { 286 | if patternType.AssignableTo(reflect.TypeOf(value)) { 287 | return true 288 | } 289 | 290 | return false 291 | } 292 | 293 | func matchMap(pattern interface{}, value interface{}) bool { 294 | patternMap := reflect.ValueOf(pattern) 295 | valueMap := reflect.ValueOf(value) 296 | 297 | stillUsablePatternKeys := patternMap.MapKeys() 298 | stillUsableValueKeys := valueMap.MapKeys() 299 | oneOfContainerType := reflect.TypeOf(oneOfContainer{}) 300 | 301 | for _, pKey := range patternMap.MapKeys() { 302 | if !containsValue(stillUsablePatternKeys, pKey) { 303 | continue 304 | } 305 | pVal := patternMap.MapIndex(pKey) 306 | matchedLeftAndRight := false 307 | 308 | for _, vKey := range valueMap.MapKeys() { 309 | if !containsValue(stillUsableValueKeys, vKey) || !containsValue(stillUsablePatternKeys, pKey) { 310 | continue 311 | } 312 | 313 | vVal := valueMap.MapIndex(vKey) 314 | keyMatched := pKey.Interface() == vKey.Interface() 315 | if keyMatched { 316 | pValInterface := pVal.Interface() 317 | vValInterface := vVal.Interface() 318 | valueMatched := pValInterface == ANY || matchValueBool(pValInterface, vValInterface) || 319 | (reflect.TypeOf(pValInterface).AssignableTo(oneOfContainerType) && oneOfContainerPatternMatch(pValInterface, vValInterface)) 320 | if valueMatched { 321 | matchedLeftAndRight = true 322 | removeValue(stillUsablePatternKeys, pKey) 323 | removeValue(stillUsableValueKeys, vKey) 324 | } 325 | } 326 | } 327 | 328 | if !matchedLeftAndRight { 329 | return false 330 | } 331 | } 332 | 333 | return true 334 | } 335 | 336 | func matchValueBool(pattern interface{}, value interface{}) bool { 337 | _, res := matchValue(pattern, value) 338 | return res 339 | } 340 | 341 | func oneOfContainerPatternMatch(oneOfPattern interface{}, value interface{}) bool { 342 | oneOfContainerPatternInstance := oneOfPattern.(oneOfContainer) 343 | for _, item := range oneOfContainerPatternInstance.items { 344 | if matchValueBool(item, value) { 345 | return true 346 | } 347 | } 348 | 349 | return false 350 | } 351 | 352 | func matchRegexp(regexp *regexp.Regexp, value interface{}) bool { 353 | valueStr := value.(string) 354 | return regexp.MatchString(valueStr) 355 | } 356 | 357 | func min(a, b int) int { 358 | if a < b { 359 | return a 360 | } 361 | 362 | return b 363 | } 364 | 365 | func max(a, b int) int { 366 | if a < b { 367 | return b 368 | } 369 | 370 | return a 371 | } 372 | 373 | func removeValue(vals []reflect.Value, val reflect.Value) []reflect.Value { 374 | indexOf := -1 375 | for index, v := range vals { 376 | if val.Interface() == v.Interface() { 377 | indexOf = index 378 | break 379 | } 380 | } 381 | 382 | vals[indexOf] = vals[len(vals)-1] 383 | vals = vals[:len(vals)-1] 384 | 385 | return vals 386 | } 387 | 388 | func containsValue(vals []reflect.Value, val reflect.Value) bool { 389 | valInterface := val.Interface() 390 | for _, v := range vals { 391 | if valInterface == v.Interface() { 392 | return true 393 | } 394 | } 395 | 396 | return false 397 | } 398 | 399 | func containsKind(vals []reflect.Kind, val reflect.Kind) bool { 400 | for _, v := range vals { 401 | if val == v { 402 | return true 403 | } 404 | } 405 | 406 | return false 407 | } 408 | -------------------------------------------------------------------------------- /match_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMatch_SimpleTypeInt(t *testing.T) { 11 | isMatched, _ := Match(42). 12 | When(42, true). 13 | Result() 14 | 15 | assert.True(t, isMatched) 16 | } 17 | 18 | func TestMatch_AnyPattern(t *testing.T) { 19 | _, res := Match(5). 20 | When(1, 1). 21 | When(2, 2). 22 | When(ANY, 10). 23 | Result() 24 | 25 | assert.Equal(t, 10, res) 26 | } 27 | 28 | func fib(n int) int { 29 | _, res := Match(n). 30 | When(1, 1). 31 | When(2, 1). 32 | When(ANY, func() int { return fib(n-1) + fib(n-2) }). 33 | Result() 34 | 35 | return res.(int) 36 | } 37 | 38 | func TestMatch_Fibonacci(t *testing.T) { 39 | assert.Equal(t, 21, fib(8)) 40 | } 41 | 42 | func TestMatch_MatchWithoutResult(t *testing.T) { 43 | Match(10). 44 | When(10, func() { assert.True(t, true) }). 45 | When(ANY, func() { assert.False(t, true) }). 46 | Result() 47 | } 48 | 49 | func TestMatch_SimpleTypeIntWithFunc(t *testing.T) { 50 | _, res := Match(42). 51 | When(42, func() interface{} { return 84 }). 52 | Result() 53 | 54 | assert.Equal(t, 84, res) 55 | } 56 | 57 | func TestMatch_SimpleTypeIntMatchWithDifferentType(t *testing.T) { 58 | isMatched, _ := Match(42). 59 | When(int64(42), true). 60 | Result() 61 | 62 | assert.False(t, isMatched) 63 | } 64 | 65 | func TestMatch_SliceWithHead(t *testing.T) { 66 | isMatched, _ := Match([]interface{}{1, 2, 3}). 67 | When([]interface{}{HEAD, 2, 3}, true). 68 | Result() 69 | 70 | assert.True(t, isMatched) 71 | } 72 | 73 | func TestMatch_SliceWithAny(t *testing.T) { 74 | isMatched, _ := Match([]interface{}{1, 2, 3}). 75 | When([]interface{}{1, ANY, 3}, true). 76 | Result() 77 | 78 | assert.True(t, isMatched) 79 | } 80 | 81 | func TestMatch_SliceWithValueIntType(t *testing.T) { 82 | isMatched, _ := Match([]int{1, 2, 3}). 83 | When([]interface{}{HEAD, 2, 3}, true). 84 | Result() 85 | 86 | assert.True(t, isMatched) 87 | } 88 | 89 | func TestMatch_SlicePanicsWhenHeadNotFirst(t *testing.T) { 90 | mr := Match([]int{1, 2, 3}). 91 | When([]interface{}{1, HEAD, 3}, true) 92 | 93 | assert.Panics(t, func() { mr.Result() }) 94 | } 95 | 96 | func TestMatch_SlicePanicsWhenTailNotLast(t *testing.T) { 97 | mr := Match([]int{1, 2, 3}). 98 | When([]interface{}{1, TAIL, 3}, true) 99 | 100 | assert.Panics(t, func() { mr.Result() }) 101 | } 102 | 103 | func TestMatch_SliceHeadNotMatched(t *testing.T) { 104 | isMatched, _ := Match([]int{}). 105 | When([]interface{}{HEAD}, true). 106 | Result() 107 | 108 | assert.False(t, isMatched) 109 | } 110 | 111 | func TestMatch_SliceWithHeadMoreThanOneElement(t *testing.T) { 112 | isMatched, _ := Match([]interface{}{1, 2, 3}). 113 | When([]interface{}{HEAD, 3}, true). 114 | Result() 115 | 116 | assert.True(t, isMatched) 117 | } 118 | 119 | func TestMatch_SliceWithOneOf(t *testing.T) { 120 | isMatched, _ := Match([]interface{}{1, 2, 3}). 121 | When([]interface{}{1, OneOf(1, 2, 3), 3}, true). 122 | Result() 123 | 124 | assert.True(t, isMatched) 125 | } 126 | 127 | func TestMatch_SliceWithOneOfDoesntMatch(t *testing.T) { 128 | isMatched, _ := Match([]interface{}{1, 2, 3}). 129 | When([]interface{}{1, OneOf(4, 5, 6), 3}, true). 130 | Result() 131 | 132 | assert.False(t, isMatched) 133 | } 134 | 135 | func TestMatch_ArrayWithAny(t *testing.T) { 136 | isMatched, _ := Match([3]int{1, 2, 3}). 137 | When([]interface{}{1, ANY, 3}, true). 138 | Result() 139 | 140 | assert.True(t, isMatched) 141 | } 142 | 143 | func TestMatch_SliceWithMatchedItems(t *testing.T) { 144 | isMatched, res := Match([]interface{}{1, 2, 3, 4, 5}). 145 | When([]interface{}{HEAD, 3, TAIL}, func(head MatchItem, tail MatchItem) [][]interface{} { 146 | return [][]interface{}{head.valueAsSlice, tail.valueAsSlice} 147 | }). 148 | Result() 149 | 150 | convertedRes := res.([][]interface{}) 151 | 152 | assert := assert.New(t) 153 | 154 | assert.True(isMatched) 155 | assert.Equal(1, convertedRes[0][0].(int)) 156 | assert.Equal(2, convertedRes[0][1].(int)) 157 | 158 | assert.Equal(4, convertedRes[1][0].(int)) 159 | assert.Equal(5, convertedRes[1][1].(int)) 160 | } 161 | 162 | func TestMatch_SliceNotMatchWithHeadAndWrongPatternLater(t *testing.T) { 163 | isMatched, _ := Match([]interface{}{1, 2, 3, 4, 5}). 164 | When([]interface{}{HEAD, 10, 11}, true). 165 | Result() 166 | 167 | assert.False(t, isMatched) 168 | } 169 | 170 | func TestMatch_SliceWithMatchedItemsWithAny(t *testing.T) { 171 | isMatched, res := Match([]interface{}{1, 2, 3, 4, 5}). 172 | When([]interface{}{HEAD, 2, ANY, 4, TAIL}, func(head MatchItem, any MatchItem, tail MatchItem) [][]interface{} { 173 | return [][]interface{}{head.valueAsSlice, {any.value}, tail.valueAsSlice} 174 | }). 175 | Result() 176 | 177 | convertedRes := res.([][]interface{}) 178 | 179 | assert := assert.New(t) 180 | 181 | assert.True(isMatched) 182 | assert.Equal(1, convertedRes[0][0].(int)) 183 | assert.Equal(3, convertedRes[1][0].(int)) 184 | assert.Equal(5, convertedRes[2][0].(int)) 185 | } 186 | 187 | func TestMatch_SliceWithMatchedItemsWithLessPatternsParamteresInAction(t *testing.T) { 188 | isMatched, res := Match([]interface{}{1, 2, 3, 4, 5}). 189 | When([]interface{}{HEAD, 2, ANY, 4, TAIL}, func(head MatchItem) [][]interface{} { 190 | return [][]interface{}{head.valueAsSlice} 191 | }). 192 | Result() 193 | 194 | convertedRes := res.([][]interface{}) 195 | 196 | assert := assert.New(t) 197 | 198 | assert.True(isMatched) 199 | assert.Equal(1, len(convertedRes)) 200 | assert.Equal(1, convertedRes[0][0].(int)) 201 | } 202 | 203 | func TestMatch_SliceWithMatchedItemsWithGreaterPatternsParamteresInAction(t *testing.T) { 204 | isMatched, res := Match([]interface{}{1, 2, 3, 4, 5}). 205 | When([]interface{}{HEAD, 5}, func(head MatchItem, any MatchItem) [][]interface{} { 206 | return [][]interface{}{head.valueAsSlice} 207 | }). 208 | Result() 209 | 210 | convertedRes := res.([][]interface{}) 211 | 212 | assert := assert.New(t) 213 | 214 | assert.True(isMatched) 215 | assert.Equal(1, len(convertedRes)) 216 | assert.Equal(1, convertedRes[0][0].(int)) 217 | } 218 | 219 | func TestMatch_SlicePatternAndValueAreEmpty(t *testing.T) { 220 | isMatched, _ := Match([]interface{}{}). 221 | When([]interface{}{}, true). 222 | Result() 223 | 224 | assert.True(t, isMatched) 225 | } 226 | 227 | func TestMatch_SlicePatternEmptyAndValueNotEmpty(t *testing.T) { 228 | isMatched, _ := Match([]interface{}{1, 2, 3, 4, 5}). 229 | When([]interface{}{}, true). 230 | Result() 231 | 232 | assert.False(t, isMatched) 233 | } 234 | 235 | func TestMatch_SlicePatternNotEmptyAndValueEmpty(t *testing.T) { 236 | isMatched, _ := Match([]interface{}{}). 237 | When([]interface{}{1, 2, 3, 4, 5}, true). 238 | Result() 239 | 240 | assert.False(t, isMatched) 241 | } 242 | 243 | func TestMatch_Map(t *testing.T) { 244 | isMatched, _ := Match(map[string]int{ 245 | "rsc": 3711, 246 | "r": 2138, 247 | "gri": 1908, 248 | "adg": 912, 249 | }). 250 | When(map[string]int{ 251 | "rsc": 3711, 252 | "r": 2138, 253 | "gri": 1908, 254 | "adg": 912, 255 | }, true). 256 | Result() 257 | 258 | assert.True(t, isMatched) 259 | } 260 | 261 | func TestMatch_MapPatternWithAny(t *testing.T) { 262 | isMatched, _ := Match(map[string]int{ 263 | "rsc": 3711, 264 | "r": 2138, 265 | "gri": 1908, 266 | "adg": 912, 267 | }). 268 | When(map[string]interface{}{ 269 | "rsc": 3711, 270 | "r": 2138, 271 | "gri": 1908, 272 | "adg": ANY, 273 | }, true). 274 | Result() 275 | 276 | assert.True(t, isMatched) 277 | } 278 | 279 | func TestMatch_MapPatternWithOneOf(t *testing.T) { 280 | isMatched, _ := Match(map[string]int{ 281 | "rsc": 3711, 282 | "r": 2138, 283 | "gri": 1908, 284 | "adg": 912, 285 | }). 286 | When(map[string]interface{}{ 287 | "rsc": 3711, 288 | "r": 2138, 289 | "gri": 1908, 290 | "adg": OneOf(111, 912), 291 | }, true). 292 | Result() 293 | 294 | assert.True(t, isMatched) 295 | } 296 | 297 | func TestMatch_MapPatternWithOneOfNotMatch(t *testing.T) { 298 | isMatched, _ := Match(map[string]int{ 299 | "rsc": 3711, 300 | "r": 2138, 301 | "gri": 1908, 302 | "adg": 912, 303 | }). 304 | When(map[string]interface{}{ 305 | "rsc": 3711, 306 | "r": 2138, 307 | "gri": 1908, 308 | "adg": OneOf(111, 913), 309 | }, true). 310 | Result() 311 | 312 | assert.False(t, isMatched) 313 | } 314 | 315 | func TestMatch_MapPatternDifferentValue(t *testing.T) { 316 | isMatched, _ := Match(map[string]int{ 317 | "rsc": 3711, 318 | "r": 2138, 319 | "gri": 1908, 320 | "adg": 912, 321 | }). 322 | When(map[string]interface{}{ 323 | "rsc": 3711, 324 | "r": 2138, 325 | "gri": 1908, 326 | "adg": 1, 327 | }, true). 328 | Result() 329 | 330 | assert.False(t, isMatched) 331 | } 332 | 333 | func TestMatch_MapWithInnerSlice(t *testing.T) { 334 | isMatched, _ := Match(map[string]interface{}{ 335 | "rsc": 3711, 336 | "r": 2138, 337 | "gri": 1908, 338 | "adg": []int{1, 2, 3}, 339 | }). 340 | When(map[string]interface{}{ 341 | "rsc": 3711, 342 | "r": 2138, 343 | "gri": 1908, 344 | "adg": []interface{}{HEAD, 2, TAIL}, 345 | }, true). 346 | Result() 347 | 348 | assert.True(t, isMatched) 349 | } 350 | 351 | func TestMatch_String(t *testing.T) { 352 | isMatched, _ := Match("gophergopher"). 353 | When("gophergopher", true). 354 | Result() 355 | 356 | assert.True(t, isMatched) 357 | } 358 | 359 | func TestMatch_Regexp(t *testing.T) { 360 | isMatched, _ := Match("gophergopher"). 361 | When(regexp.MustCompile("(gopher){2}"), true). 362 | Result() 363 | 364 | assert.True(t, isMatched) 365 | } 366 | 367 | func TestMatch_RegisterPattern(t *testing.T) { 368 | myMagicChecker := func(pattern interface{}, value interface{}) bool { 369 | 370 | if pattern == 12345 { 371 | return true 372 | } 373 | 374 | return false 375 | } 376 | 377 | RegisterMatcher(myMagicChecker) 378 | isMatched, _ := Match(1000). 379 | When(12345, true). 380 | When(1000, false). 381 | Result() 382 | 383 | assert.True(t, isMatched) 384 | } 385 | 386 | type TestStruct struct { 387 | value int 388 | } 389 | 390 | func TestMatch_SimpleStructMatch(t *testing.T) { 391 | val := TestStruct{1} 392 | 393 | isMatched, _ := Match(val). 394 | When(TestStruct{1}, 1). 395 | Result() 396 | 397 | assert.True(t, isMatched) 398 | } 399 | 400 | func TestMatch_SimpleStructNotMatch(t *testing.T) { 401 | val := TestStruct{1} 402 | 403 | isMatched, _ := Match(val). 404 | When(TestStruct{2}, 1). 405 | Result() 406 | 407 | assert.False(t, isMatched) 408 | } 409 | 410 | func TestMatch_StructTypeMatch(t *testing.T) { 411 | val := TestStruct{1} 412 | 413 | isMatched, _ := Match(val). 414 | When(func(TestStruct) {}, 1). 415 | Result() 416 | 417 | assert.True(t, isMatched) 418 | } 419 | 420 | func TestMatch_StructFuncMatch(t *testing.T) { 421 | val := TestStruct{1} 422 | 423 | isMatched, _ := Match(val). 424 | When(func(ts TestStruct) bool { return ts.value == 1 }, 1). 425 | Result() 426 | 427 | assert.True(t, isMatched) 428 | } 429 | 430 | func TestMatch_StructFuncNotMatch(t *testing.T) { 431 | val := TestStruct{1} 432 | 433 | isMatched, _ := Match(val). 434 | When(func(ts TestStruct) bool { return ts.value == 2 }, 1). 435 | Result() 436 | 437 | assert.False(t, isMatched) 438 | } 439 | 440 | type AnotherTestStruct struct { 441 | value int 442 | } 443 | 444 | func TestMatch_StructDifferentTypeNotMatch(t *testing.T) { 445 | val := TestStruct{1} 446 | 447 | isMatched, _ := Match(val). 448 | When(func(AnotherTestStruct) {}, 1). 449 | Result() 450 | 451 | assert.False(t, isMatched) 452 | } 453 | --------------------------------------------------------------------------------