Skip to content

Commit

Permalink
adding field type for performing logical time operations
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Nappi committed Jul 14, 2016
1 parent 0258ee7 commit c9877a7
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
72 changes: 72 additions & 0 deletions fields.go
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))
}
112 changes: 112 additions & 0 deletions fields_test.go
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)
}
}
}

0 comments on commit c9877a7

Please sign in to comment.