From 8a8924a2d34591616590bf8e3e8ee1e908785e2b Mon Sep 17 00:00:00 2001 From: Ambrose Chua Date: Sun, 8 Nov 2020 21:49:44 +0800 Subject: [PATCH] Add URL parser --- app_test.go | 2 +- go.mod | 1 + go.sum | 3 ++ url.go | 68 ++++++++++++++++++++++++++++++++++++++++++++ url_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 url.go create mode 100644 url_test.go diff --git a/app_test.go b/app_test.go index f4a47ba..c920016 100644 --- a/app_test.go +++ b/app_test.go @@ -9,7 +9,7 @@ import ( func TestChooseTemplate(t *testing.T) { tmpl, err := template.ParseGlob("templates/*") if err != nil { - t.Errorf("Unable to load templates: %v", err) + t.Errorf("unable to load templates: %v", err) } app := &Datetime{tmpl: tmpl} diff --git a/go.mod b/go.mod index 3dda60d..b3a0fe1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/serverwentdown/datetime.link go 1.14 require ( + github.com/google/go-cmp v0.5.2 github.com/hbollon/go-edlib v1.3.1 go.uber.org/zap v1.16.0 ) diff --git a/go.sum b/go.sum index e038195..a7c1c0e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/hbollon/go-edlib v1.3.1 h1:3x2Faq1xbShKhel5wEYyCNZFguh+s8GH75jdp8w6phU= github.com/hbollon/go-edlib v1.3.1/go.mod h1:wnt6o6EIVEzUfgbUZY7BerzQ2uvzp354qmS2xaLkrhM= @@ -37,6 +39,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/url.go b/url.go new file mode 100644 index 0000000..e67894e --- /dev/null +++ b/url.go @@ -0,0 +1,68 @@ +package main + +import ( + "errors" + "net/url" + "strings" + "time" +) + +// ErrMissingComponent is thrown when the URL has empty or missing components +var ErrMissingComponent = errors.New("missing URL component") + +// ErrTooManyComponent is thrown when there are more than 2 components +var ErrTooManyComponent = errors.New("too many components") + +var timeRFC3339NoSec = "2006-01-02T15:04Z07:00" +var timeFormats = []string{time.RFC3339, timeRFC3339NoSec} + +// Request is a parsed datetime URL +type Request struct { + Time time.Time + Zones []string +} + +// ParseRequest parses an input URL into a Request +func ParseRequest(u *url.URL) (Request, error) { + var err error + + parts := strings.Split(u.Path, "/")[1:] + if len(parts) > 2 { + return Request{}, ErrTooManyComponent + } + if len(parts) < 1 { + return Request{}, ErrMissingComponent + } + + // Parse time portion + var t time.Time + timeString := parts[0] + if len(timeString) == 0 { + return Request{}, ErrMissingComponent + } + for _, f := range timeFormats { + t, err = time.Parse(f, timeString) + if err == nil { + break + } + } + if err != nil { + return Request{}, err + } + + // Split zones + var z []string + zoneString := "" + if len(parts) >= 2 { + zoneString = parts[1] + } + if len(zoneString) == 0 { + return Request{}, ErrMissingComponent + } + z = strings.Split(zoneString, ",") + + return Request{ + Time: t, + Zones: z, + }, nil +} diff --git a/url_test.go b/url_test.go new file mode 100644 index 0000000..4ee4f11 --- /dev/null +++ b/url_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "errors" + "net/url" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func mustURLParse(s string) *url.URL { + u, err := url.Parse(s) + if err != nil { + panic(err) + } + return u +} + +func TestURLParse(t *testing.T) { + u := mustURLParse("http://test/2020-06-02T14:00+08:00/Singapore,Malaysia") + got, err := ParseRequest(u) + if err != nil { + t.Errorf("mismatch: got error %v", err) + return + } + want := Request{ + time.Date(2020, 6, 2, 14, 0, 0, 0, time.FixedZone("UTC +8", 8*60*60)), + []string{"Singapore", "Malaysia"}, + } + if !cmp.Equal(got, want) { + t.Errorf("mismatch: \n%v", cmp.Diff(got, want)) + } + + u = mustURLParse("http://test/2019-04-30T18:00:00Z/Nowhere") + got, err = ParseRequest(u) + if err != nil { + t.Errorf("mismatch: got error %v", err) + return + } + want = Request{ + time.Date(2019, 4, 30, 18, 0, 0, 0, time.FixedZone("UTC", 0)), + []string{"Nowhere"}, + } + if !cmp.Equal(got, want) { + t.Errorf("mismatch: \n%v", cmp.Diff(got, want)) + } +} + +func TestURLParseFail(t *testing.T) { + u := mustURLParse("http://test/2002-08-30T14:00+06:00/") + _, err := ParseRequest(u) + if !errors.Is(err, ErrMissingComponent) { + t.Errorf("mismatch: got error %v, want error %v", err, ErrMissingComponent) + return + } + + u = mustURLParse("http://test/") + _, err = ParseRequest(u) + if !errors.Is(err, ErrMissingComponent) { + t.Errorf("mismatch: got error %v, want error %v", err, ErrMissingComponent) + return + } + + u = mustURLParse("http://test/2000-01-13T00:00Z08:00/hi") + _, err = ParseRequest(u) + _, isParseError := err.(*time.ParseError) + if !isParseError { + t.Errorf("mismatch: got error %v, want time.ParseError", err) + return + } + + u = mustURLParse("http://test/2000-01-13 00:00+08:00/hi") + _, err = ParseRequest(u) + _, isParseError = err.(*time.ParseError) + if !isParseError { + t.Errorf("mismatch: got error %v, want time.ParseError", err) + return + } + +}