Skip to content

Commit

Permalink
Add support for $regex and $options in where search as request here p…
Browse files Browse the repository at this point in the history
…ull/14
  • Loading branch information
EmilianoBruni committed Feb 11, 2024
1 parent 805d530 commit bae0d05
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 35 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,20 @@ Pass mongo where object as `where` property JSON-encoded string and it will be a

Plugin uses simple sanitation, list of allowed operators:
```javascript
'$eq', '$gt', '$gte', '$in', '$lt', '$lte', '$ne', '$nin', '$and', '$not', '$nor', '$or', '$exists'
'$eq', '$gt', '$gte', '$in', '$lt', '$lte', '$ne', '$nin', '$and', '$not', '$nor', '$or', '$exists', '$regex', '$options'
```
For `$regex/$options` it's supported only the

```json
{ "<field>": { "$regex": "pattern", "$options": "<options>" } }
```

[syntax](https://www.mongodb.com/docs/manual/reference/operator/query/regex/#syntax). Using

```json
{ "<field>": { "$regex": /pattern/, ...
```
syntax it's not supported and produce an error.

See [Mongo operators docs](https://www.mongodb.com/docs/manual/reference/operator/query/#query-and-projection-operators)
And plugin [test case](https://github.com/jeka-kiselyov/fastify-mongoose-api/blob/master/test/complex_where.test.js)
Expand Down
6 changes: 4 additions & 2 deletions src/APIRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ class APIRouter {
'$not',
'$nor',
'$or',
'$exists'
'$exists',
'$regex',
'$options'
];
const sanitize = function (v) {
if (v instanceof Object) {
Expand All @@ -194,7 +196,7 @@ class APIRouter {
/^\$/.test(key) &&
allowedMethods.indexOf(key) === -1
) {
delete v[key];
throw new Error('Invalid where method: ' + key);
} else {
sanitize(v[key]);
}
Expand Down
120 changes: 88 additions & 32 deletions test/complex_where.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,17 @@ test('initialization of API server', async t => {
);
});

const bob = { name: 'Bob', appleCount: 1, bananaCount: 2 };
const rob = { name: 'Rob', appleCount: 2, bananaCount: 3 };
const alice = { name: 'Alice', appleCount: 50, bananaCount: 90 };

test('POST item test', async t => {
let response = null;

response = await fastify.inject({
method: 'POST',
url: '/api/wheretests',
payload: { name: 'Bob', appleCount: 1, bananaCount: 2 }
payload: bob
});

t.equal(response.statusCode, 200, 'POST api ok');
Expand All @@ -73,16 +78,12 @@ test('POST item test', async t => {
'Content-Type is correct'
);

t.match(
response.json(),
{ name: 'Bob', appleCount: 1, bananaCount: 2 },
'POST api ok'
);
t.match(response.json(), bob, 'POST api ok');

response = await fastify.inject({
method: 'POST',
url: '/api/wheretests',
payload: { name: 'Rob', appleCount: 2, bananaCount: 3 }
payload: rob
});

t.equal(response.statusCode, 200, 'POST api ok');
Expand All @@ -92,16 +93,12 @@ test('POST item test', async t => {
'Content-Type is correct'
);

t.match(
response.json(),
{ name: 'Rob', appleCount: 2, bananaCount: 3 },
'POST api ok'
);
t.match(response.json(), rob, 'POST api ok');

response = await fastify.inject({
method: 'POST',
url: '/api/wheretests',
payload: { name: 'Alice', appleCount: 50, bananaCount: 90 }
payload: alice
});

t.equal(response.statusCode, 200, 'POST api ok');
Expand All @@ -111,11 +108,7 @@ test('POST item test', async t => {
'Content-Type is correct'
);

t.match(
response.json(),
{ name: 'Alice', appleCount: 50, bananaCount: 90 },
'POST api ok'
);
t.match(response.json(), alice, 'POST api ok');

response = await fastify.inject({
method: 'GET',
Expand Down Expand Up @@ -150,11 +143,7 @@ test('GET collection complex where', async t => {

t.equal(response.json().total, 1, 'API returns 1 filtered');
t.equal(response.json().items.length, 1, 'API returns 1 filtered');
t.match(
response.json().items[0],
{ name: 'Bob', appleCount: 1, bananaCount: 2 },
'Filtered'
);
t.match(response.json().items[0], bob, 'Filtered');

response = await fastify.inject({
method: 'GET',
Expand All @@ -171,11 +160,7 @@ test('GET collection complex where', async t => {

t.equal(response.json().total, 1, 'API returns 1 filtered');
t.equal(response.json().items.length, 1, 'API returns 1 filtered');
t.match(
response.json().items[0],
{ name: 'Alice', appleCount: 50, bananaCount: 90 },
'Filtered'
);
t.match(response.json().items[0], alice, 'Filtered');

response = await fastify.inject({
method: 'GET',
Expand All @@ -196,11 +181,82 @@ test('GET collection complex where', async t => {

t.equal(response.json().total, 1, 'API returns 1 filtered');
t.equal(response.json().items.length, 1, 'API returns 1 filtered');
t.match(
response.json().items[0],
{ name: 'Rob', appleCount: 2, bananaCount: 3 },
'Filtered'
t.match(response.json().items[0], rob, 'Filtered');

// invalid where
response = await fastify.inject({
method: 'GET',
url: '/api/wheretests',
query: {
where: JSON.stringify({
name: { $nonvalid: false }
})
}
});
t.equal(response.statusCode, 500, 'API returns 500 status code');
t.equal(response.json().message, 'Invalid where method: $nonvalid');

// $regex
response = await fastify.inject({
method: 'GET',
url: '/api/wheretests',
query: {
where: JSON.stringify({
name: { $regex: '^A' }
})
}
});

t.equal(response.statusCode, 200, 'API returns 200 status code');
t.equal(
response.headers['content-type'],
'application/json; charset=utf-8',
'API returns correct content type'
);

t.equal(response.json().total, 1, 'API returns 1 filtered');
t.equal(response.json().items.length, 1, 'API returns 1 filtered');
t.match(response.json().items[0], alice, 'Filtered');

response = await fastify.inject({
method: 'GET',
url: '/api/wheretests',
query: {
where: JSON.stringify({
name: { $regex: '^a' }
})
}
});

t.equal(response.statusCode, 200, 'API returns 200 status code');
t.equal(
response.headers['content-type'],
'application/json; charset=utf-8',
'API returns correct content type'
);

t.equal(response.json().total, 0, 'API returns 0 filtered');

response = await fastify.inject({
method: 'GET',
url: '/api/wheretests',
query: {
where: JSON.stringify({
name: { $regex: '^a', $options: 'i' }
})
}
});

t.equal(response.statusCode, 200, 'API returns 200 status code');
t.equal(
response.headers['content-type'],
'application/json; charset=utf-8',
'API returns correct content type'
);

t.equal(response.json().total, 1, 'API returns 1 filtered');
t.equal(response.json().items.length, 1, 'API returns 1 filtered');
t.match(response.json().items[0], alice, 'Filtered');
});

test('teardown', async () => {
Expand Down

0 comments on commit bae0d05

Please sign in to comment.