Init Rocket example

This commit is contained in:
Sam Samai 2021-08-12 22:05:06 +10:00
parent 2e9c19dc97
commit 53bbf32229
8 changed files with 223 additions and 0 deletions

View File

@ -0,0 +1,30 @@
[package]
name = "rocket-example"
version = "0.1.0"
edition = "2018"
publish = false
[workspace]
[dependencies]
# rocket = { path = "../../../Rocket/core/lib", features = ["json"] }
rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "master", features = [
"json",
] }
# async-std = { version = "^1.9", features = ["attributes"] }
sea-orm = { path = "../../", features = [
"sqlx-all",
# "runtime-async-std-native-tls",
] }
serde_json = { version = "^1" }
futures = { version = "^0.3" }
async-stream = { version = "^0.3" }
futures-util = { version = "^0.3" }
[dependencies.sqlx]
version = "0.5.1"
default-features = false
features = ["macros", "offline", "migrate"]
[dependencies.rocket_db_pools]
git = "https://github.com/SergioBenitez/Rocket"
branch = "master"
features = ["sqlx_sqlite"]

View File

@ -0,0 +1,3 @@
# Rocket with SeaOrm example app
`cargo run` in the `rocket_example` folder

View File

@ -0,0 +1,2 @@
[default.databases.sqlx]
url = "db/sqlx/db.sqlite"

Binary file not shown.

View File

@ -0,0 +1,6 @@
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR NOT NULL,
text VARCHAR NOT NULL,
published BOOLEAN NOT NULL DEFAULT 0
);

View File

@ -0,0 +1,14 @@
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_db_pools;
#[cfg(test)]
mod tests;
mod sqlx;
#[launch]
fn rocket() -> _ {
rocket::build().attach(sqlx::stage())
}

View File

@ -0,0 +1,90 @@
use rocket::{Rocket, Build, futures};
use rocket::fairing::{self, AdHoc};
use rocket::response::status::Created;
use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket_db_pools::{sqlx, Database, Connection};
use futures::{stream::TryStreamExt, future::TryFutureExt};
#[derive(Database)]
#[database("sqlx")]
struct Db(sqlx::SqlitePool);
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Post {
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
id: Option<i64>,
title: String,
text: String,
}
#[post("/", data = "<post>")]
async fn create(mut db: Connection<Db>, post: Json<Post>) -> Result<Created<Json<Post>>> {
// There is no support for `RETURNING`.
sqlx::query!("INSERT INTO posts (title, text) VALUES (?, ?)", post.title, post.text)
.execute(&mut *db)
.await?;
Ok(Created::new("/").body(post))
}
#[get("/")]
async fn list(mut db: Connection<Db>) -> Result<Json<Vec<i64>>> {
let ids = sqlx::query!("SELECT id FROM posts")
.fetch(&mut *db)
.map_ok(|record| record.id)
.try_collect::<Vec<_>>()
.await?;
Ok(Json(ids))
}
#[get("/<id>")]
async fn read(mut db: Connection<Db>, id: i64) -> Option<Json<Post>> {
sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id)
.fetch_one(&mut *db)
.map_ok(|r| Json(Post { id: Some(r.id), title: r.title, text: r.text }))
.await
.ok()
}
#[delete("/<id>")]
async fn delete(mut db: Connection<Db>, id: i64) -> Result<Option<()>> {
let result = sqlx::query!("DELETE FROM posts WHERE id = ?", id)
.execute(&mut *db)
.await?;
Ok((result.rows_affected() == 1).then(|| ()))
}
#[delete("/")]
async fn destroy(mut db: Connection<Db>) -> Result<()> {
sqlx::query!("DELETE FROM posts").execute(&mut *db).await?;
Ok(())
}
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
match Db::fetch(&rocket) {
Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await {
Ok(_) => Ok(rocket),
Err(e) => {
error!("Failed to initialize SQLx database: {}", e);
Err(rocket)
}
}
None => Err(rocket),
}
}
pub fn stage() -> AdHoc {
AdHoc::on_ignite("SQLx Stage", |rocket| async {
rocket.attach(Db::init())
.attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations))
.mount("/sqlx", routes![list, create, read, delete, destroy])
})
}

View File

@ -0,0 +1,78 @@
use rocket::fairing::AdHoc;
use rocket::local::blocking::Client;
use rocket::serde::{Serialize, Deserialize};
use rocket::http::Status;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Post {
title: String,
text: String,
}
fn test(base: &str, stage: AdHoc) {
// Number of posts we're going to create/read/delete.
const N: usize = 20;
// NOTE: If we had more than one test running concurently that dispatches
// DB-accessing requests, we'd need transactions or to serialize all tests.
let client = Client::tracked(rocket::build().attach(stage)).unwrap();
// Clear everything from the database.
assert_eq!(client.delete(base).dispatch().status(), Status::Ok);
assert_eq!(client.get(base).dispatch().into_json::<Vec<i64>>(), Some(vec![]));
// Add some random posts, ensure they're listable and readable.
for i in 1..=N{
let title = format!("My Post - {}", i);
let text = format!("Once upon a time, at {}'o clock...", i);
let post = Post { title: title.clone(), text: text.clone() };
// Create a new post.
let response = client.post(base).json(&post).dispatch().into_json::<Post>();
assert_eq!(response.unwrap(), post);
// Ensure the index shows one more post.
let list = client.get(base).dispatch().into_json::<Vec<i64>>().unwrap();
assert_eq!(list.len(), i);
// The last in the index is the new one; ensure contents match.
let last = list.last().unwrap();
let response = client.get(format!("{}/{}", base, last)).dispatch();
assert_eq!(response.into_json::<Post>().unwrap(), post);
}
// Now delete all of the posts.
for _ in 1..=N {
// Get a valid ID from the index.
let list = client.get(base).dispatch().into_json::<Vec<i64>>().unwrap();
let id = list.get(0).expect("have post");
// Delete that post.
let response = client.delete(format!("{}/{}", base, id)).dispatch();
assert_eq!(response.status(), Status::Ok);
}
// Ensure they're all gone.
let list = client.get(base).dispatch().into_json::<Vec<i64>>().unwrap();
assert!(list.is_empty());
// Trying to delete should now 404.
let response = client.delete(format!("{}/{}", base, 1)).dispatch();
assert_eq!(response.status(), Status::NotFound);
}
#[test]
fn test_sqlx() {
test("/sqlx", crate::sqlx::stage())
}
#[test]
fn test_diesel() {
test("/diesel", crate::diesel_sqlite::stage())
}
#[test]
fn test_rusqlite() {
test("/rusqlite", crate::rusqlite::stage())
}