| 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 sorted by name |
| 27 | users = sorted(self.database["users"], key=lambda u: u["name"]) |
| 28 | return json.dumps({"users": users}) |
| 29 | else: |
| 30 | # Return specific users sorted by name |
| 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 | # Filter users by name |
| 39 | users = [user for user in self.database["users"] if user["name"] in user_names] |
| 40 | users = sorted(users, key=lambda u: u["name"]) |
| 41 | |
| 42 | # Edge Case: Some requested users don't exist |
| 43 | # (Non-existent users are simply not included in the response) |
| 44 | |
| 45 | return json.dumps({"users": users}) |
| 46 | |
| 47 | # Edge Case: Invalid URL |
| 48 | return json.dumps({"error": "Invalid URL"}) |
| 49 | |
| 8 | 50 | def post(self, url: str, payload: str | None = None) -> str: |
| 9 | | pass |
| 51 | """ |
| 52 | Handle POST requests to the API. |
| 53 | |
| 54 | Args: |
| 55 | url: The endpoint URL. |
| 56 | payload: JSON payload. |
| 57 | |
| 58 | Returns: |
| 59 | A JSON string response. |
| 60 | """ |
| 61 | if payload is None: |
| 62 | # Edge Case: Missing payload |
| 63 | return json.dumps({"error": "Payload required"}) |
| 64 | |
| 65 | data = json.loads(payload) |
| 66 | |
| 67 | if url == "/add": |
| 68 | user_name = data.get("user") |
| 69 | |
| 70 | # Edge Case: Missing user name |
| 71 | if not user_name: |
| 72 | return json.dumps({"error": "User name required"}) |
| 73 | |
| 74 | # Edge Case: User already exists |
| 75 | for user in self.database["users"]: |
| 76 | if user["name"] == user_name: |
| 77 | return json.dumps({"error": "User already exists"}) |
| 78 | |
| 79 | # Create new user |
| 80 | new_user = { |
| 81 | "name": user_name, |
| 82 | "owes": {}, |
| 83 | "owed_by": {}, |
| 84 | "balance": 0.0 |
| 85 | } |
| 86 | self.database["users"].append(new_user) |
| 87 | |
| 88 | return json.dumps(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 not lender_name or not borrower_name 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: Negative amount |
| 104 | if amount < 0: |
| 105 | return json.dumps({"error": "Amount must be non-negative"}) |
| 106 | |
| 107 | # Find lender and borrower |
| 108 | lender = None |
| 109 | borrower = None |
| 110 | |
| 111 | for user in self.database["users"]: |
| 112 | if user["name"] == lender_name: |
| 113 | lender = user |
| 114 | elif user["name"] == borrower_name: |
| 115 | borrower = user |
| 116 | |
| 117 | # Edge Case: Lender or borrower doesn't exist |
| 118 | if lender is None or borrower is None: |
| 119 | return json.dumps({"error": "Lender or borrower does not exist"}) |
| 120 | |
| 121 | # Update owes and owed_by with debt offsetting logic |
| 122 | # Check if lender already owes borrower (reverse relationship) |
| 123 | lender_owes_borrower = 0 |
| 124 | if borrower_name in lender["owes"]: |
| 125 | lender_owes_borrower = lender["owes"][borrower_name] |
| 126 | |
| 127 | if lender_owes_borrower > 0: |
| 128 | # Lender owes borrower, so we need to offset |
| 129 | if amount >= lender_owes_borrower: |
| 130 | # New loan is larger or equal, clear lender's debt and borrower becomes lender |
| 131 | del lender["owes"][borrower_name] |
| 132 | del borrower["owed_by"][lender_name] |
| 133 | |
| 134 | remaining_amount = amount - lender_owes_borrower |
| 135 | if remaining_amount > 0: |
| 136 | # Borrower now owes lender the remaining amount |
| 137 | if borrower_name in lender["owed_by"]: |
| 138 | lender["owed_by"][borrower_name] += remaining_amount |
| 139 | else: |
| 140 | lender["owed_by"][borrower_name] = remaining_amount |
| 141 | |
| 142 | if lender_name in borrower["owes"]: |
| 143 | borrower["owes"][lender_name] += remaining_amount |
| 144 | else: |
| 145 | borrower["owes"][lender_name] = remaining_amount |
| 146 | else: |
| 147 | # New loan is smaller, reduce existing debt |
| 148 | lender["owes"][borrower_name] -= amount |
| 149 | borrower["owed_by"][lender_name] -= amount |
| 150 | |
| 151 | # Clean up if debt becomes zero |
| 152 | if lender["owes"][borrower_name] == 0: |
| 153 | del lender["owes"][borrower_name] |
| 154 | del borrower["owed_by"][lender_name] |
| 155 | else: |
| 156 | # No existing reverse debt, normal IOU creation |
| 157 | # Update lender's owed_by |
| 158 | if borrower_name in lender["owed_by"]: |
| 159 | lender["owed_by"][borrower_name] += amount |
| 160 | else: |
| 161 | lender["owed_by"][borrower_name] = amount |
| 162 | |
| 163 | # Update borrower's owes |
| 164 | if lender_name in borrower["owes"]: |
| 165 | borrower["owes"][lender_name] += amount |
| 166 | else: |
| 167 | borrower["owes"][lender_name] = amount |
| 168 | |
| 169 | # Recalculate balances |
| 170 | lender_total_owed = sum(lender["owed_by"].values()) |
| 171 | lender_total_owes = sum(lender["owes"].values()) |
| 172 | lender["balance"] = lender_total_owed - lender_total_owes |
| 173 | |
| 174 | borrower_total_owed = sum(borrower["owed_by"].values()) |
| 175 | borrower_total_owes = sum(borrower["owes"].values()) |
| 176 | borrower["balance"] = borrower_total_owed - borrower_total_owes |
| 177 | |
| 178 | # Return updated users sorted by name |
| 179 | users = sorted([lender, borrower], key=lambda u: u["name"]) |
| 180 | return json.dumps({"users": users}) |
| 181 | |
| 182 | # Edge Case: Invalid URL |
| 183 | return json.dumps({"error": "Invalid URL"}) |
| 184 | |
| 185 | # Handled Edge Cases: Invalid URL, Missing payload, Missing user name, User already exists, Missing required fields, Lender and borrower are the same, Negative amount, Lender or borrower doesn't exist, Empty user list in payload, Some requested users don't exist |