-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: convert string to mongodb query
- convert query into a MongoDB query object - add optional options to customize the query - update README - add github action
- Loading branch information
Showing
18 changed files
with
1,234 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Go | ||
|
||
on: | ||
push: | ||
branches: [ main ] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Setup Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.21.x' | ||
- name: Install dependencies | ||
run: go get . | ||
- name: Build | ||
run: go build -v ./... | ||
- name: Test & prepare coverage | ||
run: go test -coverprofile=coverage.out | ||
- name: Upload coverage to Codecov | ||
uses: codecov/codecov-action@v3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,136 @@ | ||
# mgs | ||
Convert URL query parameters to MongoDB queries | ||
[![MIT License][license-shield]][license-url] | ||
|
||
<p align="center"> | ||
<h3 align="center">Mongo Golang Search</h3> | ||
<p align="center"> | ||
Mongo Golang Search provides a query language to a MongoDB database. | ||
<br /> | ||
<a href="https://github.com/ajclopez/mgs#usage"><strong>Explore the docs</strong></a> | ||
<br /> | ||
<br /> | ||
<a href="https://github.com/ajclopez/mgs/issues">Report Bug</a> | ||
· | ||
<a href="https://github.com/ajclopez/mgs/issues">Request Feature</a> | ||
</p> | ||
</p> | ||
|
||
# Mongo Golang Search (mgs) | ||
|
||
|
||
### 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) | ||
* [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? | ||
|
||
Mongo Golang Search provides a simple query language to perform advanced searches for your collections in **MongoDB**. | ||
|
||
You could also use **Mongo Golang Search** to searching, sorting, pagination and combining logical operators. | ||
|
||
## Getting Started | ||
|
||
### Installation | ||
|
||
|
||
## Usage | ||
|
||
|
||
## Supported features | ||
|
||
### 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/` | | ||
|
||
|
||
### Pagination | ||
|
||
Useful to limit the number of records returned. | ||
|
||
- Operator keys are `skip` and `limit`. | ||
- Use `limit` operator to limit the number of records returned. | ||
- Use `skip` operator to skip the specified number of records. | ||
|
||
```json | ||
skip=20&limit=10 | ||
``` | ||
|
||
### Sorting | ||
|
||
Useful to sort returned records. | ||
|
||
- Operator key is `sort`. | ||
- It accepts a comma-separated list of fields. | ||
- Use `-` prefixes to sort in descending order. | ||
- Use `+` prefixes to sort in ascedending order. | ||
|
||
```json | ||
sort=id,-date | ||
``` | ||
|
||
### Projection | ||
|
||
Useful to limit fields to return in each records. | ||
|
||
- Operator key is `fields`. | ||
- It accepts a comma-separated list of fields. | ||
|
||
```json | ||
fields=firstname,lastname,phone,email | ||
``` | ||
|
||
**Note:** | ||
* The `_id` field (returned by default). | ||
|
||
## Available options | ||
|
||
You can use advanced options: | ||
|
||
### Customize limit value | ||
|
||
|
||
### Specify casting per param keys | ||
|
||
|
||
## Contributing | ||
|
||
Should you like to provide any feedback, please open up an Issue, I appreciate feedback and comments. Any contributions you make are **greatly appreciated**. | ||
|
||
1. Fork the Project | ||
2. Create your feature branch (`git checkout -b feature/amazing-feature`) | ||
3. Commit your changes (`git commit -m 'Add some amazing-feature'`) | ||
4. Push to the branch (`git push origin feature/amazing-feature`) | ||
5. Open a Pull Request | ||
|
||
## License | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package mgs | ||
|
||
import ( | ||
"reflect" | ||
) | ||
|
||
// Convert converts the criteria value to a MongoDB query | ||
func Convert(criteria SearchCriteria, filter map[string]interface{}) { | ||
|
||
value := ParseValue(criteria.Value, criteria.Caster) | ||
|
||
switch criteria.Operation { | ||
case EQUAL: | ||
key := reflect.ValueOf(value).Kind() | ||
if key == reflect.Slice { | ||
filter[criteria.Key] = buildMongoQuery("$in", value) | ||
} else if key == reflect.Struct { | ||
filter[criteria.Key] = buildRegexOperation(value) | ||
} else { | ||
filter[criteria.Key] = value | ||
} | ||
case NOT_EQUAL: | ||
key := reflect.ValueOf(value).Kind() | ||
if key == reflect.Slice { | ||
filter[criteria.Key] = buildMongoQuery("$nin", value) | ||
} else if key == reflect.Struct { | ||
filter[criteria.Key] = buildMongoQuery("$not", buildRegexOperation(value)) | ||
} else { | ||
filter[criteria.Key] = buildMongoQuery("$ne", value) | ||
} | ||
case GREATER_THAN: | ||
filter[criteria.Key] = buildMongoQuery("$gt", value) | ||
case GREATER_THAN_EQUAL: | ||
filter[criteria.Key] = buildMongoQuery("$gte", value) | ||
case LESS_THAN: | ||
filter[criteria.Key] = buildMongoQuery("$lt", value) | ||
case LESS_THAN_EQUAL: | ||
filter[criteria.Key] = buildMongoQuery("$lte", value) | ||
case EXISTS: | ||
filter[criteria.Key] = buildMongoQuery("$exists", !criteria.Prefix) | ||
} | ||
} | ||
|
||
func buildMongoQuery(operator string, value interface{}) map[string]interface{} { | ||
query := make(map[string]interface{}) | ||
query[operator] = value | ||
|
||
return query | ||
} | ||
|
||
func buildRegexOperation(value interface{}) map[string]interface{} { | ||
regex := make(map[string]interface{}) | ||
regex["$regex"] = value.(Regex).Pattern | ||
regex["$options"] = value.(Regex).Option | ||
|
||
return regex | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package mgs | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var converetTests = []struct { | ||
Criteria SearchCriteria | ||
Filter map[string]interface{} | ||
Expected map[string]interface{} | ||
}{ | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "name", | ||
Operation: EQUAL, | ||
Value: "Jhon", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"name": "Jhon"}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "status", | ||
Operation: EQUAL, | ||
Value: "QUEUED,DEQUEUED", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"status": map[string]interface{}{"$in": []interface{}{"QUEUED", "DEQUEUED"}}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "email", | ||
Operation: EQUAL, | ||
Value: "/@gmail\\.com$/", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"email": map[string]interface{}{"$regex": "@gmail\\.com$", "$options": ""}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "status", | ||
Operation: NOT_EQUAL, | ||
Value: "SENT", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"status": map[string]interface{}{"$ne": "SENT"}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "status", | ||
Operation: NOT_EQUAL, | ||
Value: "QUEUED,DEQUEUED", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"status": map[string]interface{}{"$nin": []interface{}{"QUEUED", "DEQUEUED"}}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "phone", | ||
Operation: NOT_EQUAL, | ||
Value: "/^58/", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"phone": map[string]interface{}{"$not": map[string]interface{}{"$regex": "^58", "$options": ""}}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "price", | ||
Operation: GREATER_THAN, | ||
Value: "5", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"price": map[string]interface{}{"$gt": int64(5)}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "price", | ||
Operation: GREATER_THAN_EQUAL, | ||
Value: "5", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"price": map[string]interface{}{"$gte": int64(5)}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "price", | ||
Operation: LESS_THAN, | ||
Value: "5", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"price": map[string]interface{}{"$lt": int64(5)}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: false, | ||
Key: "price", | ||
Operation: LESS_THAN_EQUAL, | ||
Value: "5", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"price": map[string]interface{}{"$lte": int64(5)}}, | ||
}, | ||
{ | ||
SearchCriteria{ | ||
Prefix: true, | ||
Key: "email", | ||
Operation: EXISTS, | ||
Value: "", | ||
}, | ||
map[string]interface{}{}, | ||
map[string]interface{}{"email": map[string]interface{}{"$exists": false}}, | ||
}, | ||
} | ||
|
||
func TestShouldConvertFromSearchCriteria(t *testing.T) { | ||
for _, test := range converetTests { | ||
Convert(test.Criteria, test.Filter) | ||
assert.Equal(t, test.Expected, test.Filter) | ||
} | ||
} |
Oops, something went wrong.