Extract rql package

This commit is contained in:
Anton Zadvorny 2024-05-12 07:04:35 +03:00
commit 00c6309659
5 changed files with 203 additions and 0 deletions

42
.golangci.yml Normal file
View 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
View 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
View 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
View 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
View 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)
}
})
}
}