@@ -19,12 +19,15 @@ import (
1919// Route holds information about the mock route configuration
2020type Route struct {
2121 // Method is the HTTP method to match
22- Method string `json:"method "`
22+ Method string `json:"Method "`
2323 // Path is the URL path to match
24- Path string `json:"path "`
24+ Path string `json:"Path "`
2525 // Handler is the dynamic handler function to use when called
26+ // Can only be set upon creation of the server
2627 Handler http.HandlerFunc `json:"-"`
27- // ResponseBody is the static JSON response to return when called
28+ // RawResponseBody is the static, raw string response to return when called
29+ RawResponseBody string `json:"raw_response_body"`
30+ // ResponseBody will be marshalled to JSON and returned when called
2831 ResponseBody any `json:"response_body"`
2932 // ResponseStatusCode is the HTTP status code to return when called
3033 ResponseStatusCode int `json:"response_status_code"`
@@ -52,30 +55,20 @@ type ServerOption func(*Server) error
5255func WithPort (port int ) ServerOption {
5356 return func (s * Server ) error {
5457 if port == 0 {
55- s .log .Debug ().Msg ("No port specified, using random port" )
58+ s .log .Debug ().Msg ("Configuring Parrot: No port specified, using random port" )
5659 } else if port < 0 || port > 65535 {
5760 return fmt .Errorf ("invalid port: %d" , port )
5861 }
5962 s .port = port
60- s .log .Debug ().Int ("port" , port ).Msg ("Setting port" )
63+ s .log .Debug ().Int ("port" , port ).Msg ("Configuring Parrot: Setting port" )
6164 return nil
6265 }
6366}
6467
65- // WithDebug sets the log level to debug
66- func WithDebug () ServerOption {
68+ func WithLogLevel (level zerolog.Level ) ServerOption {
6769 return func (s * Server ) error {
68- s .log = s .log .Level (zerolog .DebugLevel )
69- s .log .Debug ().Msg ("Setting log level to debug" )
70- return nil
71- }
72- }
73-
74- // WithTrace sets the log level to trace
75- func WithTrace () ServerOption {
76- return func (s * Server ) error {
77- s .log = s .log .Level (zerolog .TraceLevel )
78- s .log .Debug ().Msg ("Setting log level to trace" )
70+ s .log = s .log .Level (level )
71+ s .log .Debug ().Str ("log level" , level .String ()).Msg ("Configuring Parrot: Setting log level" )
7972 return nil
8073 }
8174}
@@ -84,15 +77,7 @@ func WithTrace() ServerOption {
8477func WithLogger (l zerolog.Logger ) ServerOption {
8578 return func (s * Server ) error {
8679 s .log = l
87- s .log .Debug ().Msg ("Setting custom logger" )
88- return nil
89- }
90- }
91-
92- // Silent sets the logger to a no-op logger
93- func Silent () ServerOption {
94- return func (s * Server ) error {
95- s .log = zerolog .Nop ()
80+ s .log .Debug ().Msg ("Configuring Parrot: Setting custom logger" )
9681 return nil
9782 }
9883}
@@ -101,7 +86,7 @@ func Silent() ServerOption {
10186func WithJSONLogs () ServerOption {
10287 return func (s * Server ) error {
10388 s .log = s .log .Output (zerolog.ConsoleWriter {Out : os .Stderr , TimeFormat : time .RFC3339Nano })
104- s .log .Debug ().Msg ("Setting log output to JSON" )
89+ s .log .Debug ().Msg ("Configuring Parrot: Setting log output to JSON" )
10590 return nil
10691 }
10792}
@@ -113,7 +98,7 @@ func WithSaveFile(saveFile string) ServerOption {
11398 return fmt .Errorf ("invalid save file name: %s" , saveFile )
11499 }
115100 s .saveFile = saveFile
116- s .log .Debug ().Str ("file" , saveFile ).Msg ("Setting save file" )
101+ s .log .Debug ().Str ("file" , saveFile ).Msg ("Configuring Parrot: Setting save file" )
117102 return nil
118103 }
119104}
@@ -125,7 +110,7 @@ func WithRoutes(routes []*Route) ServerOption {
125110 if err := s .Register (route ); err != nil {
126111 return fmt .Errorf ("failed to register route: %w" , err )
127112 }
128- s .log .Debug ().Str ("path " , route .Path ).Str ("method " , route .Method ).Msg ("Pre-registered route" )
113+ s .log .Debug ().Str ("Path " , route .Path ).Str ("Method " , route .Method ).Msg ("Configuring Parrot: Pre-registered route" )
129114 }
130115 return nil
131116 }
@@ -223,21 +208,35 @@ func (p *Server) Register(route *Route) error {
223208 if route .Method == "" {
224209 return fmt .Errorf ("invalid route method: %s" , route .Method )
225210 }
226- if route .Handler == nil && route .ResponseBody == nil {
211+ if route .Handler == nil && route .ResponseBody == nil && route . RawResponseBody == "" {
227212 return fmt .Errorf ("route must have a handler or response body" )
228213 }
214+ if route .Handler != nil && (route .ResponseBody != nil || route .RawResponseBody != "" ) {
215+ return fmt .Errorf ("route cannot have both a handler and response body" )
216+ }
217+ if route .ResponseBody != nil && route .RawResponseBody != "" {
218+ return fmt .Errorf ("route cannot have both a response body and raw response body" )
219+ }
229220 if route .ResponseBody != nil {
230221 if _ , err := json .Marshal (route .ResponseBody ); err != nil {
231- return fmt .Errorf ("failed to marshal response body : %w" , err )
222+ return fmt .Errorf ("response body is unable to be marshalled into JSON : %w" , err )
232223 }
233224 }
225+
234226 p .routesMu .Lock ()
235227 defer p .routesMu .Unlock ()
236228 p .routes [route .Method + ":" + route .Path ] = route
237229
238230 return nil
239231}
240232
233+ // Routes returns all registered routes
234+ func (p * Server ) Routes () map [string ]* Route {
235+ p .routesMu .RLock ()
236+ defer p .routesMu .RUnlock ()
237+ return p .routes
238+ }
239+
241240// Unregister removes a route from the parrot
242241func (p * Server ) Unregister (method , path string ) {
243242 p .routesMu .Lock ()
@@ -247,7 +246,7 @@ func (p *Server) Unregister(method, path string) {
247246
248247// Call makes a request to the parrot server
249248func (p * Server ) Call (method , path string ) (* http.Response , error ) {
250- req , err := http .NewRequest (method , filepath .Join (p .server . Addr , path ), nil )
249+ req , err := http .NewRequest (method , "http://" + filepath .Join (p .Address () , path ), nil )
251250 if err != nil {
252251 return nil , fmt .Errorf ("failed to create request: %w" , err )
253252 }
@@ -260,24 +259,29 @@ func (p *Server) Call(method, path string) (*http.Response, error) {
260259func (p * Server ) registerRouteHandler (w http.ResponseWriter , r * http.Request ) {
261260 if r .Method != http .MethodPost {
262261 http .Error (w , "Only POST allowed" , http .StatusMethodNotAllowed )
262+ p .log .Trace ().Str ("Method" , r .Method ).Msg ("Invalid method" )
263263 return
264264 }
265265
266- var route Route
266+ var route * Route
267267 if err := json .NewDecoder (r .Body ).Decode (& route ); err != nil {
268268 http .Error (w , "Invalid request body" , http .StatusBadRequest )
269269 return
270270 }
271271 defer r .Body .Close ()
272272
273273 if route .Method == "" || route .Path == "" {
274- http .Error (w , "Method and Path are required" , http .StatusBadRequest )
274+ err := errors .New ("Method and path are required" )
275+ http .Error (w , err .Error (), http .StatusBadRequest )
275276 return
276277 }
277278
278- p .routesMu .Lock ()
279- p .routes [route .Method + ":" + route .Path ] = & route
280- p .routesMu .Unlock ()
279+ err := p .Register (route )
280+ if err != nil {
281+ http .Error (w , err .Error (), http .StatusBadRequest )
282+ p .log .Trace ().Err (err ).Msg ("Failed to register route" )
283+ return
284+ }
281285
282286 w .WriteHeader (http .StatusCreated )
283287 p .log .Info ().Str ("Path" , route .Path ).Str ("Method" , route .Method ).Msg ("Route registered" )
@@ -291,13 +295,65 @@ func (p *Server) dynamicHandler(w http.ResponseWriter, r *http.Request) {
291295
292296 if ! exists {
293297 http .NotFound (w , r )
298+ p .log .Trace ().Str ("Remote Addr" , r .RemoteAddr ).Str ("Path" , r .URL .Path ).Str ("Method" , r .Method ).Msg ("Route not found" )
294299 return
295300 }
296301
297- w .Header ().Set ("Content-Type" , route .ResponseContentType )
302+ if route .ResponseContentType != "" {
303+ w .Header ().Set ("Content-Type" , route .ResponseContentType )
304+ }
298305 w .WriteHeader (route .ResponseStatusCode )
299- if err := json .NewEncoder (w ).Encode (route .ResponseBody ); err != nil {
300- http .Error (w , "Failed to marshal response into json" , http .StatusInternalServerError )
306+
307+ if route .Handler != nil {
308+ p .log .Trace ().Str ("Remote Addr" , r .RemoteAddr ).Str ("Path" , r .URL .Path ).Str ("Method" , r .Method ).Msg ("Calling route handler" )
309+ route .Handler (w , r )
310+ } else if route .RawResponseBody != "" {
311+ if route .ResponseContentType == "" {
312+ w .Header ().Set ("Content-Type" , "text/plain" )
313+ }
314+ if _ , err := w .Write ([]byte (route .RawResponseBody )); err != nil {
315+ p .log .Trace ().Err (err ).Str ("Remote Addr" , r .RemoteAddr ).Str ("Path" , r .URL .Path ).Str ("Method" , r .Method ).Msg ("Failed to write response" )
316+ http .Error (w , "Failed to write response" , http .StatusInternalServerError )
317+ return
318+ }
319+ p .log .Trace ().
320+ Str ("Remote Addr" , r .RemoteAddr ).
321+ Str ("Response" , route .RawResponseBody ).
322+ Str ("Path" , r .URL .Path ).
323+ Str ("Method" , r .Method ).
324+ Msg ("Returned raw response" )
325+ } else if route .ResponseBody != nil {
326+ if route .ResponseContentType == "" {
327+ w .Header ().Set ("Content-Type" , "application/json" )
328+ }
329+ rawJSON , err := json .Marshal (route .ResponseBody )
330+ if err != nil {
331+ p .log .Trace ().Err (err ).
332+ Str ("Remote Addr" , r .RemoteAddr ).
333+ Str ("Path" , r .URL .Path ).
334+ Str ("Method" , r .Method ).
335+ Msg ("Failed to marshal JSON response" )
336+ http .Error (w , "Failed to marshal response into json" , http .StatusInternalServerError )
337+ return
338+ }
339+ if _ , err = w .Write (rawJSON ); err != nil {
340+ p .log .Trace ().Err (err ).
341+ RawJSON ("Response" , rawJSON ).
342+ Str ("Remote Addr" , r .RemoteAddr ).
343+ Str ("Path" , r .URL .Path ).
344+ Str ("Method" , r .Method ).
345+ Msg ("Failed to write response" )
346+ http .Error (w , "Failed to write JSON response" , http .StatusInternalServerError )
347+ return
348+ }
349+ p .log .Trace ().
350+ Str ("Remote Addr" , r .RemoteAddr ).
351+ RawJSON ("Response" , rawJSON ).
352+ Str ("Path" , r .URL .Path ).
353+ Str ("Method" , r .Method ).
354+ Msg ("Returned JSON response" )
355+ } else {
356+ p .log .Trace ().Str ("Remote Addr" , r .RemoteAddr ).Str ("Path" , r .URL .Path ).Str ("Method" , r .Method ).Msg ("Route has no response" )
301357 }
302358}
303359
@@ -336,7 +392,7 @@ func (p *Server) save() error {
336392 p .log .Debug ().Msg ("No routes to save" )
337393 return nil
338394 }
339- p .log .Debug ().Str ("file" , p .saveFile ).Msg ("Saving routes" )
395+ p .log .Trace ().Str ("file" , p .saveFile ).Msg ("Saving routes" )
340396
341397 p .routesMu .RLock ()
342398 defer p .routesMu .RUnlock ()
@@ -350,6 +406,6 @@ func (p *Server) save() error {
350406 return fmt .Errorf ("failed to write routes to file: %w" , err )
351407 }
352408
353- p .log .Info ().Str ("file" , p .saveFile ).Msg ("Saved routes" )
409+ p .log .Trace ().Str ("file" , p .saveFile ).Msg ("Saved routes" )
354410 return nil
355411}
0 commit comments