sourdough

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 or None to initialize with an empty database.
10 """
11 if database is None:
12 self.database = {"users": []}
13 else:
14 self.database = database
15
516 def get(self, url: str, payload: str | None = None) -> str:
6 pass
7
17 """
18 Handle GET requests to the API.
19
20 Args:
21 url: The endpoint URL.
22 payload: Optional JSON payload.
23
24 Returns:
25 A JSON string response.
26
27 Raises:
28 ValueError: If the URL is not recognized.
29 """
30 if url == "/users":
31 if payload is None:
32 # Return all users
33 users = [self._create_user_object(user) for user in self.database["users"]]
34 return json.dumps({"users": users})
35 else:
36 # Return specific users
37 data = json.loads(payload)
38 user_names = data.get("users", [])
39
40 # Edge Case: Empty user list in payload
41 if not user_names:
42 return json.dumps({"users": []})
43
44 # Edge Case: Non-existent users in payload
45 result_users = []
46 for name in user_names:
47 user = self._find_user(name)
48 if user:
49 result_users.append(self._create_user_object(user))
50
51 # Sort by name
52 result_users.sort(key=lambda u: u["name"])
53 return json.dumps({"users": result_users})
54 else:
55 # Edge Case: Invalid URL
56 raise ValueError(f"Invalid URL: {url}")
57
858 def post(self, url: str, payload: str | None = None) -> str:
9 pass
59 """
60 Handle POST requests to the API.
61
62 Args:
63 url: The endpoint URL.
64 payload: JSON payload.
65
66 Returns:
67 A JSON string response.
68
69 Raises:
70 ValueError: If the URL is not recognized or if there are validation errors.
71 """
72 if url == "/add":
73 if payload is None:
74 # Edge Case: Missing payload
75 raise ValueError("Payload required for /add endpoint")
76
77 data = json.loads(payload)
78 user_name = data.get("user")
79
80 # Edge Case: Missing user name
81 if not user_name:
82 raise ValueError("User name is required")
83
84 # Edge Case: User already exists
85 if self._find_user(user_name):
86 raise ValueError(f"User {user_name} already exists")
87
88 new_user = {
89 "name": user_name,
90 "owes": {},
91 "owed_by": {},
92 "balance": 0.0
93 }
94
95 self.database["users"].append(new_user)
96 return json.dumps(self._create_user_object(new_user))
97
98 elif url == "/iou":
99 if payload is None:
100 # Edge Case: Missing payload
101 raise ValueError("Payload required for /iou endpoint")
102
103 data = json.loads(payload)
104 lender_name = data.get("lender")
105 borrower_name = data.get("borrower")
106 amount = data.get("amount")
107
108 # Edge Case: Missing required fields
109 if not lender_name or not borrower_name or amount is None:
110 raise ValueError("Lender, borrower, and amount are required")
111
112 # Edge Case: Negative amount
113 if amount < 0:
114 raise ValueError("Amount must be non-negative")
115
116 # Edge Case: Lender and borrower are the same
117 if lender_name == borrower_name:
118 raise ValueError("Lender and borrower cannot be the same person")
119
120 lender = self._find_user(lender_name)
121 borrower = self._find_user(borrower_name)
122
123 # Edge Case: Non-existent lender or borrower
124 if not lender:
125 raise ValueError(f"User {lender_name} does not exist")
126 if not borrower:
127 raise ValueError(f"User {borrower_name} does not exist")
128
129 # Calculate the net debt between lender and borrower
130 # Net debt = (amount borrower owes lender) - (amount lender owes borrower) + new amount
131 current_debt = borrower["owes"].get(lender_name, 0) - lender["owes"].get(borrower_name, 0)
132 new_debt = current_debt + amount
133
134 # Remove any existing debt entries between these two users
135 if borrower_name in lender["owes"]:
136 del lender["owes"][borrower_name]
137 if lender_name in borrower["owes"]:
138 del borrower["owes"][lender_name]
139 if borrower_name in lender["owed_by"]:
140 del lender["owed_by"][borrower_name]
141 if lender_name in borrower["owed_by"]:
142 del borrower["owed_by"][lender_name]
143
144 # Set up new debt relationship based on the net amount
145 if new_debt > 0:
146 # Borrower owes lender
147 borrower["owes"][lender_name] = new_debt
148 lender["owed_by"][borrower_name] = new_debt
149 elif new_debt < 0:
150 # Lender owes borrower
151 lender["owes"][borrower_name] = -new_debt
152 borrower["owed_by"][lender_name] = -new_debt
153 # If new_debt == 0, no debts need to be recorded (both entries remain deleted)
154
155 # Update balances
156 lender["balance"] += amount
157 borrower["balance"] -= amount
158
159 # Update balances
160 lender["balance"] += amount
161 borrower["balance"] -= amount
162
163 # Create response with updated users
164 updated_users = [
165 self._create_user_object(lender),
166 self._create_user_object(borrower)
167 ]
168 updated_users.sort(key=lambda u: u["name"])
169
170 return json.dumps({"users": updated_users})
171 else:
172 # Edge Case: Invalid URL
173 raise ValueError(f"Invalid URL: {url}")
174
175 def _find_user(self, name: str):
176 """
177 Find a user by name in the database.
178
179 Args:
180 name: The name of the user to find.
181
182 Returns:
183 The user dictionary if found, None otherwise.
184 """
185 for user in self.database["users"]:
186 if user["name"] == name:
187 return user
188 return None
189
190 def _create_user_object(self, user: dict):
191 """
192 Create a user object for API responses.
193
194 Args:
195 user: The user dictionary from the database.
196
197 Returns:
198 A dictionary representing the user object for API responses.
199 """
200 # Calculate balance as (total owed by others) - (total owed to others)
201 total_owed_by = sum(user["owed_by"].values())
202 total_owes = sum(user["owes"].values())
203 balance = float(round(total_owed_by - total_owes, 2))
204
205 return {
206 "name": user["name"],
207 "owes": user["owes"],
208 "owed_by": user["owed_by"],
209 "balance": balance
210 }
211# Handled Edge Cases: Invalid URL, Missing payload, Missing user name, User already exists, Missing required fields, Negative amount, Lender and borrower are the same, Non-existent lender or borrower, Empty user list in payload, Non-existent users in payload
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.