-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactored query_parser. Now each filter format is handled by an impl…
…ementation of a filterquery interface. Added a query_parser_manager to manage the available parsing strategies, keep track of the active one or ones, etc. I also switched to using testify suties. This has had the unfortunate side effect of losing feedback on a per-test basis.
- Loading branch information
Showing
11 changed files
with
813 additions
and
314 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,111 @@ | ||
package main | ||
|
||
/* | ||
Implementation of QueryParserI interface | ||
*/ | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"regexp" | ||
"strings" | ||
|
||
log "github.com/Sirupsen/logrus" | ||
) | ||
|
||
// Format1 satisfies the QueryParserI interface for format1 | ||
// from the sg-query spec | ||
type Format1 struct{} | ||
|
||
var format1QueryRegexp = regexp.MustCompile(`^([\w]+)\((.*)\)`) | ||
|
||
// CanParseString returns a boolean indicating whether or not the method can parse the supplied string. It is not a guarantee that parsing will be successful. | ||
func (f *Format1) CanParseString(queryStr string) bool { | ||
// gotta start with and/or | ||
if !strings.HasPrefix(queryStr, "AND") && | ||
!strings.HasPrefix(queryStr, "and") && | ||
!strings.HasPrefix(queryStr, "OR") && | ||
!strings.HasPrefix(queryStr, "or") { | ||
return false | ||
} | ||
|
||
matches := format1QueryRegexp.FindStringSubmatch(queryStr) | ||
if matches != nil { | ||
if len(matches) != 3 { | ||
return false | ||
} | ||
|
||
if strings.HasPrefix(matches[2], "[[") || strings.HasPrefix(matches[2], "[") { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// ParseString parses the supplied string and returns readFilters and an error | ||
func (f *Format1) ParseString(queryStr string) (readFilters, error) { | ||
query := newReadFilters() | ||
|
||
if !strings.HasPrefix(queryStr, "AND") && | ||
!strings.HasPrefix(queryStr, "and") && | ||
!strings.HasPrefix(queryStr, "OR") && | ||
!strings.HasPrefix(queryStr, "or") { | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "Invalid query format", | ||
} | ||
} | ||
|
||
matches := queryRegexp.FindStringSubmatch(queryStr) | ||
if matches != nil { | ||
if len(matches) != 3 { | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "Invalid query format", | ||
} | ||
} | ||
|
||
var filtersStr string | ||
if strings.HasPrefix(matches[2], "[[") { | ||
filtersStr = matches[2] | ||
} else if strings.HasPrefix(matches[2], "[") { | ||
filtersStr = "[" + matches[2] + "]" | ||
} else { | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "Invalid query filter format", | ||
} | ||
} | ||
|
||
var filters []interface{} | ||
err := json.Unmarshal([]byte(filtersStr), &filters) | ||
if err != nil { | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: err.Error(), | ||
} | ||
} | ||
|
||
queryData := map[string]interface{}{ | ||
"logical_operator": matches[1], | ||
"conditions": filters, | ||
} | ||
|
||
err = mapToReadFilters(queryData, &query) | ||
|
||
return query, err | ||
|
||
} | ||
|
||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "Invalid query format", | ||
} | ||
} | ||
|
||
// Register the format with the manager | ||
func init() { | ||
manager := GetQPManager() | ||
log.Info("manager fetched", manager) | ||
manager.AddParser("format1", &Format1{}) | ||
} |
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,160 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"testing" | ||
|
||
log "github.com/Sirupsen/logrus" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
f := SetupLog() | ||
log.Info("TestMain setup") | ||
manager := GetQPManager() | ||
res := m.Run() | ||
f.Close() | ||
os.Exit(res) | ||
} | ||
|
||
// Define the suite, and absorb the built-in basic suite | ||
// functionality from testify - including a T() method which | ||
// returns the current testing context | ||
type Format1TestSuite struct { | ||
suite.Suite | ||
} | ||
|
||
func SetupLog() *os.File { | ||
f, err := os.OpenFile("sg-restful-test.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) | ||
if err != nil { | ||
panic(fmt.Sprintf("error opening file: %v", err)) | ||
} | ||
|
||
log.SetLevel(log.DebugLevel) | ||
log.SetOutput(f) | ||
log.SetFormatter(&log.TextFormatter{}) | ||
return f | ||
|
||
} | ||
|
||
// Make sure that VariableThatShouldStartAtFive is set to five | ||
// before each test | ||
func (suite *Format1TestSuite) SetupSuite() { | ||
log.Info(" -- Format1 Test Suite --\n") | ||
log.Debug("Format1TestSuite.SetupSuite() - setting active parsers to format1") | ||
manager.SetActiveParsers("format1") | ||
} | ||
|
||
// In order for 'go test' to run this suite, we need to create | ||
// a normal test function and pass our suite to suite.Run | ||
func TestFormat1TestSuite(t *testing.T) { | ||
//ts := new(Format1TestSuite) | ||
log.Info("TestFormat1TestSuite - Running test suite") | ||
suite.Run(t, new(Format1TestSuite)) | ||
log.Info("TestFormat1TestSuite - Finished test suite") | ||
|
||
} | ||
|
||
func (suite *Format1TestSuite) TestActiveParsers() { | ||
log.Info("Format1TestSuite.TestActiveParsers()") | ||
manager := GetQPManager() | ||
keys, active := manager.GetActiveParsers() | ||
log.Infof("active parsers:%s %s", keys, active) | ||
suite.Equal(len(active), 1, "Should be a single active parser") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementRegExpError() { | ||
_, err := parseQuery("()") | ||
|
||
expectedError := queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "No QueryFormats can parse input", | ||
} | ||
suite.Equal(expectedError, err, "Should be formating error") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementFilterFormatError() { | ||
_, err := parseQuery("and(foo, bar)") | ||
|
||
expectedError := queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "No QueryFormats can parse input", | ||
} | ||
suite.Equal(expectedError, err, "Should be formating error") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementJsonError() { | ||
_, err := parseQuery("and([foo, bar,])") | ||
|
||
expectedError := queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "invalid character 'o' in literal false (expecting 'a')", | ||
} | ||
suite.Equal(expectedError, err, "Should be formating error") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementNotAQuery() { | ||
_, err := parseQuery("andwell_this_is_bad") | ||
|
||
expectedError := queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "No QueryFormats can parse input", | ||
} | ||
suite.Equal(expectedError, err, "Should be formating error") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementSliceOfFilters() { | ||
rf, err := parseQuery(`and([["name", "is", "blorg"]])`) | ||
|
||
rfExpected := newReadFilters() | ||
rfExpected.AddCondition(newQueryCondition("name", "is", "blorg")) | ||
|
||
suite.Equal(nil, err, "Error should be nil") | ||
suite.Equal(rfExpected, rf, "Should be the same") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementBasicAnd() { | ||
|
||
rf, err := parseQuery(`and(["name", "is", "blorg"])`) | ||
|
||
rfExpected := newReadFilters() | ||
rfExpected.AddCondition(newQueryCondition("name", "is", "blorg")) | ||
|
||
suite.Equal(nil, err, "Error should be nil") | ||
suite.Equal(rfExpected, rf, "Should be the same") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementBasicAndUpper() { | ||
rf, err := parseQuery(`AND(["name", "is", "blorg"])`) | ||
|
||
rfExpected := newReadFilters() | ||
rfExpected.AddCondition(newQueryCondition("name", "is", "blorg")) | ||
|
||
suite.Equal(nil, err, "Error should be nil") | ||
suite.Equal(rfExpected, rf, "Should be the same") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementBasicOr() { | ||
rf, err := parseQuery(`or(["name", "is", "blorg"])`) | ||
|
||
rfExpected := newReadFilters() | ||
rfExpected.LogicalOperator = "or" | ||
rfExpected.AddCondition(newQueryCondition("name", "is", "blorg")) | ||
|
||
suite.Equal(nil, err, "Error should be nil") | ||
suite.Equal(rfExpected, rf, "Should be the same") | ||
} | ||
|
||
func (suite *Format1TestSuite) TestParseQueryAndOrStatementBasicOrUpper() { | ||
rf, err := parseQuery(`OR(["name", "is", "blorg"])`) | ||
|
||
rfExpected := newReadFilters() | ||
rfExpected.LogicalOperator = "or" | ||
rfExpected.AddCondition(newQueryCondition("name", "is", "blorg")) | ||
|
||
suite.Equal(nil, err, "Error should be nil") | ||
suite.Equal(rfExpected, rf, "Should be the same") | ||
} |
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,76 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
// Format2 satisfies the QueryParserI interface for format1 | ||
// from the sg-query spec | ||
type Format2 struct{} | ||
|
||
// CanParseString returns a boolean indicating whether or not the method can parse the supplied string. It is not a guarantee that parsing will be successful. | ||
func (f *Format2) CanParseString(queryStr string) bool { | ||
if !strings.HasPrefix(queryStr, "{") { | ||
return false | ||
} | ||
// Format: {"logical_operator": "and", "conditions": [[key, comparitor, value], ...]} | ||
var queryData map[string]interface{} | ||
err := json.Unmarshal([]byte(queryStr), &queryData) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
if _, ok := queryData["logical_operator"]; !ok { | ||
return false | ||
} | ||
|
||
if _, ok := queryData["conditions"]; !ok { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// ParseString parses the supplied string and returns readFilters and an error | ||
func (f *Format2) ParseString(queryStr string) (readFilters, error) { | ||
query := newReadFilters() | ||
if strings.HasPrefix(queryStr, "{") { | ||
// Format: {"logical_operator": "and", "conditions": [[key, comparitor, value], ...]} | ||
var queryData map[string]interface{} | ||
err := json.Unmarshal([]byte(queryStr), &queryData) | ||
if err != nil { | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: err.Error(), | ||
} | ||
} | ||
|
||
if _, ok := queryData["logical_operator"]; !ok { | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "Missing key: 'logical_operator'", | ||
} | ||
} | ||
|
||
if _, ok := queryData["conditions"]; !ok { | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "Missing key: 'conditions'", | ||
} | ||
} | ||
err = mapToReadFilters(queryData, &query) | ||
|
||
return query, err | ||
} | ||
return query, queryParseError{ | ||
StatusCode: http.StatusBadRequest, | ||
Message: "Missing Prefix: '{'", | ||
} | ||
} | ||
|
||
// Register the format with the manager | ||
func init() { | ||
manager := GetQPManager() | ||
manager.AddParser("format2", &Format2{}) | ||
} |
Oops, something went wrong.