Skip to content

Commit 0235fb7

Browse files
committed
Added ComparativeStrings to support =~ and =! operators on string values
1 parent 2b922c4 commit 0235fb7

File tree

3 files changed

+161
-9
lines changed

3 files changed

+161
-9
lines changed

decode.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ func (d *decoder) coerce(query string, target reflect.Kind, field reflect.Value)
152152
if err == nil {
153153
field.Set(reflect.ValueOf(t))
154154
}
155+
case ComparativeString:
156+
s := ComparativeString{}
157+
err = s.Parse(query)
158+
if err == nil {
159+
field.Set(reflect.ValueOf(s))
160+
}
155161
default:
156162
d.value(field)
157163
}

fields.go

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,54 @@ import (
77
"time"
88
)
99

10+
const (
11+
operatorEquals = "="
12+
operatorGreater = ">"
13+
operatorGreaterEq = ">="
14+
operatorLesser = "<"
15+
operatorLesserEq = "<="
16+
operatorLike = "~"
17+
operatorDifferent = "!"
18+
)
19+
1020
// parseOperator parses a leading logical operator out of the provided string
1121
func parseOperator(s string) string {
1222
switch s[0] {
13-
case 60: // "<"
23+
case operatorLesser[0]: // "<"
24+
if 1 == len(s) {
25+
return operatorLesser
26+
}
1427
switch s[1] {
15-
case 61: // "="
16-
return "<="
28+
case operatorEquals[0]: // "="
29+
return operatorLesserEq
1730
default:
18-
return "<"
31+
return operatorLesser
32+
}
33+
case operatorGreater[0]: // ">"
34+
if 1 == len(s) {
35+
return operatorGreater
1936
}
20-
case 62: // ">"
2137
switch s[1] {
22-
case 61: // "="
23-
return ">="
38+
case operatorEquals[0]: // "="
39+
return operatorGreaterEq
2440
default:
25-
return ">"
41+
return operatorGreater
2642
}
43+
case operatorLike[0]: // "~"
44+
return operatorLike
45+
case operatorDifferent[0]: // "!"
46+
return operatorDifferent
2747
default:
2848
// no operator found, default to "="
29-
return "="
49+
return operatorEquals
3050
}
3151
}
3252

53+
type ComparativeString struct {
54+
Operator string
55+
Str string
56+
}
57+
3358
// ComparativeTime is a field that can be used for specifying a query parameter
3459
// which includes a conditional operator and a timestamp
3560
type ComparativeTime struct {
@@ -70,3 +95,38 @@ func (c *ComparativeTime) Parse(query string) error {
7095
func (c ComparativeTime) String() string {
7196
return fmt.Sprintf("%s%s", c.Operator, c.Time.Format(time.RFC3339))
7297
}
98+
99+
// Parse is used to parse a query string into a ComparativeString instance
100+
func (c *ComparativeString) Parse(query string) error {
101+
if len(query) <= 2 {
102+
return errors.New("qstring: Invalid Query")
103+
}
104+
105+
c.Operator = parseOperator(query)
106+
107+
if c.Operator != operatorDifferent && c.Operator != operatorLike && c.Operator != operatorEquals {
108+
return errors.New(fmt.Sprintf("qstring: Invalid operator for %T", c))
109+
}
110+
if c.Operator == operatorEquals {
111+
c.Operator = ""
112+
}
113+
114+
// if no operator was provided and we defaulted to an equality operator
115+
if !strings.HasPrefix(query, c.Operator) {
116+
query = fmt.Sprintf("=%s", query)
117+
}
118+
119+
var err error
120+
c.Str = query[len(c.Operator):]
121+
if err != nil {
122+
return err
123+
}
124+
125+
return nil
126+
}
127+
128+
// String returns this ComparativeString instance in the form of the query
129+
// parameter that it came in on
130+
func (c ComparativeString) String() string {
131+
return fmt.Sprintf("%s%s", c.Operator, c.Str)
132+
}

fields_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package qstring
22

33
import (
4+
"fmt"
45
"net/url"
56
"strings"
67
"testing"
@@ -108,3 +109,88 @@ func TestComparativeTimeMarshalString(t *testing.T) {
108109
}
109110
}
110111
}
112+
113+
func TestComparativeStringUnmarshal(t *testing.T) {
114+
type Query struct {
115+
Equals ComparativeString
116+
Similar ComparativeString
117+
Different ComparativeString
118+
}
119+
120+
val1 := "stringValue1"
121+
equalsVal := fmt.Sprintf("%s", val1)
122+
val2 := "stringValue2"
123+
similarVal := fmt.Sprintf("~%s", val2)
124+
val3 := "stringValue3"
125+
diffVal := fmt.Sprintf("!%s", val3)
126+
127+
query := url.Values{
128+
"equals": []string{equalsVal},
129+
"different": []string{diffVal},
130+
"similar": []string{similarVal},
131+
}
132+
133+
params := &Query{}
134+
err := Unmarshal(query, params)
135+
if err != nil {
136+
t.Fatal(err.Error())
137+
}
138+
139+
equals := params.Equals.String()
140+
if equals != equalsVal {
141+
t.Errorf("Expected equals val of %s, got %s instead.", equalsVal, equals)
142+
}
143+
similar := params.Similar.String()
144+
if similar != similarVal {
145+
t.Errorf("Expected similar val of %s, got %s instead.", similarVal, similar)
146+
}
147+
diff := params.Different.String()
148+
if diff != diffVal {
149+
t.Errorf("Expected different val of %s, got %s instead.", diffVal, diff)
150+
}
151+
}
152+
153+
func TestComparativeStringMarshalString(t *testing.T) {
154+
type Query struct {
155+
Equals ComparativeString
156+
Similar ComparativeString
157+
Different ComparativeString
158+
}
159+
160+
val1 := "stringValue1"
161+
equalsVal := fmt.Sprintf("%s", val1)
162+
equals := &ComparativeString{}
163+
equals.Parse(equalsVal)
164+
165+
val2 := "stringValue2"
166+
similarVal := fmt.Sprintf("~%s", val2)
167+
similar := &ComparativeString{}
168+
similar.Parse(similarVal)
169+
170+
val3 := "stringValue3"
171+
diffVal := fmt.Sprintf("!%s", val3)
172+
different := &ComparativeString{}
173+
different.Parse(diffVal)
174+
175+
q := &Query{*equals, *similar, *different}
176+
result, err := MarshalString(q)
177+
if err != nil {
178+
t.Fatalf("Unable to marshal comparative timestamp: %s", err.Error())
179+
}
180+
181+
var unescaped string
182+
unescaped, err = url.QueryUnescape(result)
183+
if err != nil {
184+
t.Fatalf("Unable to unescape query string %q: %q", result, err.Error())
185+
}
186+
expected := []string{
187+
fmt.Sprintf("different=!%s", val3),
188+
fmt.Sprintf("equals=%s", val1),
189+
fmt.Sprintf("similar=~%s", val2),
190+
}
191+
for _, ts := range expected {
192+
if !strings.Contains(unescaped, ts) {
193+
t.Errorf("Expected query string %s to contain %s", unescaped, ts)
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)