diff --git a/HISTORY.md b/HISTORY.md index 6b331b2d..62aaf771 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,18 @@ The codebase for Dependency Injection, Internationalization and localization and ## Fixes and Improvements +- New `{x:weekday}` path parameter type, example code: + +```go +// 0 to 7 (leading zeros don't matter) or "Sunday" to "Monday" or "sunday" to "monday". +// http://localhost:8080/schedule/monday or http://localhost:8080/schedule/Monday or +// http://localhost:8080/schedule/1 or http://localhost:8080/schedule/0001. +app.Get("/schedule/{day:weekday}", func(ctx iris.Context) { + day, _ := ctx.Params().GetWeekday("day") + ctx.Writef("Weekday requested was: %v\n", day) +}) +``` + - Make the `Context.JSON` method customizable by modifying the `context.WriteJSON` package-level function. - Add new `iris.NewGuide` which helps you build a simple and nice JSON API with services as dependencies and better design pattern. - Make `Context.Domain()` customizable by letting developers to modify the `Context.GetDomain` package-level function. diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index b6914f08..6f6fedae 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -146,6 +146,12 @@ func main() { // +------------------------+ // yyyy/mm/dd format e.g. /blog/{param:date} matches /blog/2022/04/21. // + // +------------------------+ + // | {param:weekday} | + // +------------------------+ + // positive integer 0 to 6 or + // string of time.Weekday longname format ("sunday" to "monday" or "Sunday" to "Monday") + // format e.g. /schedule/{param:weekday} matches /schedule/monday. // // If type is missing then parameter's type is defaulted to string, so // {param} is identical to {param:string}. @@ -214,6 +220,14 @@ func main() { ctx.Writef("Raw time.Time.String value: %v\nyyyy/mm/dd: %s\n", rawTimeValue, yearMonthDay) }) + // 0 to 7 or "Sunday" to "Monday" or "sunday" to "monday". Leading zeros don't matter. + // http://localhost:8080/schedule/monday or http://localhost:8080/schedule/Monday or + // http://localhost:8080/schedule/1 or http://localhost:8080/schedule/0001. + app.Get("/schedule/{day:weekday}", func(ctx iris.Context) { + day, _ := ctx.Params().GetWeekday("day") + ctx.Writef("Weekday requested was: %v\n", day) + }) + // you can use the "string" type which is valid for a single path parameter that can be anything. app.Get("/username/{name}", func(ctx iris.Context) { ctx.Writef("Hello %s", ctx.Params().Get("name")) diff --git a/context/request_params.go b/context/request_params.go index cc863758..a86c83ab 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -17,18 +17,6 @@ type RequestParams struct { memstore.Store } -// RequestParamsReadOnly is the read-only access type of RequestParams. -type RequestParamsReadOnly interface { - Get(key string) string - GetEntryAt(index int) memstore.Entry - Visit(visitor func(key string, value string)) - GetTrim(key string) string - GetEscape(key string) string - GetDecoded(key string) string -} // Note: currently unused. - -var _ RequestParamsReadOnly = (*RequestParams)(nil) - // Set inserts a parameter value. // See `Get` too. func (r *RequestParams) Set(key, value string) { @@ -253,6 +241,20 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ return unixEpochTime } + return v + } + }, + reflect.TypeOf(time.Weekday(0)): func(paramIndex int) interface{} { + return func(ctx *Context) time.Weekday { + if ctx.Params().Len() <= paramIndex { + return time.Sunday + } + + v, ok := ctx.Params().GetEntryAt(paramIndex).ValueRaw.(time.Weekday) + if !ok { + return time.Sunday + } + return v } }, diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 95322ece..0b71ee75 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -700,6 +700,25 @@ func (e Entry) TimeDefault(def time.Time) (time.Time, error) { return vv, nil } +var weekdayType = reflect.TypeOf(time.Weekday(0)) + +// WeekdayDefault returns the stored time.Weekday value based on its "key". +// If does not exist or the stored key's value is not a weekday +// it returns the "def" weekday value and a not found error. +func (e Entry) WeekdayDefault(def time.Weekday) (time.Weekday, error) { + v := e.ValueRaw + if v == nil { + return def, e.notFound(weekdayType) + } + + vv, ok := v.(time.Weekday) + if !ok { + return def, nil + } + + return vv, nil +} + // Value returns the value of the entry, // respects the immutable. func (e Entry) Value() interface{} { @@ -1184,6 +1203,20 @@ func (r *Store) SimpleDate(key string) string { return tt.Format(simpleDateLayout) } +const zeroWeekday = time.Sunday + +// GetWeekday returns the stored time.Weekday value based on its "key". +// If does not exist or the stored key's value is not a weekday +// it returns the time.Sunday value and a not found error. +func (r *Store) GetWeekday(key string) (time.Weekday, error) { + v, ok := r.GetEntry(key) + if !ok { + return zeroWeekday, v.notFound(timeType) + } + + return v.WeekdayDefault(zeroWeekday) +} + // Remove deletes an entry linked to that "key", // returns true if an entry is actually removed. func (r *Store) Remove(key string) bool { diff --git a/macro/macro_test.go b/macro/macro_test.go index 856d2f5b..300ad1b2 100644 --- a/macro/macro_test.go +++ b/macro/macro_test.go @@ -514,6 +514,36 @@ func TestDateEvaluatorRaw(t *testing.T) { } } +func TestWeekdayEvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + expected time.Weekday + }{ + {true, "Monday", time.Monday}, // 0 + {true, "monday", time.Monday}, // 1 + {false, "Sundays", time.Weekday(-1)}, // 2 + {false, "sundays", time.Weekday(-1)}, // 3 + {false, "-1", time.Weekday(-1)}, // 4 + {true, "0000002", time.Tuesday}, // 5 + {true, "3", time.Wednesday}, // 6 + {true, "6", time.Saturday}, // 7 + } + for i, tt := range tests { + testEvaluatorRaw(t, Weekday, tt.input, reflect.TypeOf(time.Weekday(0)).Kind(), tt.pass, i) + + if v, ok := Weekday.Evaluator(tt.input); ok { + if value, ok := v.(time.Weekday); ok { + if expected, got := tt.expected, value; expected != got { + t.Fatalf("[%d] expected: %s but got: %s", i, expected, got) + } + } else { + t.Fatalf("[%d] expected to be able to cast as time.Weekday directly", i) + } + } + } +} + func TestConvertBuilderFunc(t *testing.T) { fn := func(min uint64, slice []string) func(string) bool { return func(paramValue string) bool { diff --git a/macro/macros.go b/macro/macros.go index 86eb28b8..48264654 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -444,6 +444,48 @@ var ( return tt, true }) + // ErrParamNotWeekday is fired when the parameter value is not a form of a time.Weekday. + ErrParamNotWeekday = errors.New("parameter is not a valid weekday") + longDayNames = map[string]time.Weekday{ + "Sunday": time.Sunday, + "Monday": time.Monday, + "Tuesday": time.Tuesday, + "Wednesday": time.Wednesday, + "Thursday": time.Thursday, + "Friday": time.Friday, + "Saturday": time.Saturday, + // lowercase. + "sunday": time.Sunday, + "monday": time.Monday, + "tuesday": time.Tuesday, + "wednesday": time.Wednesday, + "thursday": time.Thursday, + "friday": time.Friday, + "saturday": time.Saturday, + } + + // Weekday type, returns a type of time.Weekday. + // Valid values: + // 0 to 7 (leading zeros don't matter) or "Sunday" to "Monday" or "sunday" to "monday". + Weekday = NewMacro("weekday", "", false, false, func(paramValue string) (interface{}, bool) { + d, ok := longDayNames[paramValue] + if !ok { + // try parse from integer. + n, err := strconv.Atoi(paramValue) + if err != nil { + return fmt.Errorf("%s: %w", paramValue, err), false + } + + if n < 0 || n > 6 { + return fmt.Errorf("%s: %w", paramValue, ErrParamNotWeekday), false + } + + return time.Weekday(n), true + } + + return d, true + }) + // Defaults contains the defaults macro and parameters types for the router. // // Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. @@ -467,6 +509,7 @@ var ( Mail, Email, Date, + Weekday, } )