from urllib.parse import urlparse, parse_qs
from fhirbug.exceptions import QueryValidationError
[docs]def generate_query_string(query):
'''
Convert a ``FhirRequestQuery`` back to a query string.
'''
url_queries = "&".join(
[
f"{param}={value}"
for param, values in query.search_params.items()
for value in values
if param != "search-offset"
]
)
url_queries = "&" + url_queries if url_queries else ""
return url_queries
[docs]def split_join(lst):
"""
Accepts a list of comma separated strings, splits them and joins them in a new list
>>> split_join(['a,b,c', 'd', 'e,f'])
['a', 'b', 'c', 'd', 'e', 'f']
"""
return [e for elem in lst for e in elem.split(",")]
[docs]class FhirRequestQuery:
"""
Represents parsed parameters from requests.
"""
def __init__(
self,
resource,
resourceId=None,
operation=None,
operationId=None,
modifiers={},
search_params={},
body=None,
request=None,
):
#: A string containing the name of the requested Resource. eg: ``'Procedure'``
self.resource = resource
#: The id of the requested resource if a specific resource was requested else ``None``
self.resourceId = resourceId
#: A string holding the requested `operation <https://www.hl7.org/fhir/search.html>`_ such as ``$meta`` or ``$validate``
self.operation = operation
#: Extra parameters passed after the operation. For example if ``Patient/123/_history/2`` was requested,
#: ``operation`` would be ``_history`` and ``operationId`` would be ``2``
self.operationId = operationId
#: Dictionary. Keys are modifier names and values are the provided values. Holds search parameters
#: that start with an underscore.
#: For example ``Patient/123?_format=json`` would have a modifiers value of ``{'_format': 'json'}``
self.modifiers = modifiers
#: Dictionary. Keys are parameter names and values are the provided values. Holds search parameters
#: that are not modifiers
#: For example ``Patient/123?_format=json`` would have a modifiers value of ``{'_format': 'json'}``
self.search_params = search_params
self.body = body
self.request = request
[docs]def parse_url(url):
"""
Parse an http request string and produce an option dict.
>>> p = parse_url('Patient/123/$validate?_format=json')
>>> p.resource
'Patient'
>>> p.resourceId
'123'
>>> p.operation
'$validate'
>>> p.modifiers
{'_format': ['json']}
>>> p.search_params
{}
:param url: a string containing the path of the request. It should not contain the server
path. For example: `Patients/123?name:contains=Jo`
:returns: A :class:`FhirRequestQuery` object
"""
# Supported operations that may be applied straight on a resource type
base_operations = ["_search", "_history"]
parsed = urlparse(url)
# Parse the path
path = parsed.path.split("/")
# Remove the empty string from the end if the path ends with a slash
if path[-1] == "":
path.pop(-1)
# Remove the empty string from the start if the path is only a slash
if path and path[0] == "":
path.pop(0)
resource = path.pop(0) if path else None
# The second item may be a resource id or a base operator. We check if it exists in base_operations
if path and path[0] in base_operations:
resourceId = None
operation = path.pop(0) if path else None
else:
resourceId = path.pop(0) if path else None
operation = path.pop(0) if path else None
operationId = path.pop(0) if operation and path else None
# parse the query strings
qs = parse_qs(parsed.query)
# Get the built-in `_keywords`
modifiers = {
param: split_join(value) for param, value in qs.items() if param.startswith("_")
}
# Get the rest of the search parameters
search_params = {
param: split_join(value)
for param, value in qs.items()
if not param in modifiers
}
# We accept both id and _id params, but transfer _id to search_params as id
id_param = modifiers.pop("_id", None)
if id_param:
search_params["id"] = id_param
params = {
"resource": resource,
"resourceId": resourceId,
"operation": operation,
"operationId": operationId,
"modifiers": modifiers,
"search_params": search_params,
}
validate_params(params)
return FhirRequestQuery(**params)
[docs]def validate_params(params):
"""
Validate a parameter dictionary. If the parameters are invalid, raise a
QueryValidationError with the details.
:param params: Parameter dictionary produced by parse_url
:return:
:raises: :exc:`fhirbug.exceptions.QueryValidationError`
"""
pass
# if not_valid(params):
# raise QueryValidationError(f'Invalid request string')