Finished
1import json
2from typing import Dict, List, Optional, Any
3
14class RestAPI:
5 """
6 A RESTful API for tracking IOUs between users.
7
8 This class manages user data and IOU transactions in memory.
9 """
10
211 def __init__(self, database: dict | None = None):
3 pass
4
12 """
13 Initialize the RestAPI with an optional database.
14
15 Args:
16 database: Optional initial database dictionary with user data
17 """
18 if database is None:
19 self.database = {"users": []}
20 else:
21 self.database = database
22
523 def get(self, url: str, payload: str | None = None) -> str:
6 pass
7
24 """
25 Handle GET requests to the API.
26
27 Args:
28 url: The endpoint URL
29 payload: Optional JSON payload
30
31 Returns:
32 JSON string response
33 """
34 if url == "/users":
35 # Edge Case: Handle request with no payload
36 if payload is None:
37 return json.dumps({"users": self._get_all_users()})
38
39 # Edge Case: Handle request with payload
40 try:
41 data = json.loads(payload)
42 if "users" in data:
43 return json.dumps({"users": self._get_specific_users(data["users"])})
44 else:
45 # Edge Case: Malformed payload
46 return json.dumps({"error": "Invalid payload format"})
47 except json.JSONDecodeError:
48 # Edge Case: Invalid JSON payload
49 return json.dumps({"error": "Invalid JSON payload"})
50
51 # Edge Case: Unknown endpoint
52 return json.dumps({"error": "Unknown endpoint"})
53
854 def post(self, url: str, payload: str | None = None) -> str:
9 pass
55 """
56 Handle POST requests to the API.
57
58 Args:
59 url: The endpoint URL
60 payload: JSON payload
61
62 Returns:
63 JSON string response
64 """
65 if payload is None:
66 # Edge Case: Missing payload
67 return json.dumps({"error": "Payload required"})
68
69 try:
70 data = json.loads(payload)
71 except json.JSONDecodeError:
72 # Edge Case: Invalid JSON payload
73 return json.dumps({"error": "Invalid JSON payload"})
74
75 if url == "/add":
76 # Edge Case: Missing user field
77 if "user" not in data:
78 return json.dumps({"error": "User field required"})
79
80 user_name = data["user"]
81
82 # Edge Case: User already exists
83 if self._find_user(user_name) is not None:
84 return json.dumps({"error": "User already exists"})
85
86 new_user = {
87 "name": user_name,
88 "owes": {},
89 "owed_by": {},
90 "balance": 0.0
91 }
92 self.database["users"].append(new_user)
93 return json.dumps(new_user)
94
95 elif url == "/iou":
96 # Edge Case: Missing required fields
97 required_fields = ["lender", "borrower", "amount"]
98 for field in required_fields:
99 if field not in data:
100 return json.dumps({"error": f"{field} field required"})
101
102 lender_name = data["lender"]
103 borrower_name = data["borrower"]
104 amount = data["amount"]
105
106 # Edge Case: Invalid amount type
107 if not isinstance(amount, (int, float)):
108 return json.dumps({"error": "Amount must be a number"})
109
110 # Edge Case: Negative amount
111 if amount < 0:
112 return json.dumps({"error": "Amount must be positive"})
113
114 # Edge Case: Lender does not exist
115 lender = self._find_user(lender_name)
116 if lender is None:
117 return json.dumps({"error": "Lender does not exist"})
118
119 # Edge Case: Borrower does not exist
120 borrower = self._find_user(borrower_name)
121 if borrower is None:
122 return json.dumps({"error": "Borrower does not exist"})
123
124 # Edge Case: Lender and borrower are the same
125 if lender_name == borrower_name:
126 return json.dumps({"error": "Cannot create IOU between same user"})
127
128 # Update lender's owed_by
129 if borrower_name in lender["owed_by"]:
130 lender["owed_by"][borrower_name] += amount
131 else:
132 lender["owed_by"][borrower_name] = amount
133
134 # Update borrower's owes
135 if lender_name in borrower["owes"]:
136 borrower["owes"][lender_name] += amount
137 else:
138 borrower["owes"][lender_name] = amount
139
140 # Recalculate balances
141 self._recalculate_balance(lender)
142 self._recalculate_balance(borrower)
143
144 # Return updated users sorted by name
145 return json.dumps({
146 "users": sorted([lender, borrower], key=lambda x: x["name"])
147 })
148
149 # Edge Case: Unknown endpoint
150 return json.dumps({"error": "Unknown endpoint"})
151
152 def _find_user(self, name: str) -> Optional[Dict]:
153 """
154 Find a user by name in the database.
155
156 Args:
157 name: The name of the user to find
158
159 Returns:
160 User dictionary if found, None otherwise
161 """
162 for user in self.database["users"]:
163 if user["name"] == name:
164 return user
165 return None
166
167 def _recalculate_balance(self, user: Dict) -> None:
168 """
169 Recalculate the balance for a user based on their owes and owed_by.
170
171 Args:
172 user: The user dictionary to update
173 """
174 total_owed_by = sum(user["owed_by"].values())
175 total_owes = sum(user["owes"].values())
176 user["balance"] = round(total_owed_by - total_owes, 2)
177
178 def _get_all_users(self) -> List[Dict]:
179 """
180 Get all users with recalculated balances.
181
182 Returns:
183 List of all user dictionaries
184 """
185 for user in self.database["users"]:
186 self._recalculate_balance(user)
187 return sorted(self.database["users"], key=lambda x: x["name"])
188
189 def _get_specific_users(self, names: List[str]) -> List[Dict]:
190 """
191 Get specific users by name with recalculated balances.
192
193 Args:
194 names: List of user names to retrieve
195
196 Returns:
197 List of user dictionaries
198 """
199 result = []
200 for name in names:
201 user = self._find_user(name)
202 if user is not None:
203 self._recalculate_balance(user)
204 result.append(user)
205 return sorted(result, key=lambda x: x["name"])
206
207 # Handled Edge Cases: Unknown endpoint, Missing payload, Invalid JSON payload, Missing user field, User already exists
208 # Handled Edge Cases: Missing required fields, Invalid amount type, Negative amount, Lender does not exist, Borrower does not exist, Lender and borrower are the same
209 # Handled Edge Cases: Malformed payload
Test NameStatus
test_lender_owes_borrower
Fail
test_lender_owes_borrower_less_than_new_loan
Fail
test_lender_owes_borrower_same_as_new_loan
Fail
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_no_users
Pass

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