Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regex path variables #167

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ Mock definition:
"host": "example.com",
"method": "GET|POST|PUT|PATCH|...",
"path": "/your/path/:variable",
"pathVariables": {
"variable": "\\d*"
},
"queryStringParameters": {
"name": ["value"],
"name": ["value", "value"]
Expand Down Expand Up @@ -201,6 +204,7 @@ A core feature of Mmock is the ability to return canned HTTP responses for reque
* *host*: Request http host. (without port)
* *method*: Request http method. It allows more than one separated by pipes "|" **Mandatory**
* *path*: Resource identifier. It allows :value matching. **Mandatory**
* *pathVariables*: A map of path variables to regexp that they need to match against. { "value": "\\d*" }
* *queryStringParameters*: Array of query strings. It allows more than one value for the same key.
* *headers*: Array of headers. It allows more than one value for the same key. **Case sensitive!**
* *cookies*: Array of cookies.
Expand Down Expand Up @@ -228,6 +232,7 @@ Query strings and headers support also global matches (*) in the header/paramete
Regexp matching is available for:
- body
- query strings
- path variables

See https://pkg.go.dev/regexp/syntax for regexp syntax

Expand Down Expand Up @@ -265,6 +270,10 @@ You can use variable data in response. The variables will be defined as tags lik
- URI
- description

**Enviroment variables:** You can access the server's environment variables using

* env."*VARIABLE_NAME*"

**Request data:** Use them if you want to add request data in your response.

- request.scheme
Expand Down Expand Up @@ -631,6 +640,7 @@ You can always disable this behavior adding the following flag `-server-statisti
- Improved logging with levels thanks to [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for Regular Expressions for QueryStringParameters [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for URI and Description tags [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for Regular Expressions for Path Variables [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)

### Contributing

Expand Down
9 changes: 8 additions & 1 deletion pkg/match/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (
ErrCookiesNotMatch = errors.New("Cookies not match")
ErrScenarioNotMatch = errors.New("Scenario state not match")
ErrPathNotMatch = errors.New("Path not match")
ErrPathVariablesNotMatch = errors.New("Path variables not match")
)

func NewTester(comparator *payload.Comparator, scenario ScenearioStorer) *Request {
Expand Down Expand Up @@ -189,9 +190,15 @@ func (mm Request) Match(req *mock.Request, mock *mock.Definition, scenarioAware
return false, fmt.Errorf("Fragment not match. Actual: %s, Expected: %s", req.Fragment, mock.Request.Fragment)
}

if !glob.Glob(mock.Request.Path, req.Path) && route.Match(req.Path) == nil {
var pathMatch = route.Match(req.Path)

if !glob.Glob(mock.Request.Path, req.Path) && pathMatch == nil {
return false, fmt.Errorf("%w Actual: %s, Expected: %s", ErrPathNotMatch, req.Path, mock.Request.Path)
}

if pathMatch != nil && mock.Request.PathVariables != nil && !route.MatchPathVariables(pathMatch, mock.Request.PathVariables) {
return false, fmt.Errorf("%w Actual: %s Expected: %v", ErrPathVariablesNotMatch, mock.Request.PathVariables, pathMatch.Params)
}

if !mm.mockIncludesMethod(req.Method, &mock.Request) {
return false, fmt.Errorf("Method not match. Actual: %s, Expected: %s", req.Method, mock.Request.Method)
Expand Down
33 changes: 33 additions & 0 deletions pkg/match/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,39 @@ func TestPathVars(t *testing.T) {
}
}

func TestPathVariables(t *testing.T) {
req := mock.Request{}
req.Path = "/a/b/c"

m := mock.Definition{}
m.Request.Path = "/a/:b/:c"
m.Request.PathVariables = map[string]string{
"b": "[a-z]",
"c": "[a-z]",
}

mm := Request{}

// match
if b, err := mm.Match(&req, &m, true); !b {
t.Error(err)
}

// not match
req.Path = "a/b/3"

if b, err := mm.Match(&req, &m, true); b {
t.Error(err)
}

// not present
req.Path = "a/b"

if b, err := mm.Match(&req, &m, true); b {
t.Error(err)
}
}

func TestPathGlob(t *testing.T) {
req := mock.Request{}
req.Path = "/a/b/c"
Expand Down
3 changes: 3 additions & 0 deletions pkg/mock/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type Values map[string][]string

type Cookies map[string]string

type PathValues map[string]string

type HttpHeaders struct {
Headers Values `json:"headers"`
Cookies Cookies `json:"cookies"`
Expand All @@ -22,6 +24,7 @@ type Request struct {
Port string `json:"port"`
Method string `json:"method"`
Path string `json:"path"`
PathVariables PathValues `json:"pathVariables"`
QueryStringParameters Values `json:"queryStringParameters"`
Fragment string `json:"fragment"`
HttpHeaders
Expand Down
25 changes: 25 additions & 0 deletions pkg/route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"fmt"
"regexp"
"strings"
"github.com/jmartin82/mmock/v3/internal/config/logger"
)

var log = logger.Log

type Route struct {
Keys []string
Regex *regexp.Regexp
Expand Down Expand Up @@ -40,6 +43,28 @@ func (route *Route) Match(url string) *Match {
return &Match{route.Params(url), route.Pattern}
}

func (route *Route) MatchPathVariables(match *Match, patterns map[string]string) bool{
for key, value := range match.Params {

pattern, exists := patterns[key]

if !exists {
log.Debugf("pathVariable %v isn't in request", key)
return false
}

log.Debugf("comparing pathVariable %v that has a value of %v against pattern %v", key, value, pattern)
matched, err := regexp.MatchString(pattern, value)

if !matched || err != nil {
log.Debugf("PathVariable %v with a value of %v either doesn't match %v or %v", key, value, pattern, err)
return false
}
}

return true
}

func NewRoute(pattern string) *Route {
regex, keys := pathToRegex(pattern)
return &Route{keys, regex, pattern}
Expand Down
Loading