| 1 | import json |
| 2 | |
| 1 | 3 | class RestAPI: |
| 2 | 4 | def __init__(self, database: dict | None = None): |
| 3 | | pass |
| 4 | | |
| 5 | """ |
| 6 | Initialize the REST API with a database. |
| 7 | |
| 8 | Args: |
| 9 | database: A dictionary containing user data or None to start with an empty database. |
| 10 | """ |
| 11 | if database is None: |
| 12 | self.database = {"users": []} |
| 13 | else: |
| 14 | self.database = database |
| 15 | |
| 5 | 16 | def get(self, url: str, payload: str | None = None) -> str: |
| 6 | | pass |
| 7 | | |
| 17 | """ |
| 18 | Handle GET requests to the API. |
| 19 | |
| 20 | Args: |
| 21 | url: The endpoint URL. |
| 22 | payload: Optional JSON payload. |
| 23 | |
| 24 | Returns: |
| 25 | A JSON string response. |
| 26 | |
| 27 | Raises: |
| 28 | ValueError: If the URL is not recognized. |
| 29 | """ |
| 30 | if url == "/users": |
| 31 | if payload is None: |
| 32 | # Return all users |
| 33 | users = [self._create_user_object(user) for user in self.database["users"]] |
| 34 | return json.dumps({"users": users}) |
| 35 | else: |
| 36 | # Return specific users |
| 37 | data = json.loads(payload) |
| 38 | user_names = data.get("users", []) |
| 39 | |
| 40 | # Edge Case: Empty user list in payload |
| 41 | if not user_names: |
| 42 | return json.dumps({"users": []}) |
| 43 | |
| 44 | # Edge Case: Non-existent users in payload |
| 45 | result_users = [] |
| 46 | for name in user_names: |
| 47 | user = self._find_user(name) |
| 48 | if user is not None: |
| 49 | result_users.append(self._create_user_object(user)) |
| 50 | |
| 51 | # Sort by name |
| 52 | result_users.sort(key=lambda u: u["name"]) |
| 53 | return json.dumps({"users": result_users}) |
| 54 | else: |
| 55 | # Edge Case: Invalid URL |
| 56 | raise ValueError(f"Invalid URL: {url}") |
| 57 | |
| 8 | 58 | def post(self, url: str, payload: str | None = None) -> str: |
| 9 | | pass |
| 59 | """ |
| 60 | Handle POST requests to the API. |
| 61 | |
| 62 | Args: |
| 63 | url: The endpoint URL. |
| 64 | payload: JSON payload. |
| 65 | |
| 66 | Returns: |
| 67 | A JSON string response. |
| 68 | |
| 69 | Raises: |
| 70 | ValueError: If the URL is not recognized or if there are validation errors. |
| 71 | """ |
| 72 | if url == "/add": |
| 73 | if payload is None: |
| 74 | # Edge Case: Missing payload |
| 75 | raise ValueError("Payload required for /add endpoint") |
| 76 | |
| 77 | data = json.loads(payload) |
| 78 | user_name = data.get("user") |
| 79 | |
| 80 | # Edge Case: Missing user field |
| 81 | if user_name is None: |
| 82 | raise ValueError("'user' field is required") |
| 83 | |
| 84 | # Edge Case: User already exists |
| 85 | existing_user = self._find_user(user_name) |
| 86 | if existing_user is not None: |
| 87 | return json.dumps(self._create_user_object(existing_user)) |
| 88 | |
| 89 | new_user = { |
| 90 | "name": user_name, |
| 91 | "owes": {}, |
| 92 | "owed_by": {}, |
| 93 | "balance": 0.0 |
| 94 | } |
| 95 | |
| 96 | self.database["users"].append(new_user) |
| 97 | return json.dumps(self._create_user_object(new_user)) |
| 98 | |
| 99 | elif url == "/iou": |
| 100 | if payload is None: |
| 101 | # Edge Case: Missing payload |
| 102 | raise ValueError("Payload required for /iou endpoint") |
| 103 | |
| 104 | data = json.loads(payload) |
| 105 | lender_name = data.get("lender") |
| 106 | borrower_name = data.get("borrower") |
| 107 | amount = data.get("amount") |
| 108 | |
| 109 | # Edge Case: Missing required fields |
| 110 | if lender_name is None or borrower_name is None or amount is None: |
| 111 | raise ValueError("'lender', 'borrower', and 'amount' fields are required") |
| 112 | |
| 113 | # Edge Case: Lender and borrower are the same person |
| 114 | if lender_name == borrower_name: |
| 115 | raise ValueError("Lender and borrower cannot be the same person") |
| 116 | |
| 117 | lender = self._find_user(lender_name) |
| 118 | borrower = self._find_user(borrower_name) |
| 119 | |
| 120 | # Edge Case: Lender or borrower does not exist |
| 121 | if lender is None: |
| 122 | raise ValueError(f"User {lender_name} does not exist") |
| 123 | if borrower is None: |
| 124 | raise ValueError(f"User {borrower_name} does not exist") |
| 125 | |
| 126 | # Edge Case: Negative amount |
| 127 | if amount < 0: |
| 128 | raise ValueError("Amount must be non-negative") |
| 129 | |
| 130 | # Update lender's owed_by |
| 131 | if borrower_name in lender["owed_by"]: |
| 132 | lender["owed_by"][borrower_name] += amount |
| 133 | else: |
| 134 | lender["owed_by"][borrower_name] = amount |
| 135 | |
| 136 | # Update borrower's owes |
| 137 | if lender_name in borrower["owes"]: |
| 138 | borrower["owes"][lender_name] += amount |
| 139 | else: |
| 140 | borrower["owes"][lender_name] = amount |
| 141 | |
| 142 | # Update balances |
| 143 | lender["balance"] += amount |
| 144 | borrower["balance"] -= amount |
| 145 | |
| 146 | # Return updated user objects |
| 147 | updated_users = [ |
| 148 | self._create_user_object(lender), |
| 149 | self._create_user_object(borrower) |
| 150 | ] |
| 151 | updated_users.sort(key=lambda u: u["name"]) |
| 152 | return json.dumps({"users": updated_users}) |
| 153 | |
| 154 | else: |
| 155 | # Edge Case: Invalid URL |
| 156 | raise ValueError(f"Invalid URL: {url}") |
| 157 | |
| 158 | def _find_user(self, name: str): |
| 159 | """ |
| 160 | Find a user by name in the database. |
| 161 | |
| 162 | Args: |
| 163 | name: The name of the user to find. |
| 164 | |
| 165 | Returns: |
| 166 | The user dictionary if found, otherwise None. |
| 167 | """ |
| 168 | for user in self.database["users"]: |
| 169 | if user["name"] == name: |
| 170 | return user |
| 171 | return None |
| 172 | |
| 173 | def _create_user_object(self, user): |
| 174 | """ |
| 175 | Create a user object for API responses. |
| 176 | |
| 177 | Args: |
| 178 | user: The user dictionary from the database. |
| 179 | |
| 180 | Returns: |
| 181 | A dictionary representing the user object. |
| 182 | """ |
| 183 | # Make sure we don't modify the original user object |
| 184 | return { |
| 185 | "name": user["name"], |
| 186 | "owes": user["owes"].copy(), |
| 187 | "owed_by": user["owed_by"].copy(), |
| 188 | "balance": user["balance"] |
| 189 | } |
| 190 | # Handled Edge Cases: Invalid URL, Missing payload, Missing user field, User already exists, Missing required fields, Lender and borrower are the same person, Lender or borrower does not exist, Negative amount, Empty user list in payload, Non-existent users in payload |