@@ -28,6 +28,9 @@ const (
2828 HealthRoute = "/health"
2929 RoutesRoute = "/routes"
3030 RecorderRoute = "/recorder"
31+
32+ // MethodAny is a wildcard for any HTTP method
33+ MethodAny = "ANY"
3134)
3235
3336// Route holds information about the mock route configuration
@@ -36,9 +39,6 @@ type Route struct {
3639 Method string `json:"Method"`
3740 // Path is the URL path to match
3841 Path string `json:"Path"`
39- // Handler is the dynamic handler function to use when called
40- // Can only be set upon creation of the server
41- Handler http.HandlerFunc `json:"-"`
4242 // RawResponseBody is the static, raw string response to return when called
4343 RawResponseBody string `json:"raw_response_body"`
4444 // ResponseBody will be marshalled to JSON and returned when called
@@ -187,7 +187,7 @@ func (p *Server) run(listener net.Listener) {
187187 p .log .Error ().Err (err ).Msg ("Failed to save routes" )
188188 }
189189 if err := p .logFile .Close (); err != nil {
190- p . log . Error (). Err ( err ). Msg ( " Failed to close log file" )
190+ fmt . Println ( "ERROR: Failed to close log file:" , err )
191191 }
192192 p .shutDownOnce .Do (func () {
193193 close (p .shutDownChan )
@@ -197,7 +197,7 @@ func (p *Server) run(listener net.Listener) {
197197 p .log .Info ().Str ("Address" , p .address ).Msg ("Parrot awake and ready to squawk" )
198198 p .log .Debug ().Str ("Save File" , p .saveFileName ).Str ("Log File" , p .logFileName ).Msg ("Configuration" )
199199 if err := p .server .Serve (listener ); err != nil && ! errors .Is (err , http .ErrServerClosed ) {
200- p . log . Fatal (). Err ( err ). Msg ( "Error while running server" )
200+ fmt . Println ( "ERROR: Failed to start server:" , err )
201201 }
202202}
203203
@@ -209,11 +209,6 @@ func (p *Server) routeCallHandler(route *Route) http.HandlerFunc {
209209 return c .Str ("Route ID" , route .ID ())
210210 })
211211
212- if route .Handler != nil {
213- route .Handler (w , r )
214- return
215- }
216-
217212 if route .RawResponseBody != "" {
218213 w .Header ().Set ("Content-Type" , "text/plain" )
219214 w .WriteHeader (route .ResponseStatusCode )
@@ -317,15 +312,12 @@ func (p *Server) Register(route *Route) error {
317312 if ! isValidPath (route .Path ) {
318313 return newDynamicError (ErrInvalidPath , fmt .Sprintf ("'%s'" , route .Path ))
319314 }
320- if route .Method == "" {
321- return ErrNoMethod
315+ if ! isValidMethod ( route .Method ) {
316+ return newDynamicError ( ErrInvalidMethod , fmt . Sprintf ( "'%s'" , route . Method ))
322317 }
323- if route .Handler == nil && route . ResponseBody == nil && route .RawResponseBody == "" {
318+ if route .ResponseBody == nil && route .RawResponseBody == "" {
324319 return ErrNoResponse
325320 }
326- if route .Handler != nil && (route .ResponseBody != nil || route .RawResponseBody != "" ) {
327- return newDynamicError (ErrOnlyOneResponse , "handler and another response type provided" )
328- }
329321 if route .ResponseBody != nil && route .RawResponseBody != "" {
330322 return ErrOnlyOneResponse
331323 }
@@ -334,8 +326,19 @@ func (p *Server) Register(route *Route) error {
334326 return newDynamicError (ErrResponseMarshal , err .Error ())
335327 }
336328 }
329+ numWildcards := strings .Count (route .Path , "*" )
330+ if 1 < numWildcards {
331+ return newDynamicError (ErrWildcardPath , fmt .Sprintf ("more than 1 wildcard '%s'" , route .Path ))
332+ }
333+ if numWildcards == 1 && ! strings .HasSuffix (route .Path , "*" ) {
334+ return newDynamicError (ErrWildcardPath , fmt .Sprintf ("wildcard not at end '%s'" , route .Path ))
335+ }
337336
338- p .router .MethodFunc (route .Method , route .Path , routeRecordingMiddleware (p , p .routeCallHandler (route )))
337+ if route .Method == MethodAny {
338+ p .router .Handle (route .Path , routeRecordingMiddleware (p , p .routeCallHandler (route )))
339+ } else {
340+ p .router .MethodFunc (route .Method , route .Path , routeRecordingMiddleware (p , p .routeCallHandler (route )))
341+ }
339342
340343 p .routesMu .Lock ()
341344 defer p .routesMu .Unlock ()
@@ -496,6 +499,9 @@ func (p *Server) Call(method, path string) (*resty.Response, error) {
496499 if p .shutDown .Load () {
497500 return nil , ErrServerShutdown
498501 }
502+ if ! isValidMethod (method ) {
503+ return nil , newDynamicError (ErrInvalidMethod , fmt .Sprintf ("'%s'" , method ))
504+ }
499505 return p .client .R ().Execute (method , "http://" + filepath .Join (p .Address (), path ))
500506}
501507
@@ -660,6 +666,7 @@ func (p *Server) loggingMiddleware(next http.Handler) http.Handler {
660666
661667var pathRegex = regexp .MustCompile (`^\/[a-zA-Z0-9\-._~%!$&'()*+,;=:@\/]*$` )
662668
669+ // isValidPath checks if the path is a valid URL path
663670func isValidPath (path string ) bool {
664671 if path == "" || path == "/" {
665672 return false
@@ -681,3 +688,19 @@ func isValidPath(path string) bool {
681688 }
682689 return pathRegex .MatchString (path )
683690}
691+
692+ // isValidMethod checks if the method is a valid HTTP method, in loose terms
693+ func isValidMethod (method string ) bool {
694+ switch method {
695+ case
696+ http .MethodGet ,
697+ http .MethodPost ,
698+ http .MethodPut ,
699+ http .MethodPatch ,
700+ http .MethodDelete ,
701+ http .MethodOptions ,
702+ MethodAny :
703+ return true
704+ }
705+ return false
706+ }
0 commit comments