Browse Source

Add Preference-Applied header to GET

Jared Whiklo 4 years ago
parent
commit
9aa67176a5
2 changed files with 88 additions and 19 deletions
  1. 76 19
      lakesuperior/endpoints/ldp.py
  2. 12 0
      tests/3_endpoints/test_3_0_ldp.py

+ 76 - 19
lakesuperior/endpoints/ldp.py

@@ -162,7 +162,7 @@ def get_resource(uid, out_fmt=None):
         prefer = toolbox.parse_rfc7240(request.headers['prefer'])
         logger.debug('Parsed Prefer header: {}'.format(pformat(prefer)))
         if 'return' in prefer:
-            repr_options = parse_repr_options(prefer['return'])
+            repr_options = parse_repr_options(prefer['return'], out_headers)
 
     rsrc = rsrc_api.get(uid, repr_options)
 
@@ -643,7 +643,7 @@ def _set_post_put_params():
     return handling, disposition
 
 
-def parse_repr_options(retr_opts):
+def parse_repr_options(retr_opts, out_headers):
     """
     Set options to retrieve IMR.
 
@@ -651,9 +651,15 @@ def parse_repr_options(retr_opts):
     are set once in the `imr()` property.
 
     :param dict retr_opts:: Options parsed from `Prefer` header.
+    :param dict out_headers:: Response headers.
     """
     logger.debug('Parsing retrieval options: {}'.format(retr_opts))
-    imr_options = {}
+    uri_to_option = {
+        'embed_children': Ldpr.EMBED_CHILD_RES_URI,
+        'incl_children': Ldpr.RETURN_CHILD_RES_URI,
+        'incl_inbound': Ldpr.RETURN_INBOUND_REF_URI,
+        'incl_srv_mgd': Ldpr.RETURN_SRV_MGD_RES_URI
+    }
 
     if retr_opts.get('value') == 'minimal':
         imr_options = {
@@ -662,6 +668,7 @@ def parse_repr_options(retr_opts):
             'incl_inbound' : False,
             'incl_srv_mgd' : False,
         }
+        out_headers['Preference-Applied'] = "return=\"minimal\""
     else:
         # Default.
         imr_options = {
@@ -673,28 +680,78 @@ def parse_repr_options(retr_opts):
 
         # Override defaults.
         if 'parameters' in retr_opts:
-            include = retr_opts['parameters']['include'].split(' ') \
-                    if 'include' in retr_opts['parameters'] else []
-            omit = retr_opts['parameters']['omit'].split(' ') \
-                    if 'omit' in retr_opts['parameters'] else []
-
-            logger.debug('Include: {}'.format(include))
-            logger.debug('Omit: {}'.format(omit))
-
-            if str(Ldpr.EMBED_CHILD_RES_URI) in include:
-                    imr_options['embed_children'] = True
-            if str(Ldpr.RETURN_CHILD_RES_URI) in omit:
-                    imr_options['incl_children'] = False
-            if str(Ldpr.RETURN_INBOUND_REF_URI) in include:
-                    imr_options['incl_inbound'] = True
-            if str(Ldpr.RETURN_SRV_MGD_RES_URI) in omit:
-                    imr_options['incl_srv_mgd'] = False
+            pref_imr_options = _valid_preferences(retr_opts)
+            include = list()
+            omit = list()
+            for k, v in pref_imr_options.items():
+                # pref_imr_options only contains requested preferences, override the defaults for those.
+                imr_options[k] = v
+                # This creates Preference-Applied headers for those things we support.
+                if v:
+                    list_holder = include
+                else:
+                    list_holder = omit
+                list_holder.append(uri_to_option[k])
+
+            header_output = ""
+            if len(include) > 0:
+                header_output += " include=\"" + " ".join(include) + "\";"
+            if len(omit) > 0:
+                header_output += " omit=\"" + " ".join(omit) + "\""
+            if len(header_output) > 0:
+                out_headers['Preference-Applied'] = "return=representation;" + header_output
 
     logger.debug('Retrieval options: {}'.format(pformat(imr_options)))
 
     return imr_options
 
 
+def _preference_decision(include, omit, header):
+    """
+    Determine whether a header is in include or omit but not both.
+
+    :param include:: list of include preference uris
+    :param omit:: list of omit preference uris
+    :param header:: the uri to look for
+    :return: True if in include only or false if in omit only.
+    """
+    if str(header) in include or str(header) in omit:
+        if str(header) in include and str(header) in omit:
+            # You can't include and omit, so ignore it.
+            return None
+        else:
+            return str(header) in include
+    return None
+
+
+def _valid_preferences(retr_opts):
+    """
+    Parse the Preference header to determine which we are applying.
+
+    Re-used for response Preference-Applied header.
+
+    :param retr_opts: The incoming Preference header.
+    :return: list of options being applied.
+    """
+    imr_options = dict()
+    include = retr_opts['parameters']['include'].split(' ') \
+        if 'include' in retr_opts['parameters'] else []
+    omit = retr_opts['parameters']['omit'].split(' ') \
+        if 'omit' in retr_opts['parameters'] else []
+
+    logger.debug('Include: {}'.format(include))
+    logger.debug('Omit: {}'.format(omit))
+
+    imr_options['embed_children'] = _preference_decision(include, omit, Ldpr.EMBED_CHILD_RES_URI)
+    imr_options['incl_children'] = _preference_decision(include, omit, Ldpr.RETURN_CHILD_RES_URI)
+    imr_options['incl_inbound'] = _preference_decision(include, omit, Ldpr.RETURN_INBOUND_REF_URI)
+    imr_options['incl_srv_mgd'] = _preference_decision(include, omit, Ldpr.RETURN_SRV_MGD_RES_URI)
+
+    imr_options = {k: v for k, v in imr_options.items() if v is not None}
+
+    return imr_options
+
+
 def _headers_from_metadata(rsrc, out_fmt='text/turtle'):
     """
     Create a dict of headers from a metadata graph.

+ 12 - 0
tests/3_endpoints/test_3_0_ldp.py

@@ -21,6 +21,7 @@ from lakesuperior import env
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.model.ldp.ldpr import Ldpr
 
+from lakesuperior.util import toolbox
 
 digest_algo = env.app_globals.config['application']['uuid']['algo']
 
@@ -1715,11 +1716,22 @@ class TestPrefHeader:
             'Prefer' : 'return=representation; include={}'\
                     .format(Ldpr.EMBED_CHILD_RES_URI),
         })
+
+        assert 'Preference-Applied' in incl_embed_children_resp.headers
+        incl_headers = toolbox.parse_rfc7240(incl_embed_children_resp.headers['Preference-Applied'])
+        assert incl_headers['return']['value'] == 'representation'
+        assert incl_headers['return']['parameters']['include'] == str(Ldpr.EMBED_CHILD_RES_URI)
+
         omit_embed_children_resp = self.client.get(parent_path, headers={
             'Prefer' : 'return=representation; omit={}'\
                     .format(Ldpr.EMBED_CHILD_RES_URI),
         })
 
+        assert 'Preference-Applied' in omit_embed_children_resp.headers
+        omit_headers = toolbox.parse_rfc7240(omit_embed_children_resp.headers['Preference-Applied'])
+        assert omit_headers['return']['value'] == 'representation'
+        assert omit_headers['return']['parameters']['include'] == str(Ldpr.EMBED_CHILD_RES_URI)
+
         default_gr = Graph().parse(data=cont_resp.data, format='turtle')
         incl_gr = Graph().parse(
                 data=incl_embed_children_resp.data, format='turtle')