-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding field type for performing logical time operations
- Loading branch information
Jon Nappi
committed
Jul 14, 2016
1 parent
0258ee7
commit c9877a7
Showing
2 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package qstring | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
"time" | ||
) | ||
|
||
// parseOperator parses a leading logical operator out of the provided string | ||
func parseOperator(s string) string { | ||
switch s[0] { | ||
case 60: // "<" | ||
switch s[1] { | ||
case 61: // "=" | ||
return "<=" | ||
default: | ||
return "<" | ||
} | ||
case 62: // ">" | ||
switch s[1] { | ||
case 61: // "=" | ||
return ">=" | ||
default: | ||
return ">" | ||
} | ||
default: | ||
// no operator found, default to "=" | ||
return "=" | ||
} | ||
} | ||
|
||
// ComparativeTime is a field that can be used for specifying a query parameter | ||
// which includes a conditional operator and a timestamp | ||
type ComparativeTime struct { | ||
Operator string | ||
Time time.Time | ||
} | ||
|
||
// NewComparativeTime returns a new ComparativeTime instance with a default | ||
// operator of "=" | ||
func NewComparativeTime() *ComparativeTime { | ||
return &ComparativeTime{Operator: "="} | ||
} | ||
|
||
// Parse is used to parse a query string into a ComparativeTime instance | ||
func (c *ComparativeTime) Parse(query string) error { | ||
if len(query) <= 2 { | ||
return errors.New("qstring: Invalid Timestamp Query") | ||
} | ||
|
||
c.Operator = parseOperator(query) | ||
|
||
// if no operator was provided and we defaulted to an equality operator | ||
if !strings.HasPrefix(query, c.Operator) { | ||
query = fmt.Sprintf("=%s", query) | ||
} | ||
|
||
var err error | ||
c.Time, err = time.Parse(time.RFC3339, query[len(c.Operator):]) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// String returns this ComparativeTime instance in the form of the query | ||
// parameter that it came in on | ||
func (c ComparativeTime) String() string { | ||
return fmt.Sprintf("%s%s", c.Operator, c.Time.Format(time.RFC3339)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package qstring | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestComparativeTimeParse(t *testing.T) { | ||
tme := "2006-01-02T15:04:05Z" | ||
testio := []struct { | ||
inp string | ||
operator string | ||
errString string | ||
}{ | ||
{inp: tme, operator: "=", errString: ""}, | ||
{inp: ">" + tme, operator: ">", errString: ""}, | ||
{inp: "<" + tme, operator: "<", errString: ""}, | ||
{inp: ">=" + tme, operator: ">=", errString: ""}, | ||
{inp: "<=" + tme, operator: "<=", errString: ""}, | ||
{inp: "<=" + tme, operator: "<=", errString: ""}, | ||
{inp: "", operator: "=", errString: "qstring: Invalid Timestamp Query"}, | ||
{inp: ">=", operator: "=", errString: "qstring: Invalid Timestamp Query"}, | ||
{inp: ">=" + "foobar", operator: ">=", | ||
errString: `parsing time "foobar" as "2006-01-02T15:04:05Z07:00": cannot parse "foobar" as "2006"`}, | ||
} | ||
|
||
var ct *ComparativeTime | ||
var err error | ||
for _, test := range testio { | ||
ct = NewComparativeTime() | ||
err = ct.Parse(test.inp) | ||
|
||
if ct.Operator != test.operator { | ||
t.Errorf("Expected operator %q, got %q", test.operator, ct.Operator) | ||
} | ||
|
||
if err == nil && len(test.errString) != 0 { | ||
t.Errorf("Expected error %q, got nil", test.errString) | ||
} | ||
|
||
if err != nil && err.Error() != test.errString { | ||
t.Errorf("Expected error %q, got %q", test.errString, err.Error()) | ||
} | ||
} | ||
} | ||
|
||
func TestComparativeTimeUnmarshal(t *testing.T) { | ||
type Query struct { | ||
Created ComparativeTime | ||
Modified ComparativeTime | ||
} | ||
|
||
createdTS := ">2006-01-02T15:04:05Z" | ||
updatedTS := "<=2016-01-02T15:04:05-07:00" | ||
|
||
query := url.Values{ | ||
"created": []string{createdTS}, | ||
"modified": []string{updatedTS}, | ||
} | ||
|
||
params := &Query{} | ||
err := Unmarshal(query, params) | ||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
|
||
created := params.Created.String() | ||
if created != createdTS { | ||
t.Errorf("Expected created ts of %s, got %s instead.", createdTS, created) | ||
} | ||
|
||
modified := params.Modified.String() | ||
if modified != updatedTS { | ||
t.Errorf("Expected update ts of %s, got %s instead.", updatedTS, modified) | ||
} | ||
} | ||
|
||
func TestComparativeTimeMarshal(t *testing.T) { | ||
type Query struct { | ||
Created ComparativeTime | ||
Modified ComparativeTime | ||
} | ||
|
||
createdTS := ">2006-01-02T15:04:05Z" | ||
created := NewComparativeTime() | ||
created.Parse(createdTS) | ||
updatedTS := "<=2016-01-02T15:04:05-07:00" | ||
updated := NewComparativeTime() | ||
updated.Parse(updatedTS) | ||
|
||
q := &Query{*created, *updated} | ||
result, err := Marshal(q) | ||
if err != nil { | ||
t.Fatalf("Unable to marshal comparative timestamp: %s", err.Error()) | ||
} | ||
|
||
var unescaped string | ||
unescaped, err = url.QueryUnescape(result) | ||
if err != nil { | ||
t.Fatalf("Unable to unescape query string %q: %q", result, err.Error()) | ||
} | ||
expected := []string{"created=>2006-01-02T15:04:05Z", | ||
"modified=<=2016-01-02T15:04:05-07:00"} | ||
for _, ts := range expected { | ||
fmt.Println(ts) | ||
if !strings.Contains(unescaped, ts) { | ||
t.Errorf("Expected query string %s to contain %s", unescaped, ts) | ||
} | ||
} | ||
} |