Skip to content

Commit 5e95900

Browse files
authored
Implement rest-api exercise (exercism#1818)
* Initial skeleton for rest-api implementation. * Continue work on rest-api. * Add initial files for rest-api src. * Complete initial implementation of rest-api. * Fix style violations in rest-api exercise. * Fix the tests to use the constructor properly for rest-api. * Fix the tests and reference implementation for rest-api.
1 parent 213f578 commit 5e95900

File tree

12 files changed

+751
-0
lines changed

12 files changed

+751
-0
lines changed

config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,17 @@
781781
"matrices"
782782
]
783783
},
784+
{
785+
"slug": "rest-api",
786+
"uuid": "809c0e3d-3494-4a85-843d-2bafa8752ce8",
787+
"core": false,
788+
"unlocked_by": "robot-name",
789+
"difficulty": 6,
790+
"topics": [
791+
"strings",
792+
"parsing"
793+
]
794+
},
784795
{
785796
"slug": "roman-numerals",
786797
"uuid": "3e728cd4-5e5f-4c69-8a53-bc36d020fcdb",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* POJO representing an IOU.
3+
*
4+
* If this is in a user's "owed", then the user owes the person with {@link name} this {@link amount}.
5+
* If this is in a user's "owedBy", then {@link name} owes the user this {@link amount}.
6+
*/
7+
public class Iou {
8+
public final String name;
9+
public final double amount;
10+
11+
public Iou(String name, double amount) {
12+
this.name = name;
13+
this.amount = amount;
14+
}
15+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import org.json.JSONArray;
2+
import org.json.JSONObject;
3+
4+
import java.util.ArrayList;
5+
import java.util.Comparator;
6+
import java.util.HashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
public class RestApi {
11+
private final Map<String, User> usersByName = new HashMap<>();
12+
13+
public RestApi(User... users) {
14+
for (User user : users) {
15+
usersByName.put(user.name(), user);
16+
}
17+
}
18+
19+
public String get(String url) {
20+
if (url.equals("/users")) {
21+
return getAllUsers();
22+
}
23+
return "";
24+
}
25+
26+
private String getAllUsers() {
27+
JSONObject result = new JSONObject();
28+
JSONArray users = new JSONArray();
29+
usersByName.values().stream()
30+
.sorted(Comparator.comparing(User::name))
31+
.map(User::toJson)
32+
.forEach(users::put);
33+
result.put("users", users);
34+
return result.toString();
35+
}
36+
37+
public String get(String url, JSONObject payload) {
38+
if (url.equals("/users") && payload.has("users")) {
39+
return getUsers(payload.getJSONArray("users"));
40+
}
41+
return "";
42+
}
43+
44+
private String getUsers(JSONArray users) {
45+
return getUsers(getUserNames(users));
46+
}
47+
48+
private String getUsers(List<String> userNames) {
49+
JSONObject result = new JSONObject();
50+
JSONArray users = new JSONArray();
51+
userNames.stream()
52+
.sorted()
53+
.filter(usersByName::containsKey)
54+
.map(userName -> usersByName.get(userName).toJson())
55+
.forEach(users::put);
56+
result.put("users", users);
57+
return result.toString();
58+
}
59+
60+
private static List<String> getUserNames(JSONArray users) {
61+
List<String> names = new ArrayList<>();
62+
for (int i = 0; i < users.length(); i++) {
63+
names.add(users.getString(i));
64+
}
65+
return names;
66+
}
67+
68+
public String post(String url, JSONObject payload) {
69+
if (url.equals("/add")) {
70+
return addUser(payload);
71+
}
72+
if (url.equals("/iou")) {
73+
return addIou(payload);
74+
}
75+
return "";
76+
}
77+
78+
private String addUser(JSONObject payload) {
79+
if (payload.has("user")) {
80+
return addUser(payload.getString("user"));
81+
}
82+
return "";
83+
}
84+
85+
private String addUser(String name) {
86+
if (usersByName.containsKey(name)) {
87+
return "";
88+
}
89+
User user = User.builder().setName(name).build();
90+
usersByName.put(name, user);
91+
return user.toJson().toString();
92+
}
93+
94+
private String addIou(JSONObject payload) {
95+
if (payload.has("lender")
96+
&& payload.has("borrower")
97+
&& payload.has("amount")) {
98+
return addIou(
99+
payload.getString("lender"),
100+
payload.getString("borrower"),
101+
payload.getDouble("amount"));
102+
}
103+
return "";
104+
}
105+
106+
private String addIou(String lender, String borrower, double amount) {
107+
if (!usersByName.containsKey(lender)
108+
|| !usersByName.containsKey(borrower)
109+
|| amount <= 0.0) {
110+
return "";
111+
}
112+
updateLender(lender, borrower, amount);
113+
updateBorrower(lender, borrower, amount);
114+
return getUsers(List.of(lender, borrower));
115+
}
116+
117+
private void updateLender(String lenderName, String borrowerName, double amount) {
118+
User user = usersByName.get(lenderName);
119+
User.Builder lender = User.builder().setName(lenderName);
120+
copyUnaffectedEntries(lenderName, borrowerName, user, lender);
121+
double owedToBorrower = user.getAmountOwedTo(borrowerName);
122+
double owedByBorrower = user.getAmountOwedBy(borrowerName) + amount;
123+
if (owedToBorrower > owedByBorrower) {
124+
lender.owes(borrowerName, owedToBorrower - owedByBorrower);
125+
} else if (owedByBorrower > owedToBorrower) {
126+
lender.owedBy(borrowerName, owedByBorrower - owedToBorrower);
127+
}
128+
usersByName.put(lenderName, lender.build());
129+
}
130+
131+
private void updateBorrower(String lenderName, String borrowerName, double amount) {
132+
User user = usersByName.get(borrowerName);
133+
User.Builder borrower = User.builder().setName(borrowerName);
134+
copyUnaffectedEntries(lenderName, borrowerName, user, borrower);
135+
double owedToLender = user.getAmountOwedTo(lenderName) + amount;
136+
double owedByLender = user.getAmountOwedBy(lenderName);
137+
if (owedToLender > owedByLender) {
138+
borrower.owes(lenderName, owedToLender - owedByLender);
139+
} else if (owedByLender > owedToLender) {
140+
borrower.owedBy(lenderName, owedByLender - owedToLender);
141+
}
142+
usersByName.put(borrowerName, borrower.build());
143+
}
144+
145+
private static void copyUnaffectedEntries(String lender, String borrower, User user, User.Builder updated) {
146+
user.owes().stream()
147+
.filter(iou -> !isAffected(lender, borrower, iou))
148+
.forEach(iou -> updated.owes(iou.name, iou.amount));
149+
user.owedBy().stream()
150+
.filter(iou -> !isAffected(lender, borrower, iou))
151+
.forEach(iou -> updated.owedBy(iou.name, iou.amount));
152+
}
153+
154+
private static boolean isAffected(String lender, String borrower, Iou iou) {
155+
return iou.name.equals(lender) || iou.name.equals(borrower);
156+
}
157+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import static java.util.Collections.unmodifiableList;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.json.JSONObject;
7+
8+
/** POJO representing a User in the database. */
9+
public class User {
10+
private final String name;
11+
private final List<Iou> owes;
12+
private final List<Iou> owedBy;
13+
14+
private User(String name, List<Iou> owes, List<Iou> owedBy) {
15+
this.name = name;
16+
this.owes = new ArrayList<>(owes);
17+
this.owedBy = new ArrayList<>(owedBy);
18+
}
19+
20+
public String name() {
21+
return name;
22+
}
23+
24+
/** IOUs this user owes to other users. */
25+
public List<Iou> owes() {
26+
return unmodifiableList(owes);
27+
}
28+
29+
public double getAmountOwedTo(String other) {
30+
return owes().stream()
31+
.filter(iou -> iou.name.equals(other))
32+
.map(iou -> iou.amount)
33+
.findFirst()
34+
.orElse(0.0);
35+
}
36+
37+
/** IOUs other users owe to this user. */
38+
public List<Iou> owedBy() {
39+
return unmodifiableList(owedBy);
40+
}
41+
42+
public double getAmountOwedBy(String other) {
43+
return owedBy().stream()
44+
.filter(iou -> iou.name.equals(other))
45+
.map(iou -> iou.amount)
46+
.findFirst()
47+
.orElse(0.0);
48+
}
49+
50+
public static Builder builder() {
51+
return new Builder();
52+
}
53+
54+
public JSONObject toJson() {
55+
JSONObject object =
56+
new JSONObject()
57+
.put("name", name())
58+
.put("owes", owesToJson())
59+
.put("owedBy", owedByToJson())
60+
.put("balance", balance());
61+
return object;
62+
}
63+
64+
private JSONObject owesToJson() {
65+
return iousToJson(owes());
66+
}
67+
68+
private JSONObject owedByToJson() {
69+
return iousToJson(owedBy());
70+
}
71+
72+
private static JSONObject iousToJson(List<Iou> ious) {
73+
JSONObject object = new JSONObject();
74+
for (Iou iou : ious) {
75+
object.put(iou.name, iou.amount);
76+
}
77+
return object;
78+
}
79+
80+
private double balance() {
81+
double balance = 0.0;
82+
for (Iou iou : owes()) {
83+
balance -= iou.amount;
84+
}
85+
for (Iou iou : owedBy()) {
86+
balance += iou.amount;
87+
}
88+
return balance;
89+
}
90+
91+
public static class Builder {
92+
private String name;
93+
private final List<Iou> owes = new ArrayList<>();
94+
private final List<Iou> owedBy = new ArrayList<>();
95+
96+
public Builder setName(String name) {
97+
this.name = name;
98+
return this;
99+
}
100+
101+
public Builder owes(String name, double amount) {
102+
owes.add(new Iou(name, amount));
103+
return this;
104+
}
105+
106+
public Builder owedBy(String name, double amount) {
107+
owedBy.add(new Iou(name, amount));
108+
return this;
109+
}
110+
111+
public User build() {
112+
return new User(name, owes, owedBy);
113+
}
114+
}
115+
}

exercises/rest-api/.meta/version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.1.1

exercises/rest-api/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Implement a RESTful API for tracking IOUs.
2+
3+
Four roommates have a habit of borrowing money from each other frequently, and have trouble remembering who owes whom, and how much.
4+
5+
Your task is to implement a simple [RESTful API](https://en.wikipedia.org/wiki/Representational_state_transfer) that receives [IOU](https://en.wikipedia.org/wiki/IOU)s as POST requests, and can deliver specified summary information via GET requests.
6+
7+
### API Specification
8+
9+
#### User object
10+
```json
11+
{
12+
"name": "Adam",
13+
"owes": {
14+
"Bob": 12.0,
15+
"Chuck": 4.0,
16+
"Dan": 9.5
17+
},
18+
"owed_by": {
19+
"Bob": 6.5,
20+
"Dan": 2.75,
21+
},
22+
"balance": "<(total owed by other users) - (total owed to other users)>"
23+
}
24+
```
25+
26+
#### Methods
27+
28+
| Description | HTTP Method | URL | Payload Format | Response w/o Payload | Response w/ Payload |
29+
| --- | --- | --- | --- | --- | --- |
30+
| List of user information | GET | /users | `{"users":["Adam","Bob"]}` | `{"users":<List of all User objects>}` | `{"users":<List of User objects for <users> (sorted by name)}` |
31+
| Create user | POST | /add | `{"user":<name of new user (unique)>}` | N/A | `<User object for new user>` |
32+
| Create IOU | POST | /iou | `{"lender":<name of lender>,"borrower":<name of borrower>,"amount":5.25}` | N/A | `{"users":<updated User objects for <lender> and <borrower> (sorted by name)>}` |
33+
34+
### Other Resources:
35+
- https://restfulapi.net/
36+
- Example RESTful APIs
37+
- [GitHub](https://developer.github.com/v3/)
38+
- [Reddit](https://www.reddit.com/dev/api/)

exercises/rest-api/build.gradle

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apply plugin: "java"
2+
apply plugin: "eclipse"
3+
apply plugin: "idea"
4+
5+
repositories {
6+
mavenCentral()
7+
}
8+
9+
dependencies {
10+
compile 'org.json:json:20190722'
11+
testCompile "junit:junit:4.12"
12+
}
13+
14+
test {
15+
testLogging {
16+
exceptionFormat = 'short'
17+
showStandardStreams = true
18+
events = ["passed", "failed", "skipped"]
19+
}
20+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* POJO representing an IOU.
3+
*
4+
* If this is in a user's "owed", then the user owes the person with {@link name} this {@link amount}.
5+
* If this is in a user's "owedBy", then {@link name} owes the user this {@link amount}.
6+
*/
7+
public class Iou {
8+
public final String name;
9+
public final double amount;
10+
11+
public Iou(String name, double amount) {
12+
this.name = name;
13+
this.amount = amount;
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
3+
Since this exercise has a difficulty of > 4 it doesn't come
4+
with any starter implementation.
5+
This is so that you get to practice creating classes and methods
6+
which is an important part of programming in Java.
7+
8+
Please remove this comment when submitting your solution.
9+
10+
*/

0 commit comments

Comments
 (0)