Skip to content

Commit

Permalink
feat(issue1): supporting primitive ObjectID from mongodb driver
Browse files Browse the repository at this point in the history
  • Loading branch information
ajclopez committed Jun 23, 2024
1 parent 6c9632f commit 82b3dcf
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 97 deletions.
138 changes: 87 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@

### Content index

* [What is this?](#what-is-this)
* [Getting Started](#getting-started)
* [Installation](#installation)
* [Usage](#usage)
* [Supported features](#supported-features)
* [Filtering](#filtering)
* [Pagination](#pagination)
* [Sorting](#sorting)
* [Projection](#projection)
* [Advanced queries](#advanced-queries)
* [Available options](#available-options)
* [Customize limit value](#customize-limit-value)
* [Specify casting per param keys](#specify-casting-per-param-keys)
* [Contributing](#contributing)
* [License](#license)
- [What is this?](#what-is-this)
- [Getting Started](#getting-started)
- [Installation](#installation)
- [Usage](#usage)
- [Supported features](#supported-features)
- [Filtering](#filtering)
- [Pagination](#pagination)
- [Sorting](#sorting)
- [Projection](#projection)
- [Advanced queries](#advanced-queries)
- [Available options](#available-options)
- [Customize limit value](#customize-limit-value)
- [Specify casting per param keys](#specify-casting-per-param-keys)
- [Contributing](#contributing)
- [License](#license)

## What is this?

Expand All @@ -54,13 +54,30 @@ go get github.com/ajclopez/mgs

## Usage

To get started with the mgs, import the mgs package and use a mgs.MongoGoSearch function:
To get started with mgs, import the mgs package and implement the Primitives ObjectID function which is used to convert strings to ObjectID.

```go
mgs.MongoGoSearch(query string, opts *FindOptions)
type Primitives struct{}

func (primitives *Primitives) ObjectID(oidStr string) (interface{}, error) {
return ObjectID() // invoke ObjectID from MongoDB Driver
}
```

Then create an instance of `QueryHandler`:

```go
queryHandler := mgs.NewQueryHandler(&Primitives{})
```

Finally use a mgs.MongoGoSearch function:

```go
queryHandler.MongoGoSearch(query string, opts *FindOptions)
```

##### Arguments

`query`: query string part of the requested API URL.

`opts`: object for advanced configuration [See below](#available-options) [optional].
Expand All @@ -72,11 +89,20 @@ import (
"context"

"github.com/ajclopez/mgs"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)

type Primitives struct{}

func (primitives *Primitives) ObjectID(oidStr string) (interface{}, error) {
return primitive.ObjectIDFromHex(oidStr)
}

queryHandler := mgs.NewQueryHandler(&Primitives{})

opts := mgs.FindOption()
result, err := mgs.MongoGoSearch(query, opts)
result, err := queryHandler.MongoGoSearch(query, opts)

...

Expand All @@ -98,16 +124,25 @@ import (
"context"

"github.com/ajclopez/mgs"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)

type Primitives struct{}

func (primitives *Primitives) ObjectID(oidStr string) (interface{}, error) {
return primitive.ObjectIDFromHex(oidStr)
}

queryHandler := mgs.NewQueryHandler(&Primitives{})

opts := mgs.FindOption()
opts.SetCaster(map[string]mgs.CastType{
"mobile": mgs.STRING,
})
opts.SetMaxLimit(1000)
opts.SetDefaultLimit(10)
result, err := mgs.MongoGoSearch(query, opts)
result, err := queryHandler.MongoGoSearch(query, opts)

...

Expand All @@ -125,11 +160,13 @@ cur, err := collection.Find(context.TODO(), result.Filter, findOpts)
##### Example

A request of the form:

```js
"employees?status=sent&date>2020-01-06T14:00:00.000Z&author.firstname=Jhon&skip=50&limit=100&sort=-date&fields=id,date";
'employees?status=sent&date>2020-01-06T14:00:00.000Z&author.firstname=Jhon&skip=50&limit=100&sort=-date&fields=id,date';
```

Is translated to:

```Go
Query{
Filter: map[string]interface{}{
Expand All @@ -150,21 +187,20 @@ Query{

### Filtering

| Operator | URI | Example |
| ----------------- | --------------------- | --------------------------------- |
| `$eq` | `key=val` | `type=public` |
| `$ne` | `key!=val` | `status!=SENT` |
| `$gt` | `key>val` | `price>5` |
| `$gte` | `key>=val` | `price>=9` |
| `$lt` | `key<val` | `date<2020-01-01T14:00:00.000Z` |
| `$lte` | `key<=val` | `priority<=-5` |
| `$in` | `key=val1,val2` | `status=QUEUED,DEQUEUED` |
| `$nin` | `key!=val1,val2` | `status!=QUEUED,DEQUEUED` |
| `$exists` | `key` | `email` |
| `$exists` | `!key` | `!email` |
| `$regex` | `key=/value/<opts>` | `email=/@gmail\.com$/` |
| `$regex` | `key!=/value/<opts>` | `phone!=/^58/` |

| Operator | URI | Example |
| --------- | -------------------- | ------------------------------- |
| `$eq` | `key=val` | `type=public` |
| `$ne` | `key!=val` | `status!=SENT` |
| `$gt` | `key>val` | `price>5` |
| `$gte` | `key>=val` | `price>=9` |
| `$lt` | `key<val` | `date<2020-01-01T14:00:00.000Z` |
| `$lte` | `key<=val` | `priority<=-5` |
| `$in` | `key=val1,val2` | `status=QUEUED,DEQUEUED` |
| `$nin` | `key!=val1,val2` | `status!=QUEUED,DEQUEUED` |
| `$exists` | `key` | `email` |
| `$exists` | `!key` | `!email` |
| `$regex` | `key=/value/<opts>` | `email=/@gmail\.com$/` |
| `$regex` | `key!=/value/<opts>` | `phone!=/^58/` |

### Pagination

Expand Down Expand Up @@ -203,22 +239,23 @@ fields=firstname,lastname,phone,email
```

**Note:**
* The `_id` field (returned by default).

- The `_id` field (returned by default).

### Advanced queries

For more advanced usage (`and`, `or` logic operations), pass query `filter` as string with the logical operations, for example:

```json
filter=(country=Mexico OR country=Spain) and gender=female
````
```

##### What operations are possible?

* Filtering operations.
* The `AND/and` operator.
* The `OR/or` operator.
* Parenthesis can be used for grouping.
- Filtering operations.
- The `AND/and` operator.
- The `OR/or` operator.
- Parenthesis can be used for grouping.

## Available options

Expand All @@ -233,17 +270,17 @@ opts.SetMaxLimit(100)
opts.SetDefaultLimit(10)
```

* `FindOption` creates a new FindOptions instance.
* `SetCaster` object to specify custom casters, key is the caster name, and value is a type (`BOOLEAN, NUMBER, PATTERN, DATE, STRING`).
* `SetDefaultLimit` which contains custom value to return records.
* `SetMaxLimit` which contains custom value to return a maximum of records.
- `FindOption` creates a new FindOptions instance.
- `SetCaster` object to specify custom casters, key is the caster name, and value is a type (`BOOLEAN, NUMBER, PATTERN, DATE, STRING`).
- `SetDefaultLimit` which contains custom value to return records.
- `SetMaxLimit` which contains custom value to return a maximum of records.

### Customize limit value

You can specify your own maximum or default limit value.

* `defaultLimit`: custom value to return records.
* `maxLimit`: custom value to return a maximum of records.
- `defaultLimit`: custom value to return records.
- `maxLimit`: custom value to return a maximum of records.

```go
opts := mgs.FindOption()
Expand All @@ -257,7 +294,7 @@ result, err := mgs.MongoGoSearch("city=Madrid&skip=10&limit=1000", opts)

You can specify how query parameter values are casted by passing an object.

* `casters`: object which map keys to casters.
- `casters`: object which map keys to casters.

```go
opts := mgs.FindOption()
Expand Down Expand Up @@ -285,6 +322,5 @@ Should you like to provide any feedback, please open up an Issue, I appreciate f

This software is released under the MIT license. See `LICENSE` for more information.


[license-shield]: https://img.shields.io/badge/License-MIT-yellow.svg
[license-url]: https://github.com/ajclopez/mgs/blob/master/LICENSE
4 changes: 2 additions & 2 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
)

// Convert converts the criteria value to a MongoDB query
func Convert(criteria SearchCriteria) map[string]interface{} {
func Convert(criteria SearchCriteria, qh *QueryHandler) map[string]interface{} {

value := ParseValue(criteria.Value, criteria.Caster)
value := ParseValue(criteria.Value, qh, criteria.Caster)
filter := make(map[string]interface{})

switch criteria.Operation {
Expand Down
2 changes: 1 addition & 1 deletion convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ var converetTests = []struct {

func TestShouldConvertFromSearchCriteria(t *testing.T) {
for _, test := range converetTests {
result := Convert(test.Criteria)
result := Convert(test.Criteria, queryHandler)
assert.Equal(t, test.Expected, result)
}
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/ajclopez/mgs
go 1.21.2

require (
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10
github.com/antlr4-go/antlr/v4 v4.13.0
github.com/stretchr/testify v1.8.4
)
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down
17 changes: 12 additions & 5 deletions mgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@ var (
ErrQueryVisitor = errors.New("context does not exists")
)

// NewQueryHandler creates a new QueryHandler instance.
func NewQueryHandler(primitives Primitives) *QueryHandler {
return &QueryHandler{
Primitives: primitives,
}
}

// MongoGoSearch converts query into a MongoDB query object.
func MongoGoSearch(query string, opts *FindOptions) (Query, error) {
func (qh *QueryHandler) MongoGoSearch(query string, opts *FindOptions) (Query, error) {

res := Query{}

Expand All @@ -41,7 +48,7 @@ func MongoGoSearch(query string, opts *FindOptions) (Query, error) {
for _, criteria := range Parse(query, opts.Caster) {
switch criteria.Key {
case "filter":
filterAdvanced = parseFilterAdvanced(criteria.Value, opts.Caster)
filterAdvanced = parseFilterAdvanced(criteria.Value, qh, opts.Caster)
case "skip":
err = parseSkip(&res, criteria.Value)
case "limit":
Expand All @@ -51,7 +58,7 @@ func MongoGoSearch(query string, opts *FindOptions) (Query, error) {
case "fields":
parseFields(&res, criteria.Value)
default:
filters = append(filters, Convert(criteria))
filters = append(filters, Convert(criteria, qh))
}

if err != nil {
Expand Down Expand Up @@ -137,13 +144,13 @@ func parseFields(res *Query, value string) {
res.Projection = projection
}

func parseFilterAdvanced(value string, caster *map[string]CastType) map[string][]interface{} {
func parseFilterAdvanced(value string, qh *QueryHandler, caster *map[string]CastType) map[string][]interface{} {

is := antlr.NewInputStream(value)
lexer := parser.NewQueryLexer(is)
tokens := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
parser := parser.NewQueryParser(tokens)
visitor := NewGeneratorVisitor(caster)
visitor := NewGeneratorVisitor(qh, caster)

tree := parser.Input()
result := visitor.Visit(tree)
Expand Down
Loading

0 comments on commit 82b3dcf

Please sign in to comment.