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