Extract rql package
This commit is contained in:
commit
00c6309659
42
.golangci.yml
Normal file
42
.golangci.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
lll:
|
||||||
|
line-length: 140
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- performance
|
||||||
|
- style
|
||||||
|
- experimental
|
||||||
|
disabled-checks:
|
||||||
|
- paramTypeCombine
|
||||||
|
- unnamedResult
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- dupl
|
||||||
|
- exportloopref
|
||||||
|
- gochecknoinits
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- gosec
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- prealloc
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- stylecheck
|
||||||
|
- typecheck
|
||||||
|
- unconvert
|
||||||
|
- unused
|
||||||
|
fast: false
|
||||||
|
disable-all: true
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module go.pkg.cx/rql
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/participle/v2 v2.1.1
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
18
go.sum
Normal file
18
go.sum
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
|
||||||
|
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||||
|
github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
|
||||||
|
github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
|
||||||
|
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||||
|
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
42
parser.go
Normal file
42
parser.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package rql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alecthomas/participle/v2"
|
||||||
|
"github.com/alecthomas/participle/v2/lexer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rqlLexer = lexer.MustSimple([]lexer.SimpleRule{
|
||||||
|
{Name: "whitespace", Pattern: `\s+`},
|
||||||
|
{Name: "String", Pattern: `"[^"]*"`},
|
||||||
|
{Name: "Ident", Pattern: `[a-zA-Z_][a-zA-Z_0-9]*`},
|
||||||
|
{Name: "Punct", Pattern: `[-[!@#$%^&*()+_={};':",.<>?/]|]`},
|
||||||
|
})
|
||||||
|
|
||||||
|
rqlParser = participle.MustBuild[Query](
|
||||||
|
participle.Lexer(rqlLexer),
|
||||||
|
participle.Unquote("String"),
|
||||||
|
participle.UseLookahead(2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Parse(query string) (*Query, error) {
|
||||||
|
return rqlParser.ParseString("", query)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Operator string
|
||||||
|
|
||||||
|
type Values struct {
|
||||||
|
Single *string `parser:"@String"`
|
||||||
|
List []string `parser:"| ( '[' @String (',' @String)* ']' )"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Predicate struct {
|
||||||
|
Key string `parser:"@Ident '.'"`
|
||||||
|
Operator Operator `parser:"@('eq'|'lt'|'lte'|'gt'|'gte'|'like'|'in')"`
|
||||||
|
Values Values `parser:"'=' @@"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Predicates []*Predicate `parser:"@@*"`
|
||||||
|
}
|
87
parser_test.go
Normal file
87
parser_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package rql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
one := "1"
|
||||||
|
john := "John"
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
query string
|
||||||
|
predicates []*Predicate
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid Query",
|
||||||
|
query: `id.eq="1"`,
|
||||||
|
predicates: []*Predicate{{Key: "id", Operator: "eq", Values: Values{Single: &one}}},
|
||||||
|
errMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple Predicates",
|
||||||
|
query: `id.eq="1" name.eq="John"`,
|
||||||
|
predicates: []*Predicate{{Key: "id", Operator: "eq", Values: Values{Single: &one}}, {Key: "name", Operator: "eq", Values: Values{Single: &john}}},
|
||||||
|
errMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "List Values",
|
||||||
|
query: `id.in=["1", "2", "3"]`,
|
||||||
|
predicates: []*Predicate{
|
||||||
|
{
|
||||||
|
Key: "id",
|
||||||
|
Operator: "in",
|
||||||
|
Values: Values{
|
||||||
|
List: []string{"1", "2", "3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty Query",
|
||||||
|
query: "",
|
||||||
|
predicates: nil,
|
||||||
|
errMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Whitespace in Query",
|
||||||
|
query: ` id . eq = "1" `,
|
||||||
|
predicates: []*Predicate{{Key: "id", Operator: "eq", Values: Values{Single: &one}}},
|
||||||
|
errMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing Key",
|
||||||
|
query: `.eq="1"`,
|
||||||
|
predicates: nil,
|
||||||
|
errMessage: `unexpected token "."`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Operator",
|
||||||
|
query: `id.invalid="1"`,
|
||||||
|
predicates: nil,
|
||||||
|
errMessage: `unexpected token "invalid"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Query",
|
||||||
|
query: "invalid_query",
|
||||||
|
predicates: nil,
|
||||||
|
errMessage: `unexpected token "<EOF>"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := Parse(tc.query)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.predicates, result.Predicates)
|
||||||
|
if tc.errMessage != "" {
|
||||||
|
assert.ErrorContains(t, err, tc.errMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user