0atman / noboilerplate

Code for my talks on the No Boilerplate channel
https://www.youtube.com/c/NoBoilerplate
Creative Commons Zero v1.0 Universal
3.34k stars 154 forks source link

Poem API demo doesn't work #50

Closed nico-mayora closed 10 months ago

nico-mayora commented 11 months ago

Description of the problem

I tried to get something going using The Stack, and tried using https://github.com/0atman/noboilerplate/blob/main/scripts/30-poem.md as a base. When running cargo run the programme won't even compile.

Steps to reproduce

  1. Create a new cargo project.
  2. Set up the sqlite3 database.
  3. Paste the dependencies into Cargo.toml.
  4. Paste the imports and code into main.rs.
  5. Run cargo run on the terminal

Expected behaviour

The programme runs and an API is served on 127.0.0.1:3000.

Actual behaviour

Rustc fails to compile the programme, returning the following errors:

error[E0308]: mismatched types
  --> src/main.rs:45:25
   |
45 |               let todos = sqlx::query_as!(
   |  _________________________^
46 | |             Todo,
47 | |             "SELECT * FROM todos"
48 | |         )
   | |_________^ expected `i64`, found `Option<i64>`
   |
   = note: expected type `i64`
              found enum `std::option::Option<i64>`
   = note: this error originates in the macro `$crate::sqlx_macros::expand_query` which comes from the expansion of the macro `sqlx::query_as` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
  --> src/main.rs:45:25
   |
45 |               let todos = sqlx::query_as!(
   |  _________________________^
46 | |             Todo,
47 | |             "SELECT * FROM todos"
48 | |         )
   | |_________^ expected `String`, found `Option<String>`
   |
   = note: expected struct `std::string::String`
                found enum `std::option::Option<std::string::String>`
   = note: this error originates in the macro `$crate::sqlx_macros::expand_query` which comes from the expansion of the macro `sqlx::query_as` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
  --> src/main.rs:45:25
   |
45 |               let todos = sqlx::query_as!(
   |  _________________________^
46 | |             Todo,
47 | |             "SELECT * FROM todos"
48 | |         )
   | |_________^ expected `bool`, found `Option<bool>`
   |
   = note: expected type `bool`
              found enum `std::option::Option<bool>`
   = note: this error originates in the macro `$crate::sqlx_macros::expand_query` which comes from the expansion of the macro `sqlx::query_as` (in Nightly builds, run with -Z macro-backtrace for more info)
help: use `Option::is_some` to test if the `Option` has a value
  --> /home/nico/.cargo/registry/src/index.crates.io-6f17d22bba15001f/sqlx-0.6.3/src/macros/mod.rs:558:82
   |
558|         $crate::sqlx_macros::expand_query!(record = $out_struct, source = $query).is_some()
   |                                                                                  ++++++++++

error: future cannot be sent between threads safely
   --> src/main.rs:22:1
    |
22  | #[OpenApi]
    | ^^^^^^^^^^ future created by async block is not `Send`
    |
note: opaque type is declared here
   --> src/main.rs:44:10
    |
44  |     ) -> TodoResponse {
    |          ^^^^^^^^^^^^
note: this item depends on auto traits of the hidden type, but may also be registering the hidden type. This is not supported right now. You can try moving the opaque type and the item that actually registers a hidden type into a new submodule
   --> src/main.rs:22:1
    |
22  | #[OpenApi]
    | ^^^^^^^^^^
note: future is not `Send` as it awaits another future which is not `Send`
   --> src/main.rs:22:1
    |
22  | #[OpenApi]
    | ^^^^^^^^^^ await occurs here on type `impl Future<Output = Result<poem_openapi::payload::Json<Vec<Todo>>, poem::Error>>`, which is not `Send`
note: required by a bound in `poem::endpoint::make`
   --> /home/nico/.cargo/registry/src/index.crates.io-6f17d22bba15001f/poem-1.3.58/src/endpoint/endpoint.rs:168:31
    |
165 | pub fn make<F, Fut, T, R>(f: F) -> impl Endpoint<Output = T>
    |        ---- required by a bound in this function
...
168 |     Fut: Future<Output = R> + Send,
    |                               ^^^^ required by this bound in `make`
    = note: this error originates in the attribute macro `OpenApi` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0308`.
error: could not compile `todo-api` (bin "todo-api") due to 4 previous errors
0atman commented 10 months ago

Sorry I missed this - this is an enormously common problem when using SQLx, and one I trip up in every new project!

The problems comes in your step 2: Set up the sqlite3 database. I did not show this step in the video, and so it's as much my fault as SQLx's documentation miss, on this point.

The core conflict is: Rust has no nulls. SQL fields are nullable by default.

So, to create a SQL table that works with the following

struct Todo {
    id: i64,
    description: String,
    done: bool,
}

Your create table statement must look something like this (taken from the SQLx example project, which I believe is correct):

CREATE TABLE IF NOT EXISTS todos
(
    id          INTEGER PRIMARY KEY NOT NULL,
    description TEXT                NOT NULL,
    done        BOOLEAN             NOT NULL DEFAULT 0
);

If you forget to make your table columns NOT NULL, SQLx will quite rightly wrap them with Option, as they could be NULL, and you'll get expected i64, found Option<i64>, when query_as!-ing them!

Apologies! I should have invited the viewer to check out the SQLx example project that I was demoing, I'll add that to the ERRATA and description of the video. Thank you!

https://github.com/launchbadge/sqlx/tree/main/examples/sqlite/todos

0atman commented 10 months ago

If in the future you'd like to follow along with my projects, do clone down this repository and follow the instructions in the Justfile to build each video's rust code into a full Cargo project https://github.com/0atman/noboilerplate/blob/main/scripts/justfile All my rust code from about episode 5 onwards is compiled in this way as I work on the video, so it should at the VERY LEAST compile :-)

nico-mayora commented 10 months ago

Thanks for the response Tris, very helpful as always!