This repository has been archived by the owner on Sep 14, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
/
main.rs
240 lines (206 loc) · 6.88 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#![feature(plugin)]
#![allow(unused_variables)]
#![allow(unused_mut)]
#![allow(unused_imports)]
#![allow(unused_must_use)]
#![allow(dead_code)]
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate serde_derive;
use serde::Serialize;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use bincode::{deserialize, serialize};
use maud::{html, Markup};
use rocket::response::status;
use rocket::response::NamedFile;
use rocket::State;
use rocket_contrib::json::Json;
use sled::{ConfigBuilder, Tree};
fn main() {
let path = String::from("data.db");
let conf = sled::ConfigBuilder::new().path(path).build();
let tree = Tree::start(conf).unwrap();
let db_arc = Arc::new(tree);
let routes = all_routes();
rocket::ignite().mount("/", routes).manage(db_arc).launch();
}
fn all_routes() -> Vec<rocket::Route> {
routes![
index,
static_file,
ugly_hack,
create_task,
get_task,
get_tasks,
update_all_tasks
]
}
#[derive(Serialize, Deserialize, Debug)]
struct Task {
completed: bool,
description: String,
editing: bool,
}
/// This is the entrypoint for our yew client side app.
#[get("/")]
fn index(db: State<Arc<sled::Tree>>) -> Markup {
// maud macro
html! {
link rel="stylesheet" href="static/styles.css" {}
body {}
// yew-generated javascript attaches to <body>
script src=("static/ui.js") {}
}
}
/// Serve static assets from the "static" folder.
#[get("/static/<path..>")]
fn static_file(path: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(path)).ok()
}
// TODO: remove this when we figure out how to change the native Rust
// WebAssembly's generated JavaScript code to point at "static/" prefix.
#[get("/ui.wasm")]
fn ugly_hack() -> Option<NamedFile> {
NamedFile::open(Path::new("static/ui.wasm")).ok()
}
/// Create a new task. The database id will be automatically assigned.
#[post("/task", format = "application/json", data = "<task>")]
fn create_task(db: State<Arc<sled::Tree>>, task: Json<Task>) -> status::Accepted<String> {
println!("got a task {:?}", task);
// scan through our DB to get create an incremented ID.
let mut count = 0;
for item in db.iter() {
if item.is_ok() {
count += 1;
}
}
// Keys and Values in sled are Vec<u8>
let new_key = vec![count];
// Our task is the first field (e.g., "0") on Json<Task> Rocket passes us.
let encoded: Vec<u8> = serialize(&task.0).unwrap();
db.set(new_key, encoded);
status::Accepted(Some(format!("success")))
}
/// Return all tasks or an empty Vec, which is valid.
#[get("/tasks")]
fn get_tasks(db: State<Arc<sled::Tree>>) -> Json<Vec<Task>> {
let mut results: Vec<Task> = Vec::new();
for item in db.iter() {
match item {
Ok((_, v)) => {
let decoded: Task = deserialize(&v[..]).expect("could not deserialize Task");
results.push(decoded);
}
_ => {}
}
}
Json(results)
}
/// Update all tasks with a Vec<Task>.
#[post("/tasks", format = "application/json", data = "<tasks>")]
fn update_all_tasks(
db: State<Arc<sled::Tree>>,
tasks: Json<Vec<Task>>,
) -> status::Accepted<String> {
// get len
let mut count = 0;
for item in db.iter() {
match item {
Ok(_) => count += 1,
_ => {}
}
}
// delete everything
for k in 0..count {
db.del(&vec![k as u8]).expect("delete failed");
}
// update everything
for (i, ref v) in tasks.0.into_iter().enumerate() {
let encoded: Vec<u8> = serialize(v).unwrap();
let key = vec![i as u8];
db.set(key, encoded);
}
status::Accepted(Some(format!("success")))
}
/// Get a task by id.
#[get("/task/<id>")]
fn get_task(db: State<Arc<sled::Tree>>, id: u8) -> Option<Json<Task>> {
let val = db.get(&vec![id]);
match val {
Ok(Some(db_vec)) => {
let decoded: Task = deserialize(&db_vec[..]).expect("unable to decode Task");
Some(Json(decoded))
}
_ => None,
}
}
/// Update a task by id.
#[put("/task/<id>", format = "application/json", data = "<task>")]
fn update_task(db: State<Arc<sled::Tree>>, id: u8, task: Json<Task>) -> status::Accepted<String> {
let key = vec![id];
let encoded: Vec<u8> = serialize(&task.0).unwrap();
db.cas(key, None, Some(encoded));
status::Accepted(Some(format!("format")))
}
/// Create an instance of Rocket suitable for tests.
fn test_instance(db_path: PathBuf) -> rocket::Rocket {
let conf = sled::ConfigBuilder::new()
.path(String::from(db_path.to_str().unwrap()))
.build();
let tree = Tree::start(conf).unwrap();
let db_arc = Arc::new(tree);
rocket::ignite().mount("/", all_routes()).manage(db_arc)
}
#[test]
fn test_post_get() {
use rocket::http::{ContentType, Status};
use rocket::local::Client;
use tempdir::TempDir;
let dir = TempDir::new("rocket").unwrap();
let path = dir.path().join("test_data.db");
// create our test client
let c = Client::new(test_instance(path)).unwrap();
// create a new task with raw json string body
let req = c
.post("/task")
.body(r#"{"completed": false, "description": "foo", "editing": false}"#)
.header(ContentType::JSON);
let resp = req.dispatch();
assert_eq!(resp.status(), Status::Accepted);
let req = c.get("/task/0");
let bod = req.dispatch().body_bytes().unwrap();
let decoded: Task = serde_json::from_slice(&bod[..])
.expect("not a valid task; if your model has changed, try deleting your database file");
assert_eq!(&decoded.description, "foo");
// create another Task and let serde_json handle serialization
let task = Task {
description: String::from("baz"),
completed: true,
editing: false,
};
let req = c
.post("/task")
.body(serde_json::to_vec(&task).unwrap())
.header(ContentType::JSON);
let resp = req.dispatch();
assert_eq!(resp.status(), Status::Accepted);
// we expect our next task to have id 1
let req = c.get("/task/1");
let bod = req.dispatch().body_bytes().unwrap();
let decoded: Task = serde_json::from_slice(&bod[..]).expect("not a valid task");
assert_eq!(decoded.description, "baz");
assert_eq!(decoded.completed, true);
// now fetch both tasks from /tasks
let req = c.get("/tasks");
let bod = req.dispatch().body_bytes().unwrap();
let tasks: Vec<Task> = serde_json::from_slice(&bod[..]).expect("not an array of Task");
assert_eq!(tasks.len(), 2);
// Test that they come back in the order we expect, with the data we expect.
let foo_task = tasks.get(0).unwrap();
let baz_task = tasks.get(1).unwrap();
assert_eq!(foo_task.description, "foo");
assert_eq!(baz_task.description, "baz");
}