let it go

Finished
1import json
2
13class RestAPI:
24 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 # Edge Case: Handle None database by initializing empty users list
12 self.database = database if database is not None else {"users": []}
13
514 def get(self, url: str, payload: str | None = None) -> str:
6 pass
7
15 """
16 Handle GET requests to the API.
17
18 Args:
19 url: The endpoint URL.
20 payload: Optional JSON payload.
21
22 Returns:
23 JSON string response.
24 """
25 if url == "/users":
26 # Edge Case: Handle missing payload by returning all users
27 if payload is None:
28 # Sort users by name
29 sorted_users = sorted(self.database["users"], key=lambda u: u["name"])
30 users = [self._create_user_object(user) for user in sorted_users]
31 return json.dumps({"users": users})
32 else:
33 # Edge Case: Handle invalid JSON payload
34 try:
35 data = json.loads(payload)
36 except json.JSONDecodeError:
37 return json.dumps({"error": "Invalid JSON payload"})
38
39 # Edge Case: Handle missing 'users' key in payload
40 if "users" not in data:
41 return json.dumps({"error": "Missing 'users' key in payload"})
42
43 # Edge Case: Handle non-list 'users' value in payload
44 if not isinstance(data["users"], list):
45 return json.dumps({"error": "'users' must be a list"})
46
47 # Filter users based on payload
48 requested_users = data["users"]
49 filtered_users = [user for user in self.database["users"] if user["name"] in requested_users]
50
51 # Instead of returning an error for non-existent users, just return existing ones
52 # Sort users by name
53 sorted_users = sorted(filtered_users, key=lambda u: u["name"])
54 user_objects = [self._create_user_object(user) for user in sorted_users]
55
56 return json.dumps({"users": user_objects})
57
58 # Edge Case: Handle unknown URL
59 return json.dumps({"error": "Unknown endpoint"})
60
861 def post(self, url: str, payload: str | None = None) -> str:
9 pass
62 """
63 Handle POST requests to the API.
64
65 Args:
66 url: The endpoint URL.
67 payload: JSON payload.
68
69 Returns:
70 JSON string response.
71 """
72 # Edge Case: Handle missing payload
73 if payload is None:
74 return json.dumps({"error": "Payload required"})
75
76 # Edge Case: Handle invalid JSON payload
77 try:
78 data = json.loads(payload)
79 except json.JSONDecodeError:
80 return json.dumps({"error": "Invalid JSON payload"})
81
82 if url == "/add":
83 # Edge Case: Handle missing 'user' key
84 if "user" not in data:
85 return json.dumps({"error": "Missing 'user' key"})
86
87 # Edge Case: Handle non-string user name
88 if not isinstance(data["user"], str):
89 return json.dumps({"error": "User name must be a string"})
90
91 # Edge Case: Handle empty user name
92 if not data["user"]:
93 return json.dumps({"error": "User name cannot be empty"})
94
95 # Edge Case: Handle duplicate user - return existing user instead of error
96 for user in self.database["users"]:
97 if user["name"] == data["user"]:
98 return json.dumps(self._create_user_object(user))
99
100 # Create new user
101 new_user = {
102 "name": data["user"],
103 "owes": {},
104 "owed_by": {},
105 "balance": 0.0
106 }
107 self.database["users"].append(new_user)
108
109 return json.dumps(self._create_user_object(new_user))
110
111 elif url == "/iou":
112 # Edge Case: Handle missing required keys
113 required_keys = ["lender", "borrower", "amount"]
114 for key in required_keys:
115 if key not in data:
116 return json.dumps({"error": f"Missing '{key}' key"})
117
118 lender_name = data["lender"]
119 borrower_name = data["borrower"]
120 amount = data["amount"]
121
122 # Edge Case: Handle non-string lender/borrower names
123 if not isinstance(lender_name, str) or not isinstance(borrower_name, str):
124 return json.dumps({"error": "Lender and borrower must be strings"})
125
126 # Edge Case: Handle empty lender/borrower names
127 if not lender_name or not borrower_name:
128 return json.dumps({"error": "Lender and borrower names cannot be empty"})
129
130 # Edge Case: Handle non-numeric amount
131 if not isinstance(amount, (int, float)):
132 return json.dumps({"error": "Amount must be a number"})
133
134 # Edge Case: Handle negative amount
135 if amount < 0:
136 return json.dumps({"error": "Amount cannot be negative"})
137
138 # Edge Case: Handle lender and borrower being the same person
139 if lender_name == borrower_name:
140 return json.dumps({"error": "Lender and borrower cannot be the same person"})
141
142 # Find lender and borrower, create them if they don't exist
143 lender = None
144 borrower = None
145
146 for user in self.database["users"]:
147 if user["name"] == lender_name:
148 lender = user
149 elif user["name"] == borrower_name:
150 borrower = user
151
152 # Create users if they don't exist
153 if lender is None:
154 lender = {
155 "name": lender_name,
156 "owes": {},
157 "owed_by": {},
158 "balance": 0.0
159 }
160 self.database["users"].append(lender)
161
162 if borrower is None:
163 borrower = {
164 "name": borrower_name,
165 "owes": {},
166 "owed_by": {},
167 "balance": 0.0
168 }
169 self.database["users"].append(borrower)
170
171 # Handle IOU with proper debt netting
172 # Check if there's an existing debt in the same direction
173 # (lender is owed by borrower)
174 if borrower_name in lender["owed_by"]:
175 # Add to existing debt
176 lender["owed_by"][borrower_name] += amount
177 borrower["owes"][lender_name] += amount
178 # Check if there's an existing debt in the opposite direction
179 # (borrower owes lender)
180 elif lender_name in borrower["owes"]:
181 existing_debt = borrower["owes"][lender_name]
182
183 if amount == existing_debt:
184 # Exact offset - remove both debts
185 del borrower["owes"][lender_name]
186 if borrower_name in lender["owed_by"]:
187 del lender["owed_by"][borrower_name]
188 elif amount > existing_debt:
189 # New IOU is larger - create reverse debt
190 del borrower["owes"][lender_name]
191 if borrower_name in lender["owed_by"]:
192 del lender["owed_by"][borrower_name]
193
194 # Create reverse debt: lender now owes borrower
195 reverse_amount = amount - existing_debt
196 lender["owes"][borrower_name] = reverse_amount
197 borrower["owed_by"][lender_name] = reverse_amount
198 else:
199 # New IOU is smaller - reduce existing debt
200 new_debt = existing_debt - amount
201 borrower["owes"][lender_name] = new_debt
202 lender["owed_by"][borrower_name] = new_debt
203 # Check if there's an existing debt in the reverse direction
204 # (borrower is owed by lender)
205 elif borrower_name in lender["owes"]:
206 existing_debt = lender["owes"][borrower_name]
207
208 if amount == existing_debt:
209 # Exact offset - remove both debts
210 del lender["owes"][borrower_name]
211 if lender_name in borrower["owed_by"]:
212 del borrower["owed_by"][lender_name]
213 elif amount > existing_debt:
214 # New IOU is larger - create reverse debt
215 del lender["owes"][borrower_name]
216 if lender_name in borrower["owed_by"]:
217 del borrower["owed_by"][lender_name]
218
219 # Create reverse debt: borrower now owes lender
220 reverse_amount = amount - existing_debt
221 borrower["owes"][lender_name] = reverse_amount
222 lender["owed_by"][borrower_name] = reverse_amount
223 else:
224 # New IOU is smaller - reduce existing debt
225 new_debt = existing_debt - amount
226 lender["owes"][borrower_name] = new_debt
227 borrower["owed_by"][lender_name] = new_debt
228 else:
229 # No existing debt, create new IOU
230 lender["owed_by"][borrower_name] = amount
231 borrower["owes"][lender_name] = amount
232
233 # Recalculate balances
234 self._update_balance(lender)
235 self._update_balance(borrower)
236
237 # Return updated user objects sorted by name
238 updated_users = sorted([lender, borrower], key=lambda u: u["name"])
239 user_objects = [self._create_user_object(user) for user in updated_users]
240
241 return json.dumps({"users": user_objects})
242
243 # Edge Case: Handle unknown URL
244 return json.dumps({"error": "Unknown endpoint"})
245
246 def _create_user_object(self, user: dict) -> dict:
247 """
248 Create a user object for API response.
249
250 Args:
251 user: User data from database.
252
253 Returns:
254 Formatted user object.
255 """
256 # Calculate balance as (total owed by others) - (total owed to others)
257 total_owed_by = sum(user["owed_by"].values())
258 total_owes = sum(user["owes"].values())
259 balance = round(total_owed_by - total_owes, 2)
260
261 return {
262 "name": user["name"],
263 "owes": user["owes"],
264 "owed_by": user["owed_by"],
265 "balance": balance
266 }
267
268 def _update_balance(self, user: dict) -> None:
269 """
270 Update the balance of a user based on their IOUs.
271
272 Args:
273 user: User data to update.
274 """
275 total_owed_by = sum(user["owed_by"].values())
276 total_owes = sum(user["owes"].values())
277 user["balance"] = round(total_owed_by - total_owes, 2)
278
279# Handled Edge Cases: None database, missing payload, invalid JSON, missing 'users' key, non-list 'users', non-existent users, unknown URL, missing 'user' key, non-string user name, empty user name, duplicate user, missing required keys, non-string names, empty names, non-numeric amount, negative amount, same lender/borrower, user not found
Test NameStatus
test_add_user
Pass
test_borrower_has_negative_balance
Pass
test_both_users_have_0_balance
Pass
test_get_single_user
Pass
test_lender_has_negative_balance
Pass
test_lender_owes_borrower
Pass
test_lender_owes_borrower_less_than_new_loan
Pass
test_lender_owes_borrower_same_as_new_loan
Pass
test_no_users
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.