From 53bbf32229f49519269b1a8b5b0905d443e51664 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 12 Aug 2021 22:05:06 +1000 Subject: [PATCH] Init Rocket example --- examples/rocket_example/Cargo.toml | 30 ++++++ examples/rocket_example/README.md | 3 + examples/rocket_example/Rocket.toml | 2 + examples/rocket_example/db/sqlx/db.sqlite | Bin 0 -> 20480 bytes .../20210331024424_create-posts-table.sql | 6 ++ examples/rocket_example/src/main.rs | 14 +++ examples/rocket_example/src/sqlx.rs | 90 ++++++++++++++++++ examples/rocket_example/src/tests.rs | 78 +++++++++++++++ 8 files changed, 223 insertions(+) create mode 100644 examples/rocket_example/Cargo.toml create mode 100644 examples/rocket_example/README.md create mode 100644 examples/rocket_example/Rocket.toml create mode 100644 examples/rocket_example/db/sqlx/db.sqlite create mode 100644 examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql create mode 100644 examples/rocket_example/src/main.rs create mode 100644 examples/rocket_example/src/sqlx.rs create mode 100644 examples/rocket_example/src/tests.rs diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml new file mode 100644 index 00000000..44080a96 --- /dev/null +++ b/examples/rocket_example/Cargo.toml @@ -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"] diff --git a/examples/rocket_example/README.md b/examples/rocket_example/README.md new file mode 100644 index 00000000..122bf09d --- /dev/null +++ b/examples/rocket_example/README.md @@ -0,0 +1,3 @@ +# Rocket with SeaOrm example app + +`cargo run` in the `rocket_example` folder diff --git a/examples/rocket_example/Rocket.toml b/examples/rocket_example/Rocket.toml new file mode 100644 index 00000000..300e46d3 --- /dev/null +++ b/examples/rocket_example/Rocket.toml @@ -0,0 +1,2 @@ +[default.databases.sqlx] +url = "db/sqlx/db.sqlite" diff --git a/examples/rocket_example/db/sqlx/db.sqlite b/examples/rocket_example/db/sqlx/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..a961169b0be0cf84bbad7a806bb1f0e842f1469c GIT binary patch literal 20480 zcmeI3&2JM&7>8&5k;Fm46-Dxa%ODgr5pm?T6A}<=vkqIZ>W|=TfLv;A))TVekKkRS zAgH2JdO&bRBE4|!q2h`}54}`srM8Dkz49+qs?=1KS_I>DV&ydxi4%wLo9re#o_#j+ zKK|KKHsi&MdDjpYt;Wqa4`j)@K7 zv^%q^_--&jk%4g*x1Q}-S(S7ztS(GqW3=%mM^uT&dY5??pl0{ z$qfw#U;qYS00v+H24DaNU;qYS00v;-T^kVEU{ue3RitDK}sf^rSdQTs(4ZMhb_x0$pheLCp-+OTXhY$7d@BeqZwfghsPtmUu zKOWz{aQU~hf6adL#gpe}?<;iv|1TH)vqOKr-PeKAfMQ?(24DaNU;qYS00v;-fE$P$8NE1ExWyObx^SN4 z=~PLsH0SMgxxU%vowddjTz(EXo%*QBom$hmJ~=tbSoj5j^wUa6KTV-;DKx8wW+~)R zXhsdqQ0QX{ol--mC={d6v>KWwQ0NZ|O{t+N3O%7vMh#^sbd5r3HI$~%G=+3Eq*I7d zNSz3sCPMq=L}^cq)PbOR&>j;+9S52QO%OyK2ATyeO%QbyXcE{ff~bQ)bHIKjh&l!| z1#F8T>JZQjuvLPnBR~_tG6dNt0DGSQ?)v|27T-SLZ+s{Z24DaNU;qYS00v+H24DaN TU;qZ*Hv=&(oX~=g5*&RE<&!)v literal 0 HcmV?d00001 diff --git a/examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql b/examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql new file mode 100644 index 00000000..11941923 --- /dev/null +++ b/examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql @@ -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 +); diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs new file mode 100644 index 00000000..18fc60b9 --- /dev/null +++ b/examples/rocket_example/src/main.rs @@ -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()) +} diff --git a/examples/rocket_example/src/sqlx.rs b/examples/rocket_example/src/sqlx.rs new file mode 100644 index 00000000..ffad0c73 --- /dev/null +++ b/examples/rocket_example/src/sqlx.rs @@ -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> = std::result::Result; + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +struct Post { + #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] + id: Option, + title: String, + text: String, +} + +#[post("/", data = "")] +async fn create(mut db: Connection, post: Json) -> Result>> { + // 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) -> Result>> { + let ids = sqlx::query!("SELECT id FROM posts") + .fetch(&mut *db) + .map_ok(|record| record.id) + .try_collect::>() + .await?; + + Ok(Json(ids)) +} + +#[get("/")] +async fn read(mut db: Connection, id: i64) -> Option> { + 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("/")] +async fn delete(mut db: Connection, id: i64) -> Result> { + 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) -> Result<()> { + sqlx::query!("DELETE FROM posts").execute(&mut *db).await?; + + Ok(()) +} + +async fn run_migrations(rocket: Rocket) -> 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]) + }) +} diff --git a/examples/rocket_example/src/tests.rs b/examples/rocket_example/src/tests.rs new file mode 100644 index 00000000..98457e7f --- /dev/null +++ b/examples/rocket_example/src/tests.rs @@ -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::>(), 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::(); + assert_eq!(response.unwrap(), post); + + // Ensure the index shows one more post. + let list = client.get(base).dispatch().into_json::>().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::().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::>().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::>().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()) +}