diff --git a/README.md b/README.md index 6afd02f..82ad44e 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ a request does not satisfy the maybeFn logic. - `rest.NewErrorLogger` - creates a struct providing shorter form of logger call - `rest.FileServer` - creates a file server for static assets with directory listing disabled - `realip.Get` - returns client's IP address +- `rest.ParseFromTo` - parses "from" and "to" request's query params with various formats ## Profiler diff --git a/rest.go b/rest.go index 82361bc..57fe4a7 100644 --- a/rest.go +++ b/rest.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/json" "net/http" + "time" "github.com/pkg/errors" ) @@ -67,3 +68,33 @@ func renderJSONWithStatus(w http.ResponseWriter, data interface{}, code int) { w.WriteHeader(code) _, _ = w.Write(buf.Bytes()) } + +// ParseFromTo parses from and to query params of the request +func ParseFromTo(r *http.Request) (from, to time.Time, err error) { + parseTimeStamp := func(ts string) (time.Time, error) { + formats := []string{ + "2006-01-02T15:04:05.000000000", + "2006-01-02T15:04:05", + "2006-01-02T15:04", + "20060102", + time.RFC3339, + time.RFC3339Nano, + } + + for _, f := range formats { + if t, e := time.Parse(f, ts); e == nil { + return t, nil + } + } + return time.Time{}, errors.Errorf("can't parse date %q", ts) + } + + if from, err = parseTimeStamp(r.URL.Query().Get("from")); err != nil { + return from, to, errors.Wrap(err, "incorrect from time") + } + + if to, err = parseTimeStamp(r.URL.Query().Get("to")); err != nil { + return from, to, errors.Wrap(err, "incorrect to time") + } + return from, to, nil +} diff --git a/rest_test.go b/rest_test.go index 4aefcd5..27e8c58 100644 --- a/rest_test.go +++ b/rest_test.go @@ -5,8 +5,11 @@ import ( "io" "net/http" "net/http/httptest" + "strconv" "testing" + "time" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -73,6 +76,53 @@ func TestRest_RenderJSONWithHTML(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type")) } +func TestParseFromTo(t *testing.T) { + + tbl := []struct { + query string + from, to time.Time + err error + }{ + { + query: "from=20220406&to=20220501", + from: time.Date(2022, time.April, 6, 0, 0, 0, 0, time.UTC), + to: time.Date(2022, time.May, 1, 0, 0, 0, 0, time.UTC), + err: nil, + }, + { + query: "from=2022-04-06T18:30:25&to=2022-05-01T17:50", + from: time.Date(2022, time.April, 6, 18, 30, 25, 0, time.UTC), + to: time.Date(2022, time.May, 1, 17, 50, 0, 0, time.UTC), + err: nil, + }, + { + query: "from=2022-04-06T18:30:25&to=xyzbad", + err: errors.New(`incorrect to time: can't parse date "xyzbad"`), + }, + { + query: "from=123455&to=2022-05-01T17:50", + err: errors.New(`incorrect from time: can't parse date "123455"`), + }, + {"", time.Time{}, time.Time{}, errors.New("incorrect from time: can't parse date \"\"")}, + } + + for i, tt := range tbl { + t.Run(strconv.Itoa(i), func(t *testing.T) { + req, err := http.NewRequest("GET", "http://localhost?"+tt.query, http.NoBody) + require.NoError(t, err) + from, to, err := ParseFromTo(req) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, tt.from, from) + assert.Equal(t, tt.to, to) + }) + } + +} + func getTestHandlerBlah() http.HandlerFunc { fn := func(rw http.ResponseWriter, req *http.Request) { _, _ = rw.Write([]byte("blah"))