Skip to content

Commit

Permalink
Merge branch 'any-operator' into 'master'
Browse files Browse the repository at this point in the history
/any/ operator flag

See merge request !1
  • Loading branch information
Mantas Zimnickas committed Aug 17, 2017
2 parents 46acd79 + 585b161 commit e625756
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 89 deletions.
32 changes: 30 additions & 2 deletions docs/qvarn-api-doc/050-api-overview.yarn
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,8 @@ pattern is as follows, assuming a resource `/foos`:
* `GET /foos/search/CLAUSE` --- do a search for individual resources
of type `foo`. The search clause and the result are described below.

The clause contains any number of the following search conditions,
concatented in the URL path, which must all match a resource for it to
The `CLAUSE` contains any number of the following search conditions,
concatenated in the URL path, which must all match a resource for it to
be included in the result:

* `/exact/KEY/VALUE` --- the resource matches if it has a field `KEY`
Expand Down Expand Up @@ -738,6 +738,17 @@ Note: We'll add more conditions as they're needed, but we'll try to
keep the conditions few, simple, and powerful, and rely on some
client side logic to refine the results.

Some operators listed above can be combined with `any` modifier. `any` modifier
expects `VALUE` to be a JSON + URL encoded list with one or more values in it:

* `GET /foos/search/any/CLAUSE`

For example: `/foos/search/any/exact/key/[1,2,3]`. This query would look for
resources where `key` is exactly equal to `1`, `2` or `3`.

Currently `any` modifier works only with `exact`, `startswith` and `contains`
operators. Trying to use `any` with other operators will give you an error.

Results can be sorted by one or more search keys:

* `/sort/KEY1/sort/KEY2`
Expand Down Expand Up @@ -1170,6 +1181,23 @@ Not equal.
AND result has key "resources", a list that does not contain {"id": "$ID2"}
AND result has key "resources", a list containing {"id": "$ID3"}

Any given value.

WHEN client GETs
... /contracts/search/any/exact/start_date/%5B%222013-03-13%22%2C%222014-03-13%22%5D
THEN HTTP status code is 200
AND result has key "resources", a list containing {"id": "$ID1"}
AND result has key "resources", a list that does not contain {"id": "$ID2"}
AND result has key "resources", a list containing {"id": "$ID3"}

WHEN client GETs
... /contracts/search/any/startswith/start_date/%5B%222014%22%5D
THEN HTTP status code is 200
AND result has key "resources", a list that does not contain {"id": "$ID1"}
AND result has key "resources", a list that does not contain {"id": "$ID2"}
AND result has key "resources", a list containing {"id": "$ID3"}


We create a new organisation and do a search for that organisation.

SCENARIO search an organisation
Expand Down
1 change: 1 addition & 0 deletions qvarn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
ReadOnlyStorage,
ItemDoesNotExist,
FieldNotInResource,
create_search_param,
)

from .restype_storage import (
Expand Down
50 changes: 49 additions & 1 deletion qvarn/list_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import urllib
import urlparse
import json

import bottle

Expand Down Expand Up @@ -191,6 +192,13 @@ def get_matching_items(self, search_criteria): # pragma: no cover
sort_params = []
limit = None
offset = None
search_any = False

any_opers = [
u'exact',
u'startswith',
u'contains',
]

opers = [
u'exact',
Expand All @@ -211,8 +219,20 @@ def get_matching_items(self, search_criteria): # pragma: no cover
matching_rule = part
search_field = criteria[i + 1]
search_value = criteria[i + 2]
search_param = (matching_rule, search_field, search_value)
if search_any:
try:
search_value = json.loads(search_value)
except ValueError as e:
raise BadAnySearchValue(error=str(e))
if not isinstance(search_value, list):
raise BadAnySearchValue(
error=u"%r is not a list" % search_value)
search_param = qvarn.create_search_param(
matching_rule, search_field, search_value,
any=search_any,
)
search_params.append(search_param)
search_any = False
i += 3
elif part == u'show_all':
show_params.append(part)
Expand Down Expand Up @@ -242,6 +262,16 @@ def get_matching_items(self, search_criteria): # pragma: no cover
if offset < 0:
raise BadOffsetValue(error="should be positive integer")
i += 2
elif part == u'any':
if (i + 1) >= len(criteria):
raise MissingAnyOperator()
elif criteria[i + 1] not in any_opers:
raise InvalidAnyOperator(
allowed_operators=', '.join(any_opers),
given_operator=criteria[i + 1],
)
search_any = True
i += 1
else:
raise BadSearchCondition()

Expand Down Expand Up @@ -399,3 +429,21 @@ class BadLimitValue(LimitError):
class BadOffsetValue(LimitError):

msg = u'Invalid OFFSET value: {error}.'


class BadAnySearchValue(qvarn.BadRequest):

msg = u"Can't parse ANY search value: {error}."


class InvalidAnyOperator(qvarn.BadRequest):

msg = (
u"Only one of {allowed_operators} operators can be used with /any/, "
u"got /{given_operator}/."
)


class MissingAnyOperator(qvarn.BadRequest):

msg = u"Operator was not provided for /any/ condition."
Loading

0 comments on commit e625756

Please sign in to comment.