From 4fceff029c5cbde62390f29103e84575f748e930 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Wed, 24 Aug 2016 13:26:44 -0700 Subject: [PATCH 1/4] unicode patterns --- gjson.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++--- gjson_test.go | 66 +++++++++++++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 6 deletions(-) diff --git a/gjson.go b/gjson.go index e6cc523..478bf8f 100644 --- a/gjson.go +++ b/gjson.go @@ -485,6 +485,7 @@ func Get(json string, path string) Result { var alogok bool var alogkey string var alog []int + var uc bool // parse the path into multiple parts. for i := 0; i < len(path); i++ { @@ -521,6 +522,10 @@ func Get(json string, path string) Result { } continue } + if path[i] > 0x7f { + uc = true + continue + } if path[i] == '\\' { // go into escape mode. this is a slower path that // strips off the escape character from the part. @@ -530,6 +535,10 @@ func Get(json string, path string) Result { epart = append(epart, path[i]) i++ for ; i < len(path); i++ { + if path[i] > 0x7f { + uc = true + continue + } if path[i] == '\\' { i++ if i < len(path) { @@ -682,7 +691,7 @@ read_key: if parts[depth-1].wild { // the path part contains a wildcard character. we must do a wildcard // match to determine if it truly matches. - matched = wildcardMatch(f.key, parts[depth-1].key) + matched = wildcardMatch(f.key, parts[depth-1].key, uc) } else { // just a straight up equality check matched = parts[depth-1].key == f.key @@ -1093,12 +1102,24 @@ func stringLessInsensitive(a, b string) bool { // wilcardMatch returns true if str matches pattern. This is a very // simple wildcard match where '*' matches on any number characters // and '?' matches on any one character. -func wildcardMatch(str, pattern string) bool { +func wildcardMatch(str, pattern string, uc bool) bool { if pattern == "*" { return true } - return deepMatch(str, pattern) + if !uc { + return deepMatch(str, pattern) + } + rstr := make([]rune, 0, len(str)) + rpattern := make([]rune, 0, len(pattern)) + for _, r := range str { + rstr = append(rstr, r) + } + for _, r := range pattern { + rpattern = append(rpattern, r) + } + return deepMatchRune(rstr, rpattern) } + func deepMatch(str, pattern string) bool { for len(pattern) > 0 { switch pattern[0] { @@ -1111,11 +1132,68 @@ func deepMatch(str, pattern string) bool { return false } case '*': - return wildcardMatch(str, pattern[1:]) || - (len(str) > 0 && wildcardMatch(str[1:], pattern)) + return deepMatch(str, pattern[1:]) || + (len(str) > 0 && deepMatch(str[1:], pattern)) } str = str[1:] pattern = pattern[1:] } return len(str) == 0 && len(pattern) == 0 } +func deepMatchRune(str, pattern []rune) bool { + for len(pattern) > 0 { + switch pattern[0] { + default: + if len(str) == 0 || str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 { + return false + } + case '*': + return deepMatchRune(str, pattern[1:]) || + (len(str) > 0 && deepMatchRune(str[1:], pattern)) + } + str = str[1:] + pattern = pattern[1:] + } + return len(str) == 0 && len(pattern) == 0 +} + +/* +func wildcardMatch(str, pattern string) bool { + if pattern == "*" { + return true + } + rstr := make([]rune, 0, len(str)) + rpattern := make([]rune, 0, len(pattern)) + for _, r := range str { + rstr = append(rstr, r) + } + for _, r := range pattern { + rpattern = append(rpattern, r) + } + return deepMatch(rstr, rpattern) +} +func deepMatch(str, pattern []rune) bool { + for len(pattern) > 0 { + switch pattern[0] { + default: + if len(str) == 0 || str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 { + return false + } + case '*': + return deepMatch(str, pattern[1:]) || + (len(str) > 0 && deepMatch(str[1:], pattern)) + } + str = str[1:] + pattern = pattern[1:] + } + return len(str) == 0 && len(pattern) == 0 +} +*/ diff --git a/gjson_test.go b/gjson_test.go index 401a43f..b5739b7 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -248,7 +248,71 @@ func TestBasic(t *testing.T) { t.Fatalf("expecting %v, got %v", "Jason", fn) } } - +func TestMatch(t *testing.T) { + if !wildcardMatch("hello world", "hello world", false) { + t.Fatal("fail") + } + if wildcardMatch("hello world", "jello world", false) { + t.Fatal("fail") + } + if !wildcardMatch("hello world", "hello*", false) { + t.Fatal("fail") + } + if wildcardMatch("hello world", "jello*", false) { + t.Fatal("fail") + } + if !wildcardMatch("hello world", "hello?world", false) { + t.Fatal("fail") + } + if wildcardMatch("hello world", "jello?world", false) { + t.Fatal("fail") + } + if !wildcardMatch("hello world", "he*o?world", false) { + t.Fatal("fail") + } + if !wildcardMatch("hello world", "he*o?wor*", false) { + t.Fatal("fail") + } + if !wildcardMatch("hello world", "he*o?*r*", false) { + t.Fatal("fail") + } + if !wildcardMatch("的情况下解析一个", "*", true) { + t.Fatal("fail") + } + if !wildcardMatch("的情况下解析一个", "*况下*", true) { + t.Fatal("fail") + } + if !wildcardMatch("的情况下解析一个", "*况?*", true) { + t.Fatal("fail") + } + if !wildcardMatch("的情况下解析一个", "的情况?解析一个", true) { + t.Fatal("fail") + } +} +func TestUnicode(t *testing.T) { + var json = `{"key":0,"的情况下解":{"key":1,"的情况":2}}` + if Get(json, "的情况下解.key").Num != 1 { + t.Fatal("fail") + } + if Get(json, "的情况下解.的情况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情况下解.的?况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情况下解.的?*").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情况下解.*?况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情?下解.*?况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情下解.*?况").Num != 0 { + t.Fatal("fail") + } +} func TestUnescape(t *testing.T) { unescape(string([]byte{'\\', '\\', 0})) unescape(string([]byte{'\\', '/', '\\', 'b', '\\', 'f'})) From ffbbc90c13612cedf5347f02b5d9ec572998d4b6 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Wed, 24 Aug 2016 13:35:10 -0700 Subject: [PATCH 2/4] remove commented code --- gjson.go | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/gjson.go b/gjson.go index 478bf8f..cf73859 100644 --- a/gjson.go +++ b/gjson.go @@ -1119,7 +1119,6 @@ func wildcardMatch(str, pattern string, uc bool) bool { } return deepMatchRune(rstr, rpattern) } - func deepMatch(str, pattern string) bool { for len(pattern) > 0 { switch pattern[0] { @@ -1160,40 +1159,3 @@ func deepMatchRune(str, pattern []rune) bool { } return len(str) == 0 && len(pattern) == 0 } - -/* -func wildcardMatch(str, pattern string) bool { - if pattern == "*" { - return true - } - rstr := make([]rune, 0, len(str)) - rpattern := make([]rune, 0, len(pattern)) - for _, r := range str { - rstr = append(rstr, r) - } - for _, r := range pattern { - rpattern = append(rpattern, r) - } - return deepMatch(rstr, rpattern) -} -func deepMatch(str, pattern []rune) bool { - for len(pattern) > 0 { - switch pattern[0] { - default: - if len(str) == 0 || str[0] != pattern[0] { - return false - } - case '?': - if len(str) == 0 { - return false - } - case '*': - return deepMatch(str, pattern[1:]) || - (len(str) > 0 && deepMatch(str[1:], pattern)) - } - str = str[1:] - pattern = pattern[1:] - } - return len(str) == 0 && len(pattern) == 0 -} -*/ From f4c008206b2b4dc41c59424f57b9bfa79ed94a55 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Wed, 24 Aug 2016 15:50:18 -0700 Subject: [PATCH 3/4] added wildcard test --- gjson_test.go | 306 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) diff --git a/gjson_test.go b/gjson_test.go index b5739b7..f6e96e2 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -313,6 +313,312 @@ func TestUnicode(t *testing.T) { t.Fatal("fail") } } + +// TestWildcardMatch - Tests validate the logic of wild card matching. +// `WildcardMatch` supports '*' and '?' wildcards. +// Sample usage: In resource matching for folder policy validation. +func TestWildcardMatch(t *testing.T) { + testCases := []struct { + pattern string + text string + matched bool + }{ + // Test case - 1. + // Test case with pattern containing key name with a prefix. Should accept the same text without a "*". + { + pattern: "my-folder/oo*", + text: "my-folder/oo", + matched: true, + }, + // Test case - 2. + // Test case with "*" at the end of the pattern. + { + pattern: "my-folder/In*", + text: "my-folder/India/Karnataka/", + matched: true, + }, + // Test case - 3. + // Test case with prefixes shuffled. + // This should fail. + { + pattern: "my-folder/In*", + text: "my-folder/Karnataka/India/", + matched: false, + }, + // Test case - 4. + // Test case with text expanded to the wildcards in the pattern. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Ban", + matched: true, + }, + // Test case - 5. + // Test case with the keyname part is repeated as prefix several times. + // This is valid. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Ban/Ban/Ban/Ban/Ban", + matched: true, + }, + // Test case - 6. + // Test case to validate that `*` can be expanded into multiple prefixes. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Area1/Area2/Area3/Ban", + matched: true, + }, + // Test case - 7. + // Test case to validate that `*` can be expanded into multiple prefixes. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban", + matched: true, + }, + // Test case - 8. + // Test case where the keyname part of the pattern is expanded in the text. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Bangalore", + matched: false, + }, + // Test case - 9. + // Test case with prefixes and wildcard expanded for all "*". + { + pattern: "my-folder/In*/Ka*/Ban*", + text: "my-folder/India/Karnataka/Bangalore", + matched: true, + }, + // Test case - 10. + // Test case with keyname part being a wildcard in the pattern. + {pattern: "my-folder/*", + text: "my-folder/India", + matched: true, + }, + // Test case - 11. + { + pattern: "my-folder/oo*", + text: "my-folder/odo", + matched: false, + }, + + // Test case with pattern containing wildcard '?'. + // Test case - 12. + // "my-folder?/" matches "my-folder1/", "my-folder2/", "my-folder3" etc... + // doesn't match "myfolder/". + { + pattern: "my-folder?/abc*", + text: "myfolder/abc", + matched: false, + }, + // Test case - 13. + { + pattern: "my-folder?/abc*", + text: "my-folder1/abc", + matched: true, + }, + // Test case - 14. + { + pattern: "my-?-folder/abc*", + text: "my--folder/abc", + matched: false, + }, + // Test case - 15. + { + pattern: "my-?-folder/abc*", + text: "my-1-folder/abc", + matched: true, + }, + // Test case - 16. + { + pattern: "my-?-folder/abc*", + text: "my-k-folder/abc", + matched: true, + }, + // Test case - 17. + { + pattern: "my??folder/abc*", + text: "myfolder/abc", + matched: false, + }, + // Test case - 18. + { + pattern: "my??folder/abc*", + text: "my4afolder/abc", + matched: true, + }, + // Test case - 19. + { + pattern: "my-folder?abc*", + text: "my-folder/abc", + matched: true, + }, + // Test case 20-21. + // '?' matches '/' too. (works with s3). + // This is because the namespace is considered flat. + // "abc?efg" matches both "abcdefg" and "abc/efg". + { + pattern: "my-folder/abc?efg", + text: "my-folder/abcdefg", + matched: true, + }, + { + pattern: "my-folder/abc?efg", + text: "my-folder/abc/efg", + matched: true, + }, + // Test case - 22. + { + pattern: "my-folder/abc????", + text: "my-folder/abc", + matched: false, + }, + // Test case - 23. + { + pattern: "my-folder/abc????", + text: "my-folder/abcde", + matched: false, + }, + // Test case - 24. + { + pattern: "my-folder/abc????", + text: "my-folder/abcdefg", + matched: true, + }, + // Test case 25-26. + // test case with no '*'. + { + pattern: "my-folder/abc?", + text: "my-folder/abc", + matched: false, + }, + { + pattern: "my-folder/abc?", + text: "my-folder/abcd", + matched: true, + }, + { + pattern: "my-folder/abc?", + text: "my-folder/abcde", + matched: false, + }, + // Test case 27. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnop", + matched: false, + }, + // Test case 28. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqrst/mnopqr", + matched: true, + }, + // Test case 29. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqrst/mnopqrs", + matched: true, + }, + // Test case 30. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnop", + matched: false, + }, + // Test case 31. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopq", + matched: true, + }, + // Test case 32. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqr", + matched: true, + }, + // Test case 33. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqand", + matched: true, + }, + // Test case 34. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopand", + matched: false, + }, + // Test case 35. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqand", + matched: true, + }, + // Test case 36. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mn", + matched: false, + }, + // Test case 37. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqrst/mnopqrs", + matched: true, + }, + // Test case 38. + { + pattern: "my-folder/mnop*??", + text: "my-folder/mnopqrst", + matched: true, + }, + // Test case 39. + { + pattern: "my-folder/mnop*qrst", + text: "my-folder/mnopabcdegqrst", + matched: true, + }, + // Test case 40. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqand", + matched: true, + }, + // Test case 41. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopand", + matched: false, + }, + // Test case 42. + { + pattern: "my-folder/mnop*?and?", + text: "my-folder/mnopqanda", + matched: true, + }, + // Test case 43. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqanda", + matched: false, + }, + // Test case 44. + + { + pattern: "my-?-folder/abc*", + text: "my-folder/mnopqanda", + matched: false, + }, + } + // Iterating over the test cases, call the function under test and asert the output. + for i, testCase := range testCases { + actualResult := wildcardMatch(testCase.text, testCase.pattern, false) + if testCase.matched != actualResult { + t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult) + } + } +} func TestUnescape(t *testing.T) { unescape(string([]byte{'\\', '\\', 0})) unescape(string([]byte{'\\', '/', '\\', 'b', '\\', 'f'})) From 050904424fde7e878f74ff428e25e5a034fbe50b Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Wed, 24 Aug 2016 15:59:54 -0700 Subject: [PATCH 4/4] added unmarshal map test --- gjson_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gjson_test.go b/gjson_test.go index f6e96e2..ba2c388 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -733,6 +733,25 @@ var exampleJSON = `{ } }` +func TestUnmarshalMap(t *testing.T) { + var m1 = Parse(exampleJSON).Value().(map[string]interface{}) + var m2 map[string]interface{} + if err := json.Unmarshal([]byte(exampleJSON), &m2); err != nil { + t.Fatal(err) + } + b1, err := json.Marshal(m1) + if err != nil { + t.Fatal(err) + } + b2, err := json.Marshal(m2) + if err != nil { + t.Fatal(err) + } + if bytes.Compare(b1, b2) != 0 { + t.Fatal("b1 != b2") + } +} + type BenchStruct struct { Widget struct { Window struct {