Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/generate-spec
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ canonical_data = json.decode read_file(canonical_data_path), 1, json.null
tests_toml_path = exercise_directory .. '/.meta/tests.toml'
included_tests = included_tests_from_toml tests_toml_path

package.moonpath = "#{exercise_directory}/.meta/?.moon;#{package.moonpath}"
package.moonpath = "#{exercise_directory}/.meta/?.moon;./lib/?.moon;#{package.moonpath}"
spec_generator = require 'spec_generator'

local spec
Expand Down
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,14 @@
"prerequisites": [],
"difficulty": 7
},
{
"slug": "rest-api",
"name": "REST API",
"uuid": "a999597a-d5db-4a72-83f7-133a2b7f3add",
"practices": [],
"prerequisites": [],
"difficulty": 7
},
{
"slug": "satellite",
"name": "Satellite",
Expand Down
5 changes: 5 additions & 0 deletions exercises/practice/rest-api/.busted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
return {
default = {
ROOT = { '.' }
}
}
48 changes: 48 additions & 0 deletions exercises/practice/rest-api/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Instructions

Implement a RESTful API for tracking IOUs.

Four roommates have a habit of borrowing money from each other frequently, and have trouble remembering who owes whom, and how much.

Your task is to implement a simple [RESTful API][restful-wikipedia] that receives [IOU][iou]s as POST requests, and can deliver specified summary information via GET requests.

## API Specification

### User object

```json
{
"name": "Adam",
"owes": {
"Bob": 12.0,
"Chuck": 4.0,
"Dan": 9.5
},
"owed_by": {
"Bob": 6.5,
"Dan": 2.75
},
"balance": "<(total owed by other users) - (total owed to other users)>"
}
```

### Methods

| Description | HTTP Method | URL | Payload Format | Response w/o Payload | Response w/ Payload |
| ------------------------ | ----------- | ------ | ------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------- |
| 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)}` |
| Create user | POST | /add | `{"user":<name of new user (unique)>}` | N/A | `<User object for new user>` |
| 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)>}` |

## Other Resources

- [REST API Tutorial][restfulapi]
- Example RESTful APIs
- [GitHub][github-rest]
- [Reddit][reddit-rest]

[restful-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer
[iou]: https://en.wikipedia.org/wiki/IOU
[github-rest]: https://docs.github.com/en/rest
[reddit-rest]: https://web.archive.org/web/20231202231149/https://www.reddit.com/dev/api/
[restfulapi]: https://restfulapi.net/
17 changes: 17 additions & 0 deletions exercises/practice/rest-api/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"glennj"
],
"files": {
"solution": [
"rest_api.moon"
],
"test": [
"rest_api_spec.moon"
],
"example": [
".meta/example.moon"
]
},
"blurb": "Implement a RESTful API for tracking IOUs."
}
63 changes: 63 additions & 0 deletions exercises/practice/rest-api/.meta/example.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
contains = (list, elem) ->
for item in *list
return true if item == elem
false

sort_users = (list) ->
table.sort list, (a, b) -> a.name < b.name
list

-- The tests do not require the payload to be validated.
-- Also, I'm not concerned about accepting references to user's tables,
-- or returning references to internal tables.

class RestApi
new: (@db) =>

GET: (url, payload) => @route 'GET', url, payload
POST: (url, payload) => @route 'POST', url, payload

route: (method, url, payload) =>
subject = url\match '^/(%w+)'
func = "#{subject}_#{method}"
return {error: "Unknown endpoint: #{method} #{url}"} if not @[func]
@[func] @, payload

users_GET: (payload) =>
filter = if payload
(user) -> contains payload.users, user.name
else
(user) -> true
{users: sort_users [u for u in *@db.users when filter u]}

add_POST: (payload) =>
user = {
name: payload.user
balance: 0
owes: {}
owed_by: {}
}
table.insert @db.users, user
user

iou_POST: (payload) =>
{users: {lender}} = @users_GET {users: {payload.lender}}
{users: {borrower}} = @users_GET {users: {payload.borrower}}
borrower.balance -= payload.amount
lender.balance += payload.amount

debt = (lender.owes[borrower.name] or 0) - (lender.owed_by[borrower.name] or 0) - payload.amount

lender.owes[borrower.name] = nil
lender.owed_by[borrower.name] = nil
borrower.owes[lender.name] = nil
borrower.owed_by[lender.name] = nil

if debt > 0
lender.owes[borrower.name] = debt
borrower.owed_by[lender.name] = debt
elseif debt < 0
lender.owed_by[borrower.name] = -debt
borrower.owes[lender.name] = -debt

{users: sort_users {lender, borrower}}
25 changes: 25 additions & 0 deletions exercises/practice/rest-api/.meta/spec_generator.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
test_helpers = require 'test_helpers'
import table_dump from test_helpers

{
module_name: 'RestApi',

generate_test: (case, level) ->
lines = {
"database = #{table_dump case.input.database, level}",
"api = RestApi database"
}
call = "result = api\\#{case.property\upper!} #{quote case.input.url}"
if case.input.payload
table.insert lines, "payload = #{table_dump case.input.payload, level}"
call ..= ", payload"
table.insert lines, call
table.insert lines, "expected = #{table_dump case.expected, level}"
table.insert lines, "assert.are.same expected, result"
table.concat [indent line, level for line in *lines], '\n'

-- we have deep tables to compare, display it all when not the same
test_helpers: [[
assert\set_parameter "TableFormatLevel", 4
]]
}
37 changes: 37 additions & 0 deletions exercises/practice/rest-api/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[5be01ffb-a814-47a8-a19f-490a5622ba07]
description = "user management -> no users"

[382b70cc-9f6c-486d-9bee-fda2df81c803]
description = "user management -> add user"

[d624e5e5-1abb-4f18-95b3-45d55c818dc3]
description = "user management -> get single user"

[7a81b82c-7276-433e-8fce-29ce983a7c56]
description = "iou -> both users have 0 balance"

[1c61f957-cf8c-48ba-9e77-b221ab068803]
description = "iou -> borrower has negative balance"

[8a8567b3-c097-468a-9541-6bb17d5afc85]
description = "iou -> lender has negative balance"

[29fb7c12-7099-4a85-a7c4-9c290d2dc01a]
description = "iou -> lender owes borrower"

[ce969e70-163c-4135-a4a6-2c3a5da286f5]
description = "iou -> lender owes borrower less than new loan"

[7f4aafd9-ae9b-4e15-a406-87a87bdf47a4]
description = "iou -> lender owes borrower same as new loan"
1 change: 1 addition & 0 deletions exercises/practice/rest-api/rest_api.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
error 'Implement me'
Loading
Loading