From 05476fffbb19d283686e8359094f239c87822d5d Mon Sep 17 00:00:00 2001 From: Charles Iliya Krempeaux Date: Thu, 25 Feb 2016 14:59:38 -0800 Subject: [PATCH] initial commit. still need to create more tests. --- LICENSE | 19 + README.md | 79 ++++ bad_request_complainer.go | 121 +++++ compile.go | 114 +++++ compile_test.go | 560 ++++++++++++++++++++++++ doc.go | 62 +++ internal_error_complainer.go | 57 +++ not_enough_arguments_complainer.go | 69 +++ pattern.go | 65 +++ pattern_load.go | 99 +++++ pattern_load_test.go | 168 +++++++ pattern_match.go | 56 +++ pattern_match_test.go | 129 ++++++ pattern_syntax_error_complainer.go | 85 ++++ scan_error_complainer.go | 61 +++ set.go | 37 ++ set_test.go | 376 ++++++++++++++++ struct_field_wrong_type_complainer.go | 57 +++ unsupported_argument_type_complainer.go | 107 +++++ 19 files changed, 2321 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bad_request_complainer.go create mode 100644 compile.go create mode 100644 compile_test.go create mode 100644 doc.go create mode 100644 internal_error_complainer.go create mode 100644 not_enough_arguments_complainer.go create mode 100644 pattern.go create mode 100644 pattern_load.go create mode 100644 pattern_load_test.go create mode 100644 pattern_match.go create mode 100644 pattern_match_test.go create mode 100644 pattern_syntax_error_complainer.go create mode 100644 scan_error_complainer.go create mode 100644 set.go create mode 100644 set_test.go create mode 100644 struct_field_wrong_type_complainer.go create mode 100644 unsupported_argument_type_complainer.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aeef12f --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 Charles Iliya Krempeaux :: http://changelog.ca/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1544bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# go-pathmatch + +A library that provides *pattern matching* for paths, for the Go programming language. + +For example, a path could be a file system path, or a path could be a path from a URL (such as an HTTP or HTTPS based URL). + + +## Documention + +Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-pathmatch + +[![GoDoc](https://godoc.org/github.com/reiver/go-pathmatch?status.svg)](https://godoc.org/github.com/reiver/go-pathmatch) + + +## Example Usage +``` +import ( + "github.com/reiver/go-pathmatch" +) + +// ... + +pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}") +if nil != err { + //@TODO +} + +var userId string +var vehicleId string + +didMatch, err := pattern.Match("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &userId, &vehicleId) + +if nil != err { + //@TODO +} + +if didMatch { + fmt.Println("The path matched!") + + fmt.Printf("user_id = %q \n", userId) // user_id = "bMM_kJFMEV" + fmt.Printf("vehicle_id = %q \n", vehicleId) // vehicle_id = "o_bcU.RZGK" +} else { + fmt.Println("The patch did not match.") +} +``` + +Alternatively: +``` +import ( + "github.com/reiver/go-pathmatch" +) + +// ... + +pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}") +if nil != err { + //@TODO +} + +data := struct{ + UserId string `match:"user_id"` + VehicleId string `match:"vehicle_id"` +}{} + +didMatch, err := pattern.MatchAndLoad("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &data) + +if nil != err { + //@TODO +} + +if didMatch { + fmt.Println("The path matched!") + + fmt.Printf("user_id = %q \n", data.UserId) // user_id = "bMM_kJFMEV" + fmt.Printf("vehicle_id = %q \n", data.VehicleId) // vehicle_id = "o_bcU.RZGK" +} else { + fmt.Println("The patch did not match.") +} +``` diff --git a/bad_request_complainer.go b/bad_request_complainer.go new file mode 100644 index 0000000..942958c --- /dev/null +++ b/bad_request_complainer.go @@ -0,0 +1,121 @@ +package pathmatch + + +// BadRequestComplainer is used to represent one of the types of errors that could be returned when +// calling the pathmatch.Compile func, the pathmatch.Pattern.Match method, or the pathmatch.Pattern.MatchAndLoad +// method. The meaning of this type of error is that the problem was due to something whomever called the func or method did. +// +// For example, maybe the uncompiled pattern passed to the pathmatch.Compile() func had +// a syntax error in it. Or, also for example, maybe the type of parameter passed to +// pathmatch.Pattern.Match() was of the wrong type. Etc. +// +// Example usage of BadRequestComplainer with pathmatch.Compile(): +// +// pattern, err := pathmatch.Compile("/fruits/{there_is_an_error_in_here/") // ← The uncompiled pattern there has an error in it. +// if nil != err { +// switch err.(type) { // ← Note that we are using a Go type-switch here. +// +// case pathmatch.BadRequestComplainer: // ← Here we are detecting if the error returned is a pathmatch.BadRequestComplainer. +// +// fmt.Printf("Something you did when you called pathmatch.Compile() caused an error. The error message was....\n%s\n", err.Error()) +// return +// +// case pathmatch.InternalErrorComplainer: +// +// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pathmatch.Compile() was running. The error message was....\n%s\n", err.Error()) +// return +// +// default: +// +// fmt.Printf("Some kind of unexpected error happend: %v", err) +// return +// } +// } +// +// Somewhat continuing this example (although without the error in the uncompiled pattern), we might then +// use the pathmatch.Pattern.Match() method, which could also generate an error that fits the BadRequestComplainer +// interface. +// +// Example usage of BadRequestComplainer with pathmatch.Pattern.Match(): +// +// pattern, err := pathmatch.Compile("/users/{user_id}/cards/{fruit_id}") +// if nil != err { +// switch err.(type) { +// +// case pathmatch.BadRequestComplainer: +// +// fmt.Printf("Something you did when you called pathmatch.Compile() caused an error. The error message was....\n%s\n", err.Error()) +// return +// +// case pathmatch.InternalErrorComplainer: +// +// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pathmatch.Compile() was running. The error message was....\n%s\n", err.Error()) +// return +// +// default: +// +// fmt.Printf("Some kind of unexpected error happend: %v", err) +// return +// } +// } +// +// var userId string +// var cardId string +// +// didMatch, err := pattern.Match("/users/8sN.oP/cards/X3j_T4", userId, cardId) +// if nil != err { +// switch err.(type) { // ← Note that we are using a Go type-switch here. +// +// case pathmatch.BadRequestComplainer: // ← Here we are detecting if the error returned is a pathmatch.BadRequestComplainer. +// +// fmt.Printf("Something you did when you called pattern.Match() caused an error. The error message was....\n%s\n", err.Error()) +// return +// +// case pathmatch.InternalErrorComplainer: +// +// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pattern.Match() was running. The error message was....\n%s\n", err.Error()) +// return +// +// default: +// +// fmt.Printf("Some kind of unexpected error happend: %v", err) +// return +// } +// } +// +// Note that one can get more specific than just a BadRequestComplainer. For example: +// NotEnoughArgumentsComplainer, PatternSyntaxErrorComplainer, UnsupportedArgumentTypeComplainer, +// and StructFieldWrongTypeComplainer. +// +// To be able to detect those more specific error types, put them BEFORE the "case pathmatch.BadRequestComplainer:" +// in the type switch. For example: +// +// pattern, err := pathmatch.Compile("/users/{user_id}/cards/{fruit_id}") +// if nil != err { +// switch err.(type) { +// +// case pathmatch.PatternSyntaxErrorComplainer: // ← Here we are detecting if the error returned was due to a syntax error, in the uncompiled pattern. Also note that it comes BEFORE the 'pathmatch.BadRequestComplainer' case; THAT IS IMPORTANT! +// +// fmt.Printf("The uncompiled pattern passed to pathmatch.Compile() had a syntax error in it. The error message describing the syntax error is....\n%s\n", err.Error()) +// return +// +// case pathmatch.BadRequestComplainer: +// +// fmt.Printf("Something you did when you called pathmatch.Compile() caused an error. The error message was....\n%s\n", err.Error()) +// return +// +// case pathmatch.InternalErrorComplainer: +// +// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pathmatch.Compile() was running. The error message was....\n%s\n", err.Error()) +// return +// +// default: +// +// fmt.Printf("Some kind of unexpected error happend: %v", err) +// return +// } +// } +type BadRequestComplainer interface { + error + BadRequestComplainer() +} diff --git a/compile.go b/compile.go new file mode 100644 index 0000000..ba620c5 --- /dev/null +++ b/compile.go @@ -0,0 +1,114 @@ +package pathmatch + + +import ( + "strings" +) + + +const ( + defaultFieldTagName = "match" + wildcardBit = "{}" +) + + +var ( + errMissingEndingRightBraceToMatchBeginningLeftBrace = newPatternSyntaxErrorComplainer(`Missing ending "}" (to match beginning "{").`) + errSlashInsideOfBraces = newPatternSyntaxErrorComplainer(`"/" inside of "{...}".`) + errLeftBraceInsideOfBraces = newPatternSyntaxErrorComplainer(`"{" inside of "{...}".`) +) + + +// Compile takes an uncompiled pattern, in the form of a Go string (ex: "/users/{userId}/vehicles/{vehicleId}"), +// and returns a compiled pattern. +// +// The compiled pattern can the be used to test if a path matches the pattern it contains. +// +// If the uncompiled pattern has a syntax error, Compile returns an error. +// +// Example Usage: +// +// pattern, err := pathmath.Compile("/users/{user_id}") +// if nil != err { +// fmt.Printf("ERROR Compiling: %v\n", err) +// return +// } +func Compile(uncompiledPattern string) (Pattern, error) { + + pattern := newPattern(defaultFieldTagName) + + s := uncompiledPattern + for { + index := strings.IndexRune(s, '{') + if -1 == index { + pattern.bits = append(pattern.bits, s) + break + } + bit := s[:index] + if "" != bit { // This is to deal with the case where a {???} is right at the beginning of the uncompiledPattern. + pattern.bits = append(pattern.bits, bit) + } + s = s[1+index:] + if "" == s { + break + } + + index = strings.IndexRune(s, '}') + if -1 == index { + return nil, errMissingEndingRightBraceToMatchBeginningLeftBrace + } + + // There should not be a slash ("/") before the ending brace ("}"). + // If there is, it is a syntax error. + slashIndex := strings.IndexRune(s, '/') + if -1 != slashIndex && slashIndex <= index { + return nil, errSlashInsideOfBraces + } + + // There should not be another beginning brace ("{") before the ending brace ("}"). + // If there is, it is a syntax error. + anotherLeftBraceIndex := strings.IndexRune(s, '{') + if -1 != anotherLeftBraceIndex && anotherLeftBraceIndex <= index { + return nil, errLeftBraceInsideOfBraces + } + + + bit = s[:index] + + + // Match names should be unique, within a pattern. + if _, ok := pattern.namesSet[bit]; ok { + return nil, newPatternSyntaxErrorComplainer("Duplicate match name: %q.", bit) + } + + + pattern.names = append(pattern.names, bit) + pattern.namesSet[bit] = struct{}{} + pattern.bits = append(pattern.bits, wildcardBit) + s = s[1+index:] + if "" == s { + break + } + } + + + return pattern, nil +} + + +// MustCompile is like Compile except that it never returns an error; but +// instead panic()s if there was an error. +// +// Example Usage: +// +// pattern := pathmath.MustCompile("/users/{user_id}") +// +// Note that if one recover()s from the panic(), one can use a Go type-switch +// to figure out what kind of error it is. +func MustCompile(uncompiledPattern string) Pattern { + if pattern, err := Compile(uncompiledPattern); nil != err { + panic(err) + } else { + return pattern + } +} diff --git a/compile_test.go b/compile_test.go new file mode 100644 index 0000000..571332b --- /dev/null +++ b/compile_test.go @@ -0,0 +1,560 @@ +package pathmatch + + +import ( + "testing" +) + + +func TestCompileAndMatchNames(t *testing.T) { + + tests := []struct{ + UncompiledPattern string + ExpectedNames []string + ExpectedNamesSet map[string]struct{} + ExpectedBits []string + }{ + { + UncompiledPattern: "", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "", + }, + }, + + + + { + UncompiledPattern: "/", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "/", + }, + }, + + + + { + UncompiledPattern: "//", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "//", + }, + }, + + + + { + UncompiledPattern: "///", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "///", + }, + }, + + + + { + UncompiledPattern: "/{this}", + ExpectedNames:[]string{"this"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + }, + }, + { + UncompiledPattern: "/{this}/", + ExpectedNames:[]string{"this"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + "/", + }, + }, + { + UncompiledPattern: "/{this}/{that}", + ExpectedNames:[]string{"this","that"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + "/", + wildcardBit, + }, + }, + { + UncompiledPattern: "/{this}/{that}/", + ExpectedNames:[]string{"this","that"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + "/", + wildcardBit, + "/", + }, + }, + { + UncompiledPattern: "/{this}/{that}/{these}", + ExpectedNames:[]string{"this","that","these"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + }, + }, + { + UncompiledPattern: "/{this}/{that}/{these}/", + ExpectedNames:[]string{"this","that","these"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + }, + }, + { + UncompiledPattern: "/{this}/{that}/{these}/{those}", + ExpectedNames:[]string{"this","that","these","those"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + "those":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + }, + }, + { + UncompiledPattern: "/{this}/{that}/{these}/{those}/", + ExpectedNames:[]string{"this","that","these","those"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + "those":struct{}{}, + }, + ExpectedBits: []string{ + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + }, + }, + + + + { + UncompiledPattern: "{this}", + ExpectedNames:[]string{"this"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + }, + }, + { + UncompiledPattern: "{this}/", + ExpectedNames:[]string{"this"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + "/", + }, + }, + { + UncompiledPattern: "{this}/{that}", + ExpectedNames:[]string{"this","that"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + "/", + wildcardBit, + }, + }, + { + UncompiledPattern: "{this}/{that}/", + ExpectedNames:[]string{"this","that"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + "/", + wildcardBit, + "/", + }, + }, + { + UncompiledPattern: "{this}/{that}/{these}", + ExpectedNames:[]string{"this","that","these"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + }, + }, + { + UncompiledPattern: "{this}/{that}/{these}/", + ExpectedNames:[]string{"this","that","these"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + }, + }, + { + UncompiledPattern: "{this}/{that}/{these}/{those}", + ExpectedNames:[]string{"this","that","these","those"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + "those":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + }, + }, + { + UncompiledPattern: "{this}/{that}/{these}/{those}/", + ExpectedNames:[]string{"this","that","these","those"}, + ExpectedNamesSet:map[string]struct{}{ + "this":struct{}{}, + "that":struct{}{}, + "these":struct{}{}, + "those":struct{}{}, + }, + ExpectedBits: []string{ + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + wildcardBit, + "/", + }, + }, + + + + { + UncompiledPattern: "/apple/banana/cherry", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "/apple/banana/cherry", + }, + }, + { + UncompiledPattern: "/apple/banana/cherry/", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "/apple/banana/cherry/", + }, + }, + { + UncompiledPattern: "//apple/banana/cherry", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "//apple/banana/cherry", + }, + }, + { + UncompiledPattern: "/apple//banana/cherry", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "/apple//banana/cherry", + }, + }, + { + UncompiledPattern: "/apple/banana/cherry//", + ExpectedNames:[]string{}, + ExpectedNamesSet:map[string]struct{}{}, + ExpectedBits: []string{ + "/apple/banana/cherry//", + }, + }, + + + + { + UncompiledPattern: "/apple/{banana}", + ExpectedNames:[]string{ "banana"}, + ExpectedNamesSet:map[string]struct{}{ + "banana":struct{}{}, + }, + ExpectedBits: []string{ + "/apple/", + wildcardBit, + }, + }, + { + UncompiledPattern: "/apple/{banana}/", + ExpectedNames:[]string{ "banana"}, + ExpectedNamesSet:map[string]struct{}{ + "banana":struct{}{}, + }, + ExpectedBits: []string{ + "/apple/", + wildcardBit, + "/", + }, + }, + { + UncompiledPattern: "/apple/{banana}//", + ExpectedNames:[]string{ "banana"}, + ExpectedNamesSet:map[string]struct{}{ + "banana":struct{}{}, + }, + ExpectedBits: []string{ + "/apple/", + wildcardBit, + "//", + }, + }, + + + + { + UncompiledPattern: "/apple/{banana}/cherry", + ExpectedNames:[]string{ "banana"}, + ExpectedNamesSet:map[string]struct{}{ + "banana":struct{}{}, + }, + ExpectedBits: []string{ + "/apple/", + wildcardBit, + "/cherry", + }, + }, + { + UncompiledPattern: "/apple/{banana}/cherry/", + ExpectedNames:[]string{ "banana"}, + ExpectedNamesSet:map[string]struct{}{ + "banana":struct{}{}, + }, + ExpectedBits: []string{ + "/apple/", + wildcardBit, + "/cherry/", + }, + }, + + + + { + UncompiledPattern: "/apple/{banana}/cherry/{grape}", + ExpectedNames:[]string{ "banana", "grape"}, + ExpectedNamesSet:map[string]struct{}{ + "banana":struct{}{}, + "grape":struct{}{}, + }, + ExpectedBits: []string{ + "/apple/", + wildcardBit, + "/cherry/", + wildcardBit, + }, + }, + { + UncompiledPattern: "/apple/{banana}/cherry/{grape}/", + ExpectedNames:[]string{ "banana", "grape"}, + ExpectedNamesSet:map[string]struct{}{ + "banana":struct{}{}, + "grape":struct{}{}, + }, + ExpectedBits: []string{ + "/apple/", + wildcardBit, + "/cherry/", + wildcardBit, + "/", + }, + }, + } + + + for testNumber, test := range tests { + + actualPattern, err := Compile(test.UncompiledPattern) + if nil != err { + t.Errorf("For test #%d, did not expect to receive an error, but actually got one: %v\nPATTERN: %q", testNumber, err, test.UncompiledPattern) + continue + } + + if expected, actual := test.ExpectedBits, actualPattern.(*internalPattern).bits; len(expected) != len(actual) { + t.Errorf("For test #%d, expected compiled pattern to have %d bits, but actually had %d.\nEXPECTED BITS: %#v\nACTUAL BITS: %#v\nPATTERN: %q", testNumber, len(expected), len(actual), expected, actual, test.UncompiledPattern) + continue + } else { + for bitNumber, expectedBit := range expected { + + actualBit := actual[bitNumber] + + if expectedBit != actualBit { + t.Errorf("For test #%d and bit #%d, expected %q, but actually got %q.\nEXPECTED BITS: %#v\nACTUAL BITS: %#v\nPATTERN: %q", testNumber, bitNumber, expectedBit, actualBit, expected, actual, test.UncompiledPattern) + continue + } + } + } + + if expected, actual := test.ExpectedNames, actualPattern.MatchNames(); len(expected) != len(actual) { + t.Errorf("For test #%d, when checking directly, expected compiled pattern to have %d names, but actually had %d.\nEXPECTED NAMES: %#v\nACTUAL NAMES: %#v\nPATTERN: %q", testNumber, len(expected), len(actual), expected, actual, test.UncompiledPattern) + continue + } else { + for nameNumber, expectedName := range expected { + + actualName := actual[nameNumber] + + if expectedName != actualName { + t.Errorf("For test #%d and name #%d, expected %q, but actually got %q.\nEXPECTED NAMES: %#v\nACTUAL NAMES: %#v\nPATTERN: %q", testNumber, nameNumber, expectedName, actualName, expected, actual, test.UncompiledPattern) + continue + } + } + } + + if expected, actual := test.ExpectedNamesSet, actualPattern.(*internalPattern).namesSet; len(expected) != len(actual) { + t.Errorf("For test #%d, when checking directly, expected compiled pattern to have %d names in set, but actually had %d.\nEXPECTED NAMES SET: %#v\nACTUAL NAMES SET: %#v\nPATTERN: %q", testNumber, len(expected), len(actual), expected, actual, test.UncompiledPattern) + continue + } else { + for expectedName, _ := range expected { + + _, ok := actual[expectedName] + + if !ok { + t.Errorf("For test #%d, expected name %q to exist, but actually did't.\nEXPECTED NAMES SET: %#v\nACTUAL NAMES SET: %#v\nPATTERN: %q", testNumber, expectedName, expected, actual, test.UncompiledPattern) + continue + } + } + } + + if expected, actual := len(test.ExpectedNames), len(actualPattern.MatchNames()); expected != actual { + t.Errorf("For test #%d, when checking using .MatchNames(), expected compiled pattern to have %d names, but actually had %d.\nEXPECTED NAMES: %#v\nACTUAL NAMES: %#v\nPATTERN: %q", testNumber, expected, actual, test.ExpectedNames, actualPattern.MatchNames(), test.UncompiledPattern) + continue + } + } +} + + +func TestCompileFail(t *testing.T) { + + tests := []struct{ + UncompiledPattern string + ExpectedError string + }{ + { + UncompiledPattern: "/users/{userId", + ExpectedError: `Bad Request: Syntax Error: Missing ending "}" (to match beginning "{").`, + }, + { + UncompiledPattern: "/users/{userId}/vehicles/{vehicleId", + ExpectedError: `Bad Request: Syntax Error: Missing ending "}" (to match beginning "{").`, + }, + { + UncompiledPattern: "/users/{userId/vehicles/{vehicleId}", + ExpectedError: `Bad Request: Syntax Error: "/" inside of "{...}".`, + }, + { + UncompiledPattern: "/users/{userId{vehicleId}", + ExpectedError: `Bad Request: Syntax Error: "{" inside of "{...}".`, + }, + { + UncompiledPattern: "/apple/{fruitId}/banana/{fruitId}", + ExpectedError: `Bad Request: Syntax Error: Duplicate match name: "fruitId".`, + }, + { + UncompiledPattern: "/fruit/{apple/banana/cherry}", + ExpectedError: `Bad Request: Syntax Error: "/" inside of "{...}".`, + }, + } + + + for testNumber, test := range tests { + _, err := Compile(test.UncompiledPattern) + if nil == err { + t.Errorf("For test #%d, expected to receive an error, but actually did not get one: %v\nPATTERN: %q", testNumber, err, test.UncompiledPattern) + continue + } + if expected, actual := test.ExpectedError, err.Error(); expected != actual { + t.Errorf("For test #%d, expected error message to be %q, but actually was %q.\nPATTERN: %q", testNumber, expected, actual, test.UncompiledPattern) + continue + } + } +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..b82cca8 --- /dev/null +++ b/doc.go @@ -0,0 +1,62 @@ +/* +Package pathmatch provides pattern matching for paths. + +For example, a path could be a file system path, or a path could be a path from a URL (such as an HTTP or HTTPS based URL). + +The matches can be loaded into variables (when using pathmatch.Match()); +or can be loaded into a struct (when using pathmatch.Pattern.MatchAndLoad()). + +Example Usage: + + pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}") + if nil != err { + //@TODO + } + + var userId string + var vehicleId string + + didMatch, err := pattern.Match("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &userId, &vehicleId) + + if nil != err { + //@TODO + } + + if didMatch { + fmt.Println("The path matched!") + + fmt.Printf("user_id = %q \n", userId) // user_id = "bMM_kJFMEV" + fmt.Printf("vehicle_id = %q \n", vehicleId) // vehicle_id = "o_bcU.RZGK" + } else { + fmt.Println("The patch did not match.") + } + +Alternate Example Usage: + + pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}") + if nil != err { + //@TODO + } + + data := struct{ + UserId string `match:"user_id"` + VehicleId string `match:"vehicle_id"` + }{} + + didMatch, err := pattern.MatchAndLoad("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &data) + + if nil != err { + //@TODO + } + + if didMatch { + fmt.Println("The path matched!") + + fmt.Printf("user_id = %q \n", data.UserId) // user_id = "bMM_kJFMEV" + fmt.Printf("vehicle_id = %q \n", data.VehicleId) // vehicle_id = "o_bcU.RZGK" + } else { + fmt.Println("The patch did not match.") + } + +*/ +package pathmatch diff --git a/internal_error_complainer.go b/internal_error_complainer.go new file mode 100644 index 0000000..fdf91ea --- /dev/null +++ b/internal_error_complainer.go @@ -0,0 +1,57 @@ +package pathmatch + + +import ( + "bytes" + "fmt" +) + + +const ( + internalErrorMessagePrefix = "Internal Error: " +) + + +type InternalErrorComplainer interface { + error + InternalErrorComplainer() +} + + +// internalInternalErrorComplainer is the only underlying implementation that fits the +// InternalErrorComplainer interface, in this library. +type internalInternalErrorComplainer struct { + msg string +} + + +// newInternalErrorComplainer creates a new internalInternalErrorComplainer (struct) and +// returns it as a InternalErrorComplainer (interface). +func newInternalErrorComplainer(format string, a ...interface{}) InternalErrorComplainer { + msg := fmt.Sprintf(format, a...) + + err := internalInternalErrorComplainer{ + msg:msg, + } + + return &err +} + + +// Error method is necessary to satisfy the 'error' interface (and the InternalErrorComplainer +// interface). +func (err *internalInternalErrorComplainer) Error() string { + var buffer bytes.Buffer + + buffer.WriteString(internalErrorMessagePrefix) + buffer.WriteString(err.msg) + + return buffer.String() +} + + +// InternalErrorComplainer method is necessary to satisfy the 'InternalErrorComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalInternalErrorComplainer) InternalErrorComplainer() { + // Nothing here. +} diff --git a/not_enough_arguments_complainer.go b/not_enough_arguments_complainer.go new file mode 100644 index 0000000..d53b1af --- /dev/null +++ b/not_enough_arguments_complainer.go @@ -0,0 +1,69 @@ +package pathmatch + + +import ( + "fmt" +) + + +type NotEnoughArgumentsComplainer interface { + BadRequestComplainer + NotEnoughArgumentsComplainer() + + ExpectedAtLeast() int + Actual() int +} + + +// internalNotEnoughArgumentsComplainer is the only underlying implementation that fits the +// NotEnoughArgumentsComplainer interface, in this library. +type internalNotEnoughArgumentsComplainer struct { + expectedAtLeast int + actual int +} + + +// newNotEnoughArgumentsComplainer creates a new internalNotEnoughArgumentsComplainer (struct) and +// returns it as a NotEnoughArgumentsComplainer (interface). +func newNotEnoughArgumentsComplainer(expectedAtLeast int, actual int) NotEnoughArgumentsComplainer { + err := internalNotEnoughArgumentsComplainer{ + expectedAtLeast:expectedAtLeast, + actual:actual, + } + + return &err +} + + +// Error method is necessary to satisfy the 'error' interface (and the +// NotEnoughArgumentsComplainer interface). +func (err *internalNotEnoughArgumentsComplainer) Error() string { + plural := "" + if 1 < err.expectedAtLeast { + plural = "s" + } + return fmt.Sprintf("Bad Request: Not enough arguments. Expected at least %d argument%s, but actually got %d.", err.expectedAtLeast, plural, err.actual) +} + + +// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalNotEnoughArgumentsComplainer) BadRequestComplainer() { + // Nothing here. +} + + +// NotEnoughArgumentsComplainer method is necessary to satisfy the 'NotEnoughArgumentsComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalNotEnoughArgumentsComplainer) NotEnoughArgumentsComplainer() { + // Nothing here. +} + + +func (err *internalNotEnoughArgumentsComplainer) ExpectedAtLeast() int { + return err.expectedAtLeast +} + +func (err *internalNotEnoughArgumentsComplainer) Actual() int { + return err.actual +} diff --git a/pattern.go b/pattern.go new file mode 100644 index 0000000..55dca51 --- /dev/null +++ b/pattern.go @@ -0,0 +1,65 @@ +package pathmatch + + +// Pattern represents a compiled pattern. It is what is returned +// from calling either the Compile to MustCompile funcs. +// +// Pattern provides the Match, MatchAndLoad, and MatchNames methods. +// +// Example Usage: +// +// pattern, err := pathmath.Compile("/users/{user_id}") +// if nil != err { +// fmt.Printf("ERROR Compiling: %v\n", err) +// return +// } +// +// var userId string +// +// didMatch, err := pattern.Match("/users/123", userId) +// if nil != err { +// fmt.Printf("ERROR Matching: %v\n", err) +// return +// } +// +// if didMatch { +// fmt.Printf("user_id = %q\n", userId) +// } else { +// fmt.Println("Did not match.") +// } +type Pattern interface { + Match(string, ...interface{}) (bool, error) + MatchAndLoad(string, interface{}) (bool, error) + MatchNames() []string +} + + +type internalPattern struct { + bits []string + names []string + namesSet map[string]struct{} + fieldTagName string +} + + +func (pattern *internalPattern) MatchNames() []string { + + return pattern.names +} + + +func newPattern(fieldTagName string) *internalPattern { + bits := []string{} + names := []string{} + namesSet := map[string]struct{}{} + + pattern := internalPattern{ + bits:bits, + names:names, + namesSet:namesSet, + fieldTagName:fieldTagName, + } + + return &pattern + +} diff --git a/pattern_load.go b/pattern_load.go new file mode 100644 index 0000000..8201f7d --- /dev/null +++ b/pattern_load.go @@ -0,0 +1,99 @@ +package pathmatch + + +import ( + "errors" + "fmt" + "reflect" + "strings" +) + + +var ( + errExpectedAPointerToAStruct = newUnsupportedArgumentTypeComplainer("Expected a pointer to a struct, but wasn't.") +) + + +func (pattern *internalPattern) MatchAndLoad(path string, strct interface{}) (bool, error) { + +//@TODO: Is it a good idea to be dynamically creating this? +//@TODO: Also, can the struct fields be put in here directly instead? + args := []interface{}{} + numNames := len(pattern.MatchNames()) + for i:=0; i argsIndex { + return newInternalErrorComplainer("Index value %d is less than zero.", argsIndex) + } + + if lenArgs := len(args); argsIndex >= lenArgs { + expectedAtLeast := 1+argsIndex + actual := lenArgs + + return newNotEnoughArgumentsComplainer(expectedAtLeast, actual) + } + + arg := args[argsIndex] + + switch a := arg.(type) { + case sql.Scanner: + if err := a.Scan(value); nil != err { + return newScanErrorComplainer(err, argsIndex, fmt.Sprintf("%T", arg)) + } + case *string: + *a = value + default: + return newUnsupportedIndexedArgumentTypeComplainer(argsIndex, fmt.Sprintf("%T", arg)) + } + + return nil +} diff --git a/set_test.go b/set_test.go new file mode 100644 index 0000000..422eecc --- /dev/null +++ b/set_test.go @@ -0,0 +1,376 @@ +package pathmatch + + +import ( + "math/rand" + "time" + + "testing" +) + + +func TestSetStringPointers(t *testing.T) { + + randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() )) + runes := []rune(".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz") + lenRunes := len(runes) + + randomString := func() string { + length := 1 + randomness.Intn(59) + rs := make([]rune, length) + for i := range rs { + rs[i] = runes[randomness.Intn(lenRunes)] + } + + return string(rs) + } + + + tests := []struct{ + Value string + ArgsIndex int + Args []interface{} + }{ + { + Value: randomString(), + ArgsIndex: 0, + Args: []interface{}{ new(string), }, + }, + + + + { + Value: randomString(), + ArgsIndex: 0, + Args: []interface{}{ new(string), new(string), }, + }, + { + Value: randomString(), + ArgsIndex: 1, + Args: []interface{}{ new(string), new(string), }, + }, + + + + { + Value: randomString(), + ArgsIndex: 0, + Args: []interface{}{ new(string), new(string), new(string), }, + }, + { + Value: randomString(), + ArgsIndex: 1, + Args: []interface{}{ new(string), new(string), new(string), }, + }, + { + Value: randomString(), + ArgsIndex: 2, + Args: []interface{}{ new(string), new(string), new(string), }, + }, + } + + + for testNumber, test := range tests { + + for argNumber, arg := range test.Args { + if argStringPtr, ok := arg.(*string); !ok { + t.Errorf("For test #%d, expected test.Args[%d] to be type *string (pointer to string), but actually was: %T", testNumber, argNumber, arg) + continue + } else if argString := *argStringPtr; "" != argString { + t.Errorf("For test #%d, expected test.Args[%d] to be \"\" (empty string), but actually was: %q", testNumber, argNumber, argString) + continue + } + } + + if err := set(test.Value, test.ArgsIndex, test.Args...); nil != err { + t.Errorf("For test #%d, did not expected error when calling set(), but actually received one: %v", testNumber, err) + continue + } + + for argNumber, arg := range test.Args { + if argNumber == test.ArgsIndex { + continue + } + + if argStringPtr, ok := arg.(*string); !ok { + t.Errorf("For test #%d, expected test.Args[%d] to be type *string (pointer to string), but actually was: %T", testNumber, argNumber, arg) + continue + } else if argString := *argStringPtr; "" != argString { + t.Errorf("For test #%d, expected test.Args[%d] to be \"\" (empty string), but actually was: %q", testNumber, argNumber, argString) + continue + } + } + + if actualPtr, ok := test.Args[test.ArgsIndex].(*string); !ok { + t.Errorf("For test #%d, expected test.Args[%d] to be type *string (pointer to string), but actually was: %T", testNumber, test.ArgsIndex, test.Args[test.ArgsIndex]) + continue + } else if expected, actual := test.Value, *actualPtr; expected != actual { + t.Errorf("For test #%d, expected test.Args[%d] to be type %q, but actually was %q.", testNumber, test.ArgsIndex, expected, actual) + continue + } + } +} + + +func TestSetFail(t *testing.T) { + + const internalError = "internal_error" + const notEnoughArguments = "not_enough_arguments" + const unsupportedArgumentType = "unsupported_argument_type" + + + randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() )) + runes := []rune(".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz") + lenRunes := len(runes) + + randomString := func() string { + length := 1 + randomness.Intn(59) + rs := make([]rune, length) + for i := range rs { + rs[i] = runes[randomness.Intn(lenRunes)] + } + + return string(rs) + } + + + tests := []struct{ + Value string + ArgsIndex int + Args []interface{} + ExpectedFit string + ExpectedErrorString string + }{ + { + Value: randomString(), + ArgsIndex: -1, + Args: []interface{}{ new(string), }, + ExpectedFit: internalError, + ExpectedErrorString: "Internal Error: Index value -1 is less than zero.", + }, + { + Value: randomString(), + ArgsIndex: -1, + Args: []interface{}{ randomString(), }, + ExpectedFit: internalError, + ExpectedErrorString: "Internal Error: Index value -1 is less than zero.", + }, + + + + { + Value: randomString(), + ArgsIndex: -2, + Args: []interface{}{ new(string), }, + ExpectedFit: internalError, + ExpectedErrorString: "Internal Error: Index value -2 is less than zero.", + }, + { + Value: randomString(), + ArgsIndex: -2, + Args: []interface{}{ randomString(), }, + ExpectedFit: internalError, + ExpectedErrorString: "Internal Error: Index value -2 is less than zero.", + }, + + + + { + Value: randomString(), + ArgsIndex: -3, + Args: []interface{}{ new(string), }, + ExpectedFit: internalError, + ExpectedErrorString: "Internal Error: Index value -3 is less than zero.", + }, + { + Value: randomString(), + ArgsIndex: -3, + Args: []interface{}{ randomString(), }, + ExpectedFit: internalError, + ExpectedErrorString: "Internal Error: Index value -3 is less than zero.", + }, + + + + { + Value: randomString(), + ArgsIndex: 0, + Args: []interface{}{ randomString(), }, + ExpectedFit: unsupportedArgumentType, + ExpectedErrorString: "Bad Request: Type of argument #0 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", + }, + + + + { + Value: randomString(), + ArgsIndex: 0, + Args: []interface{}{ randomString(), randomString(), }, + ExpectedFit: unsupportedArgumentType, + ExpectedErrorString: "Bad Request: Type of argument #0 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", + }, + { + Value: randomString(), + ArgsIndex: 1, + Args: []interface{}{ randomString(), randomString(), }, + ExpectedFit: unsupportedArgumentType, + ExpectedErrorString: "Bad Request: Type of argument #1 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", + }, + + + + { + Value: randomString(), + ArgsIndex: 0, + Args: []interface{}{ randomString(), randomString(), randomString(), }, + ExpectedFit: unsupportedArgumentType, + ExpectedErrorString: "Bad Request: Type of argument #0 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", + }, + { + Value: randomString(), + ArgsIndex: 1, + Args: []interface{}{ randomString(), randomString(), randomString(), }, + ExpectedFit: unsupportedArgumentType, + ExpectedErrorString: "Bad Request: Type of argument #1 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", + }, + { + Value: randomString(), + ArgsIndex: 2, + Args: []interface{}{ randomString(), randomString(), randomString(), }, + ExpectedFit: unsupportedArgumentType, + ExpectedErrorString: "Bad Request: Type of argument #2 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", + }, + + + + { + Value: randomString(), + ArgsIndex: 0, + Args: []interface{}{ }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 1 argument, but actually got 0.", + }, + { + Value: randomString(), + ArgsIndex: 1, + Args: []interface{}{ }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 2 arguments, but actually got 0.", + }, + { + Value: randomString(), + ArgsIndex: 2, + Args: []interface{}{ }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 3 arguments, but actually got 0.", + }, + + + + { + Value: randomString(), + ArgsIndex: 1, + Args: []interface{}{ randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 2 arguments, but actually got 1.", + }, + { + Value: randomString(), + ArgsIndex: 2, + Args: []interface{}{ randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 3 arguments, but actually got 1.", + }, + { + Value: randomString(), + ArgsIndex: 3, + Args: []interface{}{ randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 4 arguments, but actually got 1.", + }, + + + + { + Value: randomString(), + ArgsIndex: 2, + Args: []interface{}{ randomString(), randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 3 arguments, but actually got 2.", + }, + { + Value: randomString(), + ArgsIndex: 3, + Args: []interface{}{ randomString(), randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 4 arguments, but actually got 2.", + }, + { + Value: randomString(), + ArgsIndex: 4, + Args: []interface{}{ randomString(), randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 5 arguments, but actually got 2.", + }, + + + + { + Value: randomString(), + ArgsIndex: 3, + Args: []interface{}{ randomString(), randomString(), randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 4 arguments, but actually got 3.", + }, + { + Value: randomString(), + ArgsIndex: 4, + Args: []interface{}{ randomString(), randomString(), randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 5 arguments, but actually got 3.", + }, + { + Value: randomString(), + ArgsIndex: 5, + Args: []interface{}{ randomString(), randomString(), randomString(), }, + ExpectedFit: notEnoughArguments, + ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 6 arguments, but actually got 3.", + }, + } + + + for testNumber, test := range tests { + + if err := set(test.Value, test.ArgsIndex, test.Args...); nil == err { + t.Errorf("For test #%d, expected error when calling set(), but did not actually received one: %v", testNumber, err) + continue + } else { + switch err.(type) { + case InternalErrorComplainer: + if expected, actual := internalError, test.ExpectedFit; expected != actual { + t.Errorf("For test #%d, did indeed expect an error, but did not expect it to fit the \"InternalErrorComplainer\" interface, but actually did: %T.", testNumber, err) + continue + } + case NotEnoughArgumentsComplainer: + if expected, actual := notEnoughArguments, test.ExpectedFit; expected != actual { + t.Errorf("For test #%d, did indeed expect an error, but did not expect it to fit the \"NotEnoughArgumentsComplainer\" interface, but actually did: %T.", testNumber, err) + continue + } + case UnsupportedArgumentTypeComplainer: + if expected, actual := unsupportedArgumentType, test.ExpectedFit; expected != actual { + t.Errorf("For test #%d, did indeed expect an error, but did not expect it to fit the \"UnsupportedArgumentTypeComplainer\" interface, but actually did: %T.", testNumber, err) + continue + } + default: + t.Errorf("For test #%d, did indeed expect an error, but did not expect this error: [%v] (%T).", testNumber, err, err) + continue + } + + if expected, actual := test.ExpectedErrorString, err.Error(); expected != actual { + t.Errorf("For test #%d, expected error string to be %q, but actually got %q.", testNumber, expected, actual) + continue + } + } + + } +} diff --git a/struct_field_wrong_type_complainer.go b/struct_field_wrong_type_complainer.go new file mode 100644 index 0000000..0381b23 --- /dev/null +++ b/struct_field_wrong_type_complainer.go @@ -0,0 +1,57 @@ +package pathmatch + + +import ( + "bytes" + "fmt" +) + + +const structFieldWrongTypeMessagePrefix = "Bad Request: Wrong type for match " + + +type StructFieldWrongTypeComplainer interface { + BadRequestComplainer + MatchName() string +} + +// internalStructFieldWrongTypeComplainer is the only underlying implementation that fits the +// StructFieldWrongTypeComplainer interface, in this library. +type internalStructFieldWrongTypeComplainer struct { + matchName string +} + +// newStructFieldWrongTypeComplainer creates a new internalStructFieldWrongTypeComplainer (struct) and +// returns it as a StructFieldWrongTypeComplainer (interface). +func newStructFieldWrongTypeComplainer(matchName string) StructFieldWrongTypeComplainer { + err := internalStructFieldWrongTypeComplainer{ + matchName:matchName, + } + + return &err +} + + +// Error method is necessary to satisfy the 'error' interface (and the StructFieldWrongTypeComplainer +// interface). +func (err *internalStructFieldWrongTypeComplainer) Error() string { + var buffer bytes.Buffer + + buffer.WriteString(structFieldWrongTypeMessagePrefix) + buffer.WriteString(fmt.Sprintf("%q", err.matchName)) + + return buffer.String() +} + + +// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalStructFieldWrongTypeComplainer) BadRequestComplainer() { + // Nothing here. +} + + +// DependencyName method is necessary to satisfy the 'StructFieldWrongTypeComplainer' interface. +func (err *internalStructFieldWrongTypeComplainer) MatchName() string { + return err.matchName +} diff --git a/unsupported_argument_type_complainer.go b/unsupported_argument_type_complainer.go new file mode 100644 index 0000000..ac46de4 --- /dev/null +++ b/unsupported_argument_type_complainer.go @@ -0,0 +1,107 @@ +package pathmatch + + +import ( + "fmt" +) + + +type UnsupportedArgumentTypeComplainer interface { + BadRequestComplainer + UnsupportedArgumentTypeComplainer() +} + + + + + + +// internalUnsupportedIndexedArgumentTypeComplainer is the only underlying implementation that fits the +// UnsupportedArgumentTypeComplainer interface, in this library. +type internalUnsupportedIndexedArgumentTypeComplainer struct { + argumentIndex int + argumentType string +} + + +// newUnsupportedIndexedArgumentTypeComplainer creates a new internalUnsupportedIndexedArgumentTypeComplainer (struct) and +// returns it as a UnsupportedArgumentTypeComplainer (interface). +func newUnsupportedIndexedArgumentTypeComplainer(argumentIndex int, argumentType string) UnsupportedArgumentTypeComplainer { + err := internalUnsupportedIndexedArgumentTypeComplainer{ + argumentIndex:argumentIndex, + argumentType:argumentType, + } + + return &err +} + + +// Error method is necessary to satisfy the 'error' interface (and the +// UnsupportedArgumentTypeComplainer interface). +func (err *internalUnsupportedIndexedArgumentTypeComplainer) Error() string { + s := fmt.Sprintf("Bad Request: Type of argument #%d (%s) is unsupported.", err.argumentIndex, err.argumentType) + if "string" == err.argumentType { + s = fmt.Sprintf("%s However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", s) + } + return s +} + + +// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalUnsupportedIndexedArgumentTypeComplainer) BadRequestComplainer() { + // Nothing here. +} + + +// UnsupportedArgumentTypeComplainer method is necessary to satisfy the 'UnsupportedArgumentTypeComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalUnsupportedIndexedArgumentTypeComplainer) UnsupportedArgumentTypeComplainer() { + // Nothing here. +} + + + + + + +// internalUnsupportedArgumentTypeComplainer is the only underlying implementation that fits the +// UnsupportedArgumentTypeComplainer interface, in this library. +type internalUnsupportedArgumentTypeComplainer struct { + msg string +} + + +// newUnsupportedArgumentTypeComplainer creates a new internalUnsupportedArgumentTypeComplainer (struct) and +// returns it as a UnsupportedArgumentTypeComplainer (interface). +func newUnsupportedArgumentTypeComplainer(format string, a ...interface{}) UnsupportedArgumentTypeComplainer { + msg := fmt.Sprintf(format, a...) + + err := internalUnsupportedArgumentTypeComplainer{ + msg:msg, + } + + return &err +} + + +// Error method is necessary to satisfy the 'error' interface (and the +// UnsupportedArgumentTypeComplainer interface). +func (err *internalUnsupportedArgumentTypeComplainer) Error() string { + + return fmt.Sprintf("Bad Request: Unsupported Argument Type: %s", err.msg) +} + + +// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalUnsupportedArgumentTypeComplainer) BadRequestComplainer() { + // Nothing here. +} + + +// UnsupportedArgumentTypeComplainer method is necessary to satisfy the 'UnsupportedArgumentTypeComplainer' interface. +// It exists to make this error type detectable in a Go type-switch. +func (err *internalUnsupportedArgumentTypeComplainer) UnsupportedArgumentTypeComplainer() { + // Nothing here. +}