Skip to content

Commit

Permalink
feat(service): add list skip / CEL filters, test db (#60)
Browse files Browse the repository at this point in the history
To provide an illustrative example of CEL's usage as a filter language,
implement it, along with filtering and sending data along to a functional database
(in this case, sqlite).

A small example cel2sql parser was written to illustrate the ability to use
CEL as a query language. However, this will be replaced by some cel -> sql parser,
hopefully contributed as a more general library and dependency.

Example:

```
curl $'http://localhost:8081/publishers?filter=description.startsWith(\'tomorrow\')'
{"results":[{"description":"tomorrow they will write again","path":"publishers/foo"}],"nextPageToken":""}
```

Fixes #59
  • Loading branch information
toumorokoshi authored Jan 13, 2025
1 parent a521231 commit 6fa792d
Show file tree
Hide file tree
Showing 19 changed files with 1,455 additions and 375 deletions.
409 changes: 215 additions & 194 deletions example/bookstore/v1/bookstore.pb.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions example/bookstore/v1/bookstore.proto
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,12 @@ message ListPublishersRequest {

// The maximum number of resources to return in a single page.
int32 max_page_size = 10017;

// The number of resources to skip before returning the first resource in the page.
int32 skip = 10021;

// The filter to apply to the list.
string filter = 10022;
}

// Response message for the Listpublisher method
Expand Down
15 changes: 15 additions & 0 deletions example/bookstore/v1/bookstore.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,21 @@
"required": false,
"type": "integer",
"format": "int32"
},
{
"name": "skip",
"description": "The number of resources to skip before returning the first resource in the page.",
"in": "query",
"required": false,
"type": "integer",
"format": "int32"
},
{
"name": "filter",
"description": "The filter to apply to the list.",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
Expand Down
4 changes: 3 additions & 1 deletion example/bookstore/v1/bookstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ resources:
read: {}
update: {}
delete: {}
list: {}
list:
supports_filter: true
supports_skip: true
apply: {}
# example of a child resource
- kind: "book"
Expand Down
14 changes: 14 additions & 0 deletions example/bookstore/v1/bookstore_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@
"schema": {
"type": "string"
}
},
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer"
}
},
{
"name": "filter",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
Expand Down
8 changes: 8 additions & 0 deletions example/bookstore/v1/bookstore_openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ paths:
name: page_token
schema:
type: string
- in: query
name: skip
schema:
type: integer
- in: query
name: filter
schema:
type: string
responses:
"200":
content:
Expand Down
29 changes: 29 additions & 0 deletions example/service/cel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package service

import (
"github.com/aep-dev/aepc/pkg/cel2ansisql"
"github.com/google/cel-go/cel"
)

func convertCELToSQL(expr string) (string, error) {
if expr == "" {
return "", nil
}
env, err := cel.NewEnv(
cel.Variable("path", cel.StringType),
cel.Variable("description", cel.StringType),
)
if err != nil {
return "", err
}
ast, iss := env.Compile(expr)
if iss.Err() != nil {
return "", iss.Err()
}

sql, err := cel2ansisql.ConvertToSQL(ast)
if err != nil {
return "", err
}
return sql, nil
}
36 changes: 36 additions & 0 deletions example/service/cel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package service

import (
"testing"
)

func TestCELToSQL(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "simple expression",
input: "description.startsWith('tomorrow')",
expected: "description LIKE CONCAT('tomorrow', '%')",
},
{
name: "empty",
input: "",
expected: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := convertCELToSQL(tt.input)
if err != nil {
t.Errorf("convertCELToSQL() = %v, want %v", err, tt.expected)
}
if got != tt.expected {
t.Errorf("convertCELToSQL() = %v, want %v", got, tt.expected)
}
})
}
}
Loading

0 comments on commit 6fa792d

Please sign in to comment.