Skip to content

Commit 0d85116

Browse files
committed
Add Context.PathParamDefault(name string, defaultValue string) string method
Improve method comments here and there.
1 parent 7efeffb commit 0d85116

File tree

5 files changed

+344
-41
lines changed

5 files changed

+344
-41
lines changed

context.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ type Context interface {
5555
// PathParam returns path parameter by name.
5656
PathParam(name string) string
5757

58+
// PathParamDefault returns the path parameter or default value for the provided name.
59+
//
60+
// Notes for DefaultRouter implementation:
61+
// Path parameter could be empty for cases like that:
62+
// * route `/release-:version/bin` and request URL is `/release-/bin`
63+
// * route `/api/:version/image.jpg` and request URL is `/api//image.jpg`
64+
// but not when path parameter is last part of route path
65+
// * route `/download/file.:ext` will not match request `/download/file.`
66+
PathParamDefault(name string, defaultValue string) string
67+
5868
// PathParams returns path parameter values.
5969
PathParams() PathParams
6070

@@ -176,6 +186,9 @@ type Context interface {
176186
Redirect(code int, url string) error
177187

178188
// Echo returns the `Echo` instance.
189+
//
190+
// WARNING: Remember that Echo public fields and methods are coroutine safe ONLY when you are NOT mutating them
191+
// anywhere in your code after Echo server has started.
179192
Echo() *Echo
180193
}
181194

@@ -379,7 +392,10 @@ func (c *DefaultContext) PathParam(name string) string {
379392
return c.pathParams.Get(name, "")
380393
}
381394

382-
// PathParamDefault does not exist as expecting empty path param makes no sense
395+
// PathParamDefault returns the path parameter or default value for the provided name.
396+
func (c *DefaultContext) PathParamDefault(name, defaultValue string) string {
397+
return c.pathParams.Get(name, defaultValue)
398+
}
383399

384400
// PathParams returns path parameter values.
385401
func (c *DefaultContext) PathParams() PathParams {
@@ -406,7 +422,8 @@ func (c *DefaultContext) QueryParam(name string) string {
406422
}
407423

408424
// QueryParamDefault returns the query param or default value for the provided name.
409-
// Note: QueryParamDefault does not distinguish if form had no value by that name or value was empty string
425+
// Note: QueryParamDefault does not distinguish if query had no value by that name or value was empty string
426+
// This means URLs `/test?search=` and `/test` would both return `1` for `c.QueryParamDefault("search", "1")`
410427
func (c *DefaultContext) QueryParamDefault(name, defaultValue string) string {
411428
value := c.QueryParam(name)
412429
if value == "" {

context_test.go

Lines changed: 253 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -448,22 +448,142 @@ func TestContextCookie(t *testing.T) {
448448
assert.Contains(t, rec.Header().Get(HeaderSetCookie), "HttpOnly")
449449
}
450450

451-
func TestContextPathParam(t *testing.T) {
452-
e := New()
453-
req := httptest.NewRequest(http.MethodGet, "/", nil)
454-
c := e.NewContext(req, nil).(*DefaultContext)
451+
func TestContext_PathParams(t *testing.T) {
452+
var testCases = []struct {
453+
name string
454+
given *PathParams
455+
expect PathParams
456+
}{
457+
{
458+
name: "param exists",
459+
given: &PathParams{
460+
{Name: "uid", Value: "101"},
461+
{Name: "fid", Value: "501"},
462+
},
463+
expect: PathParams{
464+
{Name: "uid", Value: "101"},
465+
{Name: "fid", Value: "501"},
466+
},
467+
},
468+
{
469+
name: "params is empty",
470+
given: &PathParams{},
471+
expect: PathParams{},
472+
},
473+
}
474+
475+
for _, tc := range testCases {
476+
t.Run(tc.name, func(t *testing.T) {
477+
e := New()
478+
req := httptest.NewRequest(http.MethodGet, "/", nil)
479+
c := e.NewContext(req, nil)
455480

456-
params := &PathParams{
457-
{Name: "uid", Value: "101"},
458-
{Name: "fid", Value: "501"},
481+
c.(RoutableContext).SetRawPathParams(tc.given)
482+
483+
assert.EqualValues(t, tc.expect, c.PathParams())
484+
})
459485
}
460-
// ParamNames
461-
c.pathParams = params
462-
assert.EqualValues(t, *params, c.PathParams())
486+
}
487+
488+
func TestContext_PathParam(t *testing.T) {
489+
var testCases = []struct {
490+
name string
491+
given *PathParams
492+
whenParamName string
493+
expect string
494+
}{
495+
{
496+
name: "param exists",
497+
given: &PathParams{
498+
{Name: "uid", Value: "101"},
499+
{Name: "fid", Value: "501"},
500+
},
501+
whenParamName: "uid",
502+
expect: "101",
503+
},
504+
{
505+
name: "multiple same param values exists - return first",
506+
given: &PathParams{
507+
{Name: "uid", Value: "101"},
508+
{Name: "uid", Value: "202"},
509+
{Name: "fid", Value: "501"},
510+
},
511+
whenParamName: "uid",
512+
expect: "101",
513+
},
514+
{
515+
name: "param does not exists",
516+
given: &PathParams{
517+
{Name: "uid", Value: "101"},
518+
},
519+
whenParamName: "nope",
520+
expect: "",
521+
},
522+
}
523+
524+
for _, tc := range testCases {
525+
t.Run(tc.name, func(t *testing.T) {
526+
e := New()
527+
req := httptest.NewRequest(http.MethodGet, "/", nil)
528+
c := e.NewContext(req, nil)
529+
530+
c.(RoutableContext).SetRawPathParams(tc.given)
531+
532+
assert.EqualValues(t, tc.expect, c.PathParam(tc.whenParamName))
533+
})
534+
}
535+
}
536+
537+
func TestContext_PathParamDefault(t *testing.T) {
538+
var testCases = []struct {
539+
name string
540+
given *PathParams
541+
whenParamName string
542+
whenDefaultValue string
543+
expect string
544+
}{
545+
{
546+
name: "param exists",
547+
given: &PathParams{
548+
{Name: "uid", Value: "101"},
549+
{Name: "fid", Value: "501"},
550+
},
551+
whenParamName: "uid",
552+
whenDefaultValue: "999",
553+
expect: "101",
554+
},
555+
{
556+
name: "param exists and is empty",
557+
given: &PathParams{
558+
{Name: "uid", Value: ""},
559+
{Name: "fid", Value: "501"},
560+
},
561+
whenParamName: "uid",
562+
whenDefaultValue: "999",
563+
expect: "", // <-- this is different from QueryParamDefault behaviour
564+
},
565+
{
566+
name: "param does not exists",
567+
given: &PathParams{
568+
{Name: "uid", Value: "101"},
569+
},
570+
whenParamName: "nope",
571+
whenDefaultValue: "999",
572+
expect: "999",
573+
},
574+
}
575+
576+
for _, tc := range testCases {
577+
t.Run(tc.name, func(t *testing.T) {
578+
e := New()
579+
req := httptest.NewRequest(http.MethodGet, "/", nil)
580+
c := e.NewContext(req, nil)
463581

464-
// Param
465-
assert.Equal(t, "501", c.PathParam("fid"))
466-
assert.Equal(t, "", c.PathParam("undefined"))
582+
c.(RoutableContext).SetRawPathParams(tc.given)
583+
584+
assert.EqualValues(t, tc.expect, c.PathParamDefault(tc.whenParamName, tc.whenDefaultValue))
585+
})
586+
}
467587
}
468588

469589
func TestContextGetAndSetParam(t *testing.T) {
@@ -568,27 +688,129 @@ func TestContextFormValue(t *testing.T) {
568688
assert.Error(t, err)
569689
}
570690

571-
func TestContextQueryParam(t *testing.T) {
572-
q := make(url.Values)
573-
q.Set("name", "Jon Snow")
574-
q.Set("email", "[email protected]")
575-
req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil)
576-
e := New()
577-
c := e.NewContext(req, nil)
691+
func TestContext_QueryParams(t *testing.T) {
692+
var testCases = []struct {
693+
name string
694+
givenURL string
695+
expect url.Values
696+
}{
697+
{
698+
name: "multiple values in url",
699+
givenURL: "/?test=1&test=2&email=jon%40labstack.com",
700+
expect: url.Values{
701+
"test": []string{"1", "2"},
702+
"email": []string{"[email protected]"},
703+
},
704+
},
705+
{
706+
name: "single value in url",
707+
givenURL: "/?nope=1",
708+
expect: url.Values{
709+
"nope": []string{"1"},
710+
},
711+
},
712+
{
713+
name: "no query params in url",
714+
givenURL: "/?",
715+
expect: url.Values{},
716+
},
717+
}
578718

579-
// QueryParam
580-
assert.Equal(t, "Jon Snow", c.QueryParam("name"))
581-
assert.Equal(t, "[email protected]", c.QueryParam("email"))
719+
for _, tc := range testCases {
720+
t.Run(tc.name, func(t *testing.T) {
721+
req := httptest.NewRequest(http.MethodGet, tc.givenURL, nil)
722+
e := New()
723+
c := e.NewContext(req, nil)
582724

583-
// QueryParamDefault
584-
assert.Equal(t, "Jon Snow", c.QueryParamDefault("name", "nope"))
585-
assert.Equal(t, "default", c.QueryParamDefault("missing", "default"))
725+
assert.Equal(t, tc.expect, c.QueryParams())
726+
})
727+
}
728+
}
586729

587-
// QueryParams
588-
assert.Equal(t, url.Values{
589-
"name": []string{"Jon Snow"},
590-
"email": []string{"[email protected]"},
591-
}, c.QueryParams())
730+
func TestContext_QueryParam(t *testing.T) {
731+
var testCases = []struct {
732+
name string
733+
givenURL string
734+
whenParamName string
735+
expect string
736+
}{
737+
{
738+
name: "value exists in url",
739+
givenURL: "/?test=1",
740+
whenParamName: "test",
741+
expect: "1",
742+
},
743+
{
744+
name: "multiple values exists in url",
745+
givenURL: "/?test=9&test=8",
746+
whenParamName: "test",
747+
expect: "9", // <-- first value in returned
748+
},
749+
{
750+
name: "value does not exists in url",
751+
givenURL: "/?nope=1",
752+
whenParamName: "test",
753+
expect: "",
754+
},
755+
{
756+
name: "value is empty in url",
757+
givenURL: "/?test=",
758+
whenParamName: "test",
759+
expect: "",
760+
},
761+
}
762+
763+
for _, tc := range testCases {
764+
t.Run(tc.name, func(t *testing.T) {
765+
req := httptest.NewRequest(http.MethodGet, tc.givenURL, nil)
766+
e := New()
767+
c := e.NewContext(req, nil)
768+
769+
assert.Equal(t, tc.expect, c.QueryParam(tc.whenParamName))
770+
})
771+
}
772+
}
773+
774+
func TestContext_QueryParamDefault(t *testing.T) {
775+
var testCases = []struct {
776+
name string
777+
givenURL string
778+
whenParamName string
779+
whenDefaultValue string
780+
expect string
781+
}{
782+
{
783+
name: "value exists in url",
784+
givenURL: "/?test=1",
785+
whenParamName: "test",
786+
whenDefaultValue: "999",
787+
expect: "1",
788+
},
789+
{
790+
name: "value does not exists in url",
791+
givenURL: "/?nope=1",
792+
whenParamName: "test",
793+
whenDefaultValue: "999",
794+
expect: "999",
795+
},
796+
{
797+
name: "value is empty in url",
798+
givenURL: "/?test=",
799+
whenParamName: "test",
800+
whenDefaultValue: "999",
801+
expect: "999",
802+
},
803+
}
804+
805+
for _, tc := range testCases {
806+
t.Run(tc.name, func(t *testing.T) {
807+
req := httptest.NewRequest(http.MethodGet, tc.givenURL, nil)
808+
e := New()
809+
c := e.NewContext(req, nil)
810+
811+
assert.Equal(t, tc.expect, c.QueryParamDefault(tc.whenParamName, tc.whenDefaultValue))
812+
})
813+
}
592814
}
593815

594816
func TestContextFormFile(t *testing.T) {

echo.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ import (
5555
)
5656

5757
// Echo is the top-level framework instance.
58-
// Note: replacing/nilling public fields is not coroutine/thread-safe and can cause data-races/panics.
58+
//
59+
// Note: replacing/nilling public fields is not coroutine/thread-safe and can cause data-races/panics. This is very likely
60+
// to happen when you access Echo instances through Context.Echo() method.
5961
type Echo struct {
6062
// premiddleware are middlewares that are run for every request before routing is done
6163
premiddleware []MiddlewareFunc
@@ -67,8 +69,8 @@ type Echo struct {
6769
routerCreator func(e *Echo) Router
6870

6971
contextPool sync.Pool
70-
// contextPathParamAllocSize holds maximum parameter count for all added routes. This is necessary info for context
71-
// creation time so we can allocate path parameter values slice.
72+
// contextPathParamAllocSize holds maximum parameter count for all added routes. This is necessary info at context
73+
// creation moment so we can allocate path parameter values slice with correct size.
7274
contextPathParamAllocSize int
7375

7476
// NewContextFunc allows using custom context implementations, instead of default *echo.context

0 commit comments

Comments
 (0)