From 47f9c3ba61ac30ea7e71d0d14aefa4d789f48afc Mon Sep 17 00:00:00 2001
From: aheschl1 <ajheschl@gmail.com>
Date: Sat, 8 Feb 2025 12:09:31 -0700
Subject: [PATCH 1/2] Reduce the complexity of Request

---
 README.md                |   8 ++-
 src/requests/requests.py | 136 +--------------------------------------
 test/test_fol.py         |   4 +-
 3 files changed, 11 insertions(+), 137 deletions(-)

diff --git a/README.md b/README.md
index 8cda942..eb181a1 100644
--- a/README.md
+++ b/README.md
@@ -3,4 +3,10 @@ https://github.com/aheschl1/ResourceScheduler/tree/master
 
 I have extracted the portion to validate a request against an FOL policy string.
 
-Basically, you explain what makes a valid json object "valid" using various binary connectives, quantifiers, and variables
\ No newline at end of file
+Basically, you explain what makes a valid json object "valid" using various binary connectives, quantifiers, and variables
+
+# Refactor 1: Unnececarry Generalization
+The "Request" class was designed around the idea that it may need to accept HTTP requests, and thus, it can parse HTTP.
+This is not the case now, and thus, I have removed the feature.
+
+This includes stripping down the "Request" object to be a simple typed wrapper around a Dict
\ No newline at end of file
diff --git a/src/requests/requests.py b/src/requests/requests.py
index 28ada70..ce14139 100644
--- a/src/requests/requests.py
+++ b/src/requests/requests.py
@@ -1,141 +1,11 @@
-from typing import Dict, Tuple
-import json
-
-import re
-
-
-def _validate_request_path(path: str) -> bool:
-    """
-    Is the path valid?
-    Can not start with dot
-    Can not end with dot
-    Can have numbers, letters, and underscore
-    Can not have two dots in a raw
-    :param path:
-    :return:
-    """
-    regex = re.compile(r'^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*$')
-    return regex.match(path) is not None
-
-
 class Request:
     """
-    Handles the validation and transfer of various request types
+    Stores a request and provides access to its data.
     """
-    def __init__(self, request_data: bytes, sending_dict=False):
+    def __init__(self, request_data: bytes):
         
-        if not sending_dict:
-            raw_data = request_data.decode()
-            self.request_method, request_data = Request._decode_http(raw_data)
-            try:
-                self._request_data = Request._decode_request(request_data)
-            except Exception as _:
-                raise ValueError("Poorly formatted request. Could not parse the request data.")
-        else:
-            self._request_data = request_data
-            self.request_method = "GET"
-        if self.request_method in ["POST", "GET"]:
-            if "entity" not in self._request_data:
-                raise ValueError("Missing entity for your ")
-            # This is for post features in a request. i.e. for traversing the tree
-            self._path_fragments = self._request_data["entity"].split(".")
-            self._root_name = self._path_fragments[0]
-            self._current_fragment = 0
-
-    @staticmethod
-    def _decode_http(raw_data: str) -> Tuple[str, str]:
-        lines = raw_data.split("\r\n")
-        # assert correct header line
-        status_line = lines[0].split(" ")
-        if status_line[1] != "/":
-            raise ValueError("Server only supports root HTTP query.")
-        if status_line[2] != "HTTP/1.1":
-            raise ValueError("Only HTTP/1.1 is supported.")
-        # first line, first word is the request method
-        method = status_line[0]
-        if method not in ["GET", "POST", "PUT"]:
-            raise ValueError(
-                "Unsupported method. Use GET to query on resources, POST to register a resource, and PUT to create an entity/organization")
-
-        content = raw_data.split("\r\n\r\n")[-1]
-        return method, content
-
-    @staticmethod
-    def _decode_request(req: str) -> Dict:
-        """
-        Given bytes, returns the dictionary.
-        Throws a json format ValueError
-        :param req:
-        :return:
-        """
-        return json.loads(req)
-
-    def _post_validation(self):
-        if "entity" not in self._request_data:
-            raise ValueError("Entity path not specified in request")
-        if not _validate_request_path(self.entity_path):
-            raise ValueError("Requested path is not legal")
-        return True
-
-    def _put_validation(self) -> True:
-        # TODO actually validate!
-        return True
-
-    def _get_validation(self) -> True:
-        if "entity" not in self._request_data:
-            raise ValueError("Entity path not specified in request.")
-        if "recursive" not in self._request_data:
-            raise ValueError("Specify recursive request as true/false.")
-        if not isinstance(self._request_data["recursive"], bool):
-            raise ValueError("Specify recursive request as true/false.")
-
-    def validate(self) -> True:
-        if not self._request_data:
-            raise ValueError("Request data is None")
-        if self.request_method == "POST":
-            return self._post_validation()
-        elif self.request_method == "PUT":
-            return self._put_validation()
-        elif self.request_method == "GET":
-            return self._get_validation()
-        else:
-            raise NotImplementedError("Requested method not implemented")
-
-    def extract_next_route(self) -> str:
-        """
-        Gives the next route, and removes it from the consumable path
-        :return: next route
-        """
-        if self._current_fragment == len(self._path_fragments):
-            raise ValueError()
-        next_route = self._path_fragments[self._current_fragment]
-        self._current_fragment += 1
-        return next_route
-
-    @property
-    def entity_path(self):
-        return self._request_data["entity"]
-
-    @property
-    def root_name(self) -> str:
-        return self._root_name
-
-    @property
-    def data(self) -> dict:
-        return self._request_data["data"]
+        self._request_data = request_data
 
     @property
     def raw_request(self) -> dict:
         return self._request_data
-
-    @property
-    def current_name(self):
-        return self._path_fragments[self._current_fragment - 1]
-
-    @property
-    def headers(self):
-        return list(self._request_data.keys())
-
-
-if __name__ == "__main__":
-    print(_validate_request_path("a.0aa_.bbbb"))
\ No newline at end of file
diff --git a/test/test_fol.py b/test/test_fol.py
index 0321606..8f6c9e3 100644
--- a/test/test_fol.py
+++ b/test/test_fol.py
@@ -1,7 +1,5 @@
-from src.policy import Policy
 from src.fol_policies.policy import FolPolicyFactory
 from src.requests.requests import Request
-import json
 
 isoregex = r'^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$'
 
@@ -19,7 +17,7 @@ def get_test_request():
             "b": "2024-12-13T12:12:12.001Z"
         }
     }
-    return Request(request2, sending_dict=True)
+    return Request(request2)
 
 def run_test(query, expected):
     request = get_test_request()
-- 
GitLab


From 8ae0e27fa69192845eaa67e00d4023946f03e65b Mon Sep 17 00:00:00 2001
From: aheschl1 <ajheschl@gmail.com>
Date: Sat, 8 Feb 2025 12:13:05 -0700
Subject: [PATCH 2/2] Finish refactor

---
 README.md                  |  2 +-
 src/fol_policies/policy.py | 27 +++++++++++++--------------
 src/policy.py              |  8 +++-----
 src/requests/__init__.py   |  0
 src/requests/requests.py   | 11 -----------
 test/test_fol.py           |  3 +--
 6 files changed, 18 insertions(+), 33 deletions(-)
 delete mode 100644 src/requests/__init__.py
 delete mode 100644 src/requests/requests.py

diff --git a/README.md b/README.md
index eb181a1..c40ea15 100644
--- a/README.md
+++ b/README.md
@@ -9,4 +9,4 @@ Basically, you explain what makes a valid json object "valid" using various bina
 The "Request" class was designed around the idea that it may need to accept HTTP requests, and thus, it can parse HTTP.
 This is not the case now, and thus, I have removed the feature.
 
-This includes stripping down the "Request" object to be a simple typed wrapper around a Dict
\ No newline at end of file
+The Request class has been removed, and instead, a Dict object is passed around the policy.
\ No newline at end of file
diff --git a/src/fol_policies/policy.py b/src/fol_policies/policy.py
index 066cb85..3e00848 100644
--- a/src/fol_policies/policy.py
+++ b/src/fol_policies/policy.py
@@ -4,7 +4,6 @@ from abc import abstractmethod
 from typing import Tuple, Any, Dict, List, Union
 
 from src.policy import Policy
-from src.requests.requests import Request
 from src.utils.utils import hierarchical_dict_lookup, hierarchical_keys
 
 
@@ -17,7 +16,7 @@ class Constant:
         self._literal = literal.strip()
         self._extracted_regulars = extracted_regulars
 
-    def extract(self, request: Request) -> Any:
+    def extract(self, request: Dict) -> Any:
         """
         Extract the value from the request, or return the constant
         :param request:
@@ -26,7 +25,7 @@ class Constant:
         if self._literal[0] == "*":
             raise NotImplementedError("Key lookup does not yet exist (will be wrapped by existential)")
         if self._literal[0] == "$":
-            return hierarchical_dict_lookup(request.raw_request, self._literal[1:])
+            return hierarchical_dict_lookup(request, self._literal[1:])
         if self._literal[0] == "^":
             return self._extracted_regulars[self._literal[1:]]
         else:
@@ -67,7 +66,7 @@ class AtomicPolicy(Policy):
         c2 = Constant(current_read, extracted_regulars=self._extracted_regulars)
         return operation, c1, c2
 
-    def validate(self, request: Request):
+    def validate(self, request: Dict):
         """
         Validates an atomic request
         :param request:
@@ -108,7 +107,7 @@ class AndPolicy(Policy):
         super().__init__(False)
         self._policies = policies
 
-    def validate(self, request: Request):
+    def validate(self, request: Dict):
         result = True
         for atomic_policy in self._policies:
             result = result and atomic_policy(request)
@@ -130,7 +129,7 @@ class OrPolicy(Policy):
         super().__init__(False)
         self._policies = policies
 
-    def validate(self, request: Request):
+    def validate(self, request: Dict):
         result = False
         for atomic_policy in self._policies:
             result = result or atomic_policy(request)
@@ -153,7 +152,7 @@ class NotPolicy(Policy):
         super().__init__(False)
         self._policy = policy
 
-    def validate(self, request: Request):
+    def validate(self, request: Dict):
         return not self._policy(request)
 
     def __str__(self):
@@ -185,7 +184,7 @@ class QuantifierPolicy(Policy):
             i += 1
         return replaced
 
-    def _get_keys_for_check(self, request: Request) -> List[str]:
+    def _get_keys_for_check(self, request: Dict) -> List[str]:
         """
         Given a list of keys, returns all recursive keys to check in the request.
         If a key does not end in *, then we do not go recursively.
@@ -194,20 +193,20 @@ class QuantifierPolicy(Policy):
         :param request:
         :return:
         """
-        all_keys = hierarchical_keys(request.raw_request) if self._bases is None else []
+        all_keys = hierarchical_keys(request) if self._bases is None else []
         if self._bases is None:
             return all_keys
         for key in self._bases:
             # TODO chance for index out of bounds
             if key[-1] == "*" and key[-2] == ".":
                 key = key[:-2]
-                all_keys.extend(hierarchical_keys(hierarchical_dict_lookup(request.raw_request, key), parent_key=key))
+                all_keys.extend(hierarchical_keys(hierarchical_dict_lookup(request, key), parent_key=key))
             else:
                 all_keys.append(key)
         return all_keys
 
     @abstractmethod
-    def validate(self, request: Request):
+    def validate(self, request: Dict):
         ...
 
 
@@ -221,7 +220,7 @@ class ExistentialPolicy(QuantifierPolicy):
     If in A we encounter $x, we will then be replacing with the value at key in the request
     """
 
-    def validate(self, request: Request):
+    def validate(self, request: Dict):
         all_keys = self._get_keys_for_check(request)
         for key in all_keys:
             literal_attempt = self._replace_variable(key)
@@ -241,7 +240,7 @@ class UniversalPolicy(QuantifierPolicy):
     If in A we encounter $x, we will then be replacing with the value at key in the request
     """
 
-    def validate(self, request: Request):
+    def validate(self, request: Dict):
         all_keys = self._get_keys_for_check(request)
         result = True
         for key in all_keys:
@@ -256,7 +255,7 @@ class FolWrapper(Policy):
         super().__init__(False)
         self.policy = policy
 
-    def validate(self, request: Request) -> Tuple[bool, str]:
+    def validate(self, request: Dict) -> Tuple[bool, str]:
         result = self.policy.validate(request)
         if isinstance(result, tuple):
             result = result[0]
diff --git a/src/policy.py b/src/policy.py
index 9df9f6e..39a1f52 100644
--- a/src/policy.py
+++ b/src/policy.py
@@ -1,7 +1,5 @@
 from abc import abstractmethod
-from typing import Tuple
-
-from src.requests.requests import Request
+from typing import Dict, Tuple
 
 
 class Policy:
@@ -9,7 +7,7 @@ class Policy:
         self.full_approval = full_approval
 
     @abstractmethod
-    def validate(self, request: Request) -> Tuple[bool, str]:
+    def validate(self, request: Dict) -> Tuple[bool, str]:
         """
         Validate a request against a policy
         :param request: The request to validate
@@ -22,5 +20,5 @@ class Policy:
     def __str__(self):
         return str(self.__class__.__name__)
 
-    def __call__(self, request: Request) -> Tuple[bool, str]:
+    def __call__(self, request: Dict) -> Tuple[bool, str]:
         return self.validate(request)
diff --git a/src/requests/__init__.py b/src/requests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/requests/requests.py b/src/requests/requests.py
deleted file mode 100644
index ce14139..0000000
--- a/src/requests/requests.py
+++ /dev/null
@@ -1,11 +0,0 @@
-class Request:
-    """
-    Stores a request and provides access to its data.
-    """
-    def __init__(self, request_data: bytes):
-        
-        self._request_data = request_data
-
-    @property
-    def raw_request(self) -> dict:
-        return self._request_data
diff --git a/test/test_fol.py b/test/test_fol.py
index 8f6c9e3..46ef79b 100644
--- a/test/test_fol.py
+++ b/test/test_fol.py
@@ -1,5 +1,4 @@
 from src.fol_policies.policy import FolPolicyFactory
-from src.requests.requests import Request
 
 isoregex = r'^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$'
 
@@ -17,7 +16,7 @@ def get_test_request():
             "b": "2024-12-13T12:12:12.001Z"
         }
     }
-    return Request(request2)
+    return request2
 
 def run_test(query, expected):
     request = get_test_request()
-- 
GitLab