| 1 | import json |
| 2 | |
| 1 | 3 | class RestAPI: |
| 2 | 4 | def __init__(self, database: dict | None = None): |
| 3 | | pass |
| 4 | | |
| 5 | # Initialize the in-memory database |
| 6 | self.database = database if database is not None else {"users": []} |
| 7 | |
| 5 | 8 | def get(self, url: str, payload: str | None = None) -> str: |
| 6 | | pass |
| 7 | | |
| 9 | # Edge Case: Handle invalid URL |
| 10 | if url != "/users": |
| 11 | return json.dumps({"error": "Invalid URL"}) |
| 12 | |
| 13 | # Edge Case: Handle empty database |
| 14 | if not self.database["users"]: |
| 15 | return json.dumps({"users": []}) |
| 16 | |
| 17 | # Parse payload if provided |
| 18 | if payload: |
| 19 | try: |
| 20 | data = json.loads(payload) |
| 21 | # Edge Case: Handle missing 'users' key in payload |
| 22 | if "users" not in data: |
| 23 | return json.dumps({"error": "Invalid payload"}) |
| 24 | |
| 25 | # Filter users based on payload |
| 26 | requested_users = data["users"] |
| 27 | users = [user for user in self.database["users"] if user["name"] in requested_users] |
| 28 | # Sort users by name |
| 29 | users.sort(key=lambda x: x["name"]) |
| 30 | return json.dumps({"users": users}) |
| 31 | except json.JSONDecodeError: |
| 32 | # Edge Case: Handle invalid JSON in payload |
| 33 | return json.dumps({"error": "Invalid JSON payload"}) |
| 34 | else: |
| 35 | # Return all users sorted by name |
| 36 | users = sorted(self.database["users"], key=lambda x: x["name"]) |
| 37 | return json.dumps({"users": users}) |
| 38 | |
| 8 | 39 | def post(self, url: str, payload: str | None = None) -> str: |
| 9 | | pass |
| 40 | # Edge Case: Handle missing payload |
| 41 | if not payload: |
| 42 | return json.dumps({"error": "Payload required"}) |
| 43 | |
| 44 | try: |
| 45 | data = json.loads(payload) |
| 46 | except json.JSONDecodeError: |
| 47 | # Edge Case: Handle invalid JSON in payload |
| 48 | return json.dumps({"error": "Invalid JSON payload"}) |
| 49 | |
| 50 | if url == "/add": |
| 51 | # Edge Case: Handle missing 'user' key in payload |
| 52 | if "user" not in data: |
| 53 | return json.dumps({"error": "Missing user field"}) |
| 54 | |
| 55 | new_user_name = data["user"] |
| 56 | |
| 57 | # Edge Case: Handle duplicate user |
| 58 | for user in self.database["users"]: |
| 59 | if user["name"] == new_user_name: |
| 60 | return json.dumps({"error": "User already exists"}) |
| 61 | |
| 62 | # Create new user |
| 63 | new_user = { |
| 64 | "name": new_user_name, |
| 65 | "owes": {}, |
| 66 | "owed_by": {}, |
| 67 | "balance": 0.0 |
| 68 | } |
| 69 | |
| 70 | self.database["users"].append(new_user) |
| 71 | return json.dumps(new_user) |
| 72 | |
| 73 | elif url == "/iou": |
| 74 | # Edge Case: Handle missing required fields |
| 75 | required_fields = ["lender", "borrower", "amount"] |
| 76 | for field in required_fields: |
| 77 | if field not in data: |
| 78 | return json.dumps({"error": f"Missing {field} field"}) |
| 79 | |
| 80 | lender_name = data["lender"] |
| 81 | borrower_name = data["borrower"] |
| 82 | amount = data["amount"] |
| 83 | |
| 84 | # Edge Case: Handle invalid amount type |
| 85 | if not isinstance(amount, (int, float)): |
| 86 | return json.dumps({"error": "Amount must be a number"}) |
| 87 | |
| 88 | # Edge Case: Handle negative amount |
| 89 | if amount < 0: |
| 90 | return json.dumps({"error": "Amount must be positive"}) |
| 91 | |
| 92 | # Edge Case: Handle lender and borrower being the same person |
| 93 | if lender_name == borrower_name: |
| 94 | return json.dumps({"error": "Lender and borrower cannot be the same"}) |
| 95 | |
| 96 | # Find lender and borrower in database |
| 97 | lender = None |
| 98 | borrower = None |
| 99 | |
| 100 | for user in self.database["users"]: |
| 101 | if user["name"] == lender_name: |
| 102 | lender = user |
| 103 | elif user["name"] == borrower_name: |
| 104 | borrower = user |
| 105 | |
| 106 | # Edge Case: Handle lender not found |
| 107 | if lender is None: |
| 108 | return json.dumps({"error": "Lender not found"}) |
| 109 | |
| 110 | # Edge Case: Handle borrower not found |
| 111 | if borrower is None: |
| 112 | return json.dumps({"error": "Borrower not found"}) |
| 113 | |
| 114 | # Update IOUs |
| 115 | # Update lender's owed_by |
| 116 | if borrower_name in lender["owed_by"]: |
| 117 | lender["owed_by"][borrower_name] += amount |
| 118 | else: |
| 119 | lender["owed_by"][borrower_name] = amount |
| 120 | |
| 121 | # Update borrower's owes |
| 122 | if lender_name in borrower["owes"]: |
| 123 | borrower["owes"][lender_name] += amount |
| 124 | else: |
| 125 | borrower["owes"][lender_name] = amount |
| 126 | |
| 127 | # Recalculate balances |
| 128 | lender_total_owed = sum(lender["owed_by"].values()) |
| 129 | lender_total_owes = sum(lender["owes"].values()) |
| 130 | lender["balance"] = lender_total_owed - lender_total_owes |
| 131 | |
| 132 | borrower_total_owed = sum(borrower["owed_by"].values()) |
| 133 | borrower_total_owes = sum(borrower["owes"].values()) |
| 134 | borrower["balance"] = borrower_total_owed - borrower_total_owes |
| 135 | |
| 136 | # Return updated users sorted by name |
| 137 | updated_users = sorted([lender, borrower], key=lambda x: x["name"]) |
| 138 | return json.dumps({"users": updated_users}) |
| 139 | |
| 140 | else: |
| 141 | # Edge Case: Handle invalid URL |
| 142 | return json.dumps({"error": "Invalid URL"}) |
| 143 | # Handled Edge Cases: Invalid URL, empty database, missing 'users' key in payload, invalid JSON in payload, missing payload, missing 'user' key in payload, duplicate user, missing required fields, invalid amount type, negative amount, lender and borrower being the same person, lender not found, borrower not found, invalid URL |