Skip to content

Commit

Permalink
Upgrade to Goyave v5
Browse files Browse the repository at this point in the history
  • Loading branch information
System-Glitch committed Aug 3, 2023
1 parent 7663b32 commit f4d0fee
Show file tree
Hide file tree
Showing 21 changed files with 725 additions and 608 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.17", "1.18", "1.19", "1.20"]
go: ["1.20"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
Expand All @@ -23,7 +23,7 @@ jobs:
- name: Run tests
run: |
go test -v -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... ./...
- if: ${{ matrix.go == 1.20 }}
- if: ${{ matrix.go == '1.20' }}
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: coverage.txt
Expand All @@ -36,5 +36,5 @@ jobs:
- name: Run lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.52
version: v1.53
args: --timeout 5m
1 change: 1 addition & 0 deletions .golangci.yml
Expand Up @@ -37,3 +37,4 @@ issues:
max-same-issues: 0
exclude:
- should have a package comment
- type name will be used as filter.FilterValidator by other packages, and that stutters; consider calling this Validator
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Jérémy LAMBERT (SystemGlitch)
Copyright (c) 2023 Jérémy LAMBERT (SystemGlitch)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
94 changes: 57 additions & 37 deletions README.md
Expand Up @@ -5,7 +5,7 @@
[![Coverage Status](https://coveralls.io/repos/github/go-goyave/filter/badge.svg)](https://coveralls.io/github/go-goyave/filter)
[![Go Reference](https://pkg.go.dev/badge/goyave.dev/filter.svg)](https://pkg.go.dev/goyave.dev/filter)

**Compatible with Goyave `v4` only**
**Compatible with Goyave `v5` only**. If you want to use the library for v4, use `v0.6.0`.

`goyave.dev/filter` allows powerful filtering using query parameters. Inspired by [nestjsx/crud](https://github.com/nestjsx/crud/wiki/Requests).

Expand All @@ -15,23 +15,9 @@
go get goyave.dev/filter
```

First, apply filters validation to the `RuleSet` used on the routes you wish the filters on.
```go
import "goyave.dev/filter"

//...


var (
IndexRequest = validation.RuleSet{}
)

func init() {
filter.ApplyValidation(IndexRequest)
}
```
```go
router.Get("/users", user.Index).Validate(user.IndexRequest)
router.GlobalMiddleware(&parse.Middleware{}) // Don't forget the parse middleware!
router.Get("/users", user.Index).ValidateQuery(filter.Validation)
```

Then implement your controller handler:
Expand All @@ -40,12 +26,16 @@ import "goyave.dev/filter"

//...

func Index(response *goyave.Response, request *goyave.Request) {
func (ctrl *UserController) Index(response *goyave.Response, request *goyave.Request) {
var users []*model.User
paginator, tx := filter.Scope(database.GetConnection(), request, &users)
if response.HandleDatabaseError(tx) {
response.JSON(http.StatusOK, paginator)
paginator, tx := filter.Scope(ctrl.DB(), request, &users)
if response.WriteDBError(tx.Error) {
return
}

// Convert to DTO and write response
dto := typeutil.MustConvert[database.PaginatorDTO[dto.User]](paginator)
response.JSON(http.StatusOK, dto)
}
```

Expand All @@ -54,18 +44,20 @@ And **that's it**! Now your front-end can add query parameters to filter as it w
You can also find records without paginating using `ScopeUnpaginated()`:
```go
var users []*model.User
tx := filter.ScopeUnpaginated(database.GetConnection(), request, &users)
if response.HandleDatabaseError(tx) {
response.JSON(http.StatusOK, users)
tx := filter.ScopeUnpaginated(ctrl.DB(), request, &users)
if response.WriteDBError(tx.Error) {
return
}
dto := typeutil.MustConvert[database.PaginatorDTO[dto.User]](paginator)
response.JSON(http.StatusOK, dto)
```

### Settings

You can disable certain features, or blacklist certain fields using `filter.Settings`:

```go
settings := &filter.Settings{
settings := &filter.Settings[*model.User]{
DisableFields: true, // Prevent usage of "fields"
DisableFilter: true, // Prevent usage of "filter"
DisableSort: true, // Prevent usage of "sort"
Expand All @@ -92,7 +84,8 @@ settings := &filter.Settings{
},
},
}
paginator, tx := settings.Scope(database.GetConnection(), request, &results)
results := []*model.User{}
paginator, tx := settings.Scope(ctrl.DB(), request, &results)
```

### Filter
Expand Down Expand Up @@ -298,12 +291,14 @@ type MyModel struct{
If you want to add static conditions (not automatically defined by the library), it is advised to group them like so:
```go
users := []model.User{}
db := database.GetConnection()
db := ctrl.DB()
db = db.Where(db.Session(&gorm.Session{NewDB: true}).Where("username LIKE ?", "%Miss%").Or("username LIKE ?", "%Ms.%"))
paginator, tx := filter.Scope(db, request, &users)
if response.HandleDatabaseError(tx) {
response.JSON(http.StatusOK, paginator)
if response.WriteDBError(tx.Error) {
return
}
dto := typeutil.MustConvert[database.PaginatorDTO[dto.User]](paginator)
response.JSON(http.StatusOK, dto)
```

### Custom operators
Expand All @@ -315,7 +310,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/schema"
"goyave.dev/filter"
"goyave.dev/goyave/v4/util/sqlutil"
"goyave.dev/goyave/v5/util/sqlutil"
)

// ...
Expand Down Expand Up @@ -352,9 +347,9 @@ filter.Operators["$eq"] = &filter.Operator{

Some database engines such as PostgreSQL provide operators for array operations (`@>`, `&&`, ...). You may encounter issue implementing these operators in your project because of GORM converting slices into records (`("a", "b")` instead of `{"a", "b"}`).

To fix this issue, you will have to implement your own variant of `ConvertArgsToSafeType` so it returns a **pointer** to a slice with a concrete type instead of `[]interface{}`. By sending a pointer to GORM, it won't try to render the slice itself and pass it directly to the underlying driver, which usually knows how to handle slices for the native types.
To fix this issue, you will have to implement your own variant of `ConvertArgsToSafeType` so it returns a **pointer** to a slice with a concrete type instead of `[]any`. By sending a pointer to GORM, it won't try to render the slice itself and pass it directly to the underlying driver, which usually knows how to handle slices for the native types.

**Example** (using generics with go 1.18+):
**Example** (using generics):
```go
type argType interface {
string | int64 | uint64 | float64 | bool
Expand Down Expand Up @@ -417,18 +412,43 @@ func convertArgsToSafeTypeArray[T argType](args []string, dataType filter.DataTy
Manual joins are supported and won't clash with joins that are automatically generated by the library. That means that if needed, you can write something like described in the following piece of code.

```go
func Index(response *goyave.Response, request *goyave.Request) {
func (ctrl *UserController) Index(response *goyave.Response, request *goyave.Request) {
var users []*model.User

db := database.GetConnection().Joins("Relation")
db := ctrl.DB().Joins("Relation")

paginator, tx := filter.Scope(db, request, &users)
if response.HandleDatabaseError(tx) {
response.JSON(http.StatusOK, paginator)
if response.WriteDBError(tx.Error) {
return
}

// Convert to DTO and write response
dto := typeutil.MustConvert[database.PaginatorDTO[dto.User]](paginator)
response.JSON(http.StatusOK, dto)
}
```

### Upgrading from Goyave v4 to v5

User parameter are expected to be in the `Query` now. Update validation on your routes:
```go
router.Get("/", ctrl.Index).ValidateQuery(filter.Validation)
```

If your route has custom query parameters as well, you can append the lib's validation:
```go
func IndexRequest(r *goyave.Request) v.RuleSet {
return append(v.RuleSet{
{Path: "foo", Rules: v.List{v.Int(), v.Min(1)}},
}, filter.Validation(r)...)
}
```

`*filter.Settings` now takes a generic parameter: a pointer to the target model. The slice given to the `Scope` method is expected to be a pointer to a slice of the same type.
```go
settings := &Settings[*model.User]{}
```

## License

`goyave.dev/filter` is MIT Licensed. Copyright (c) 2021 Jérémy LAMBERT (SystemGlitch)
`goyave.dev/filter` is MIT Licensed. Copyright (c) 2023 Jérémy LAMBERT (SystemGlitch)
7 changes: 4 additions & 3 deletions filter.go
Expand Up @@ -10,6 +10,7 @@ import (
)

// Filter structured representation of a filter query.
// The generic parameter is the type pointer type of the model.
type Filter struct {
Field string
Operator *Operator
Expand All @@ -18,8 +19,8 @@ type Filter struct {
}

// Scope returns the GORM scope to use in order to apply this filter.
func (f *Filter) Scope(settings *Settings, sch *schema.Schema) (func(*gorm.DB) *gorm.DB, func(*gorm.DB) *gorm.DB) {
field, s, joinName := getField(f.Field, sch, &settings.Blacklist)
func (f *Filter) Scope(blacklist Blacklist, sch *schema.Schema) (func(*gorm.DB) *gorm.DB, func(*gorm.DB) *gorm.DB) {
field, s, joinName := getField(f.Field, sch, &blacklist)
if field == nil {
return nil, nil
}
Expand Down Expand Up @@ -64,7 +65,7 @@ func (f *Filter) Scope(settings *Settings, sch *schema.Schema) (func(*gorm.DB) *

// Where applies a condition to given transaction, automatically taking the "Or"
// filter value into account.
func (f *Filter) Where(tx *gorm.DB, query string, args ...interface{}) *gorm.DB {
func (f *Filter) Where(tx *gorm.DB, query string, args ...any) *gorm.DB {
if f.Or {
return tx.Or(query, args...)
}
Expand Down

0 comments on commit f4d0fee

Please sign in to comment.