I'm trying to create an API using Actix-web, async-grahpql and sqlx with postgresql
In the QueryRoot of the async-graphql I am trying to capture the reference of the DB and make the query the database with sqlx, but it gives me an error
let items = Todo::list(&pool).await?;
| ^^^^^ expected struct `sqlx::Pool`, found enum `std::result::Result`
Here I want to capture the reference
use crate::todo::*;
use async_graphql::{Context, FieldResult};
use sqlx::postgres::PgPool;
pub struct QueryRoot;
#[async_graphql::Object]
impl QueryRoot {
async fn todos(&self, ctx: &Context<'_>) -> FieldResult<Vec<Todo>> {
let pool = ctx.data::<PgPool>();
let items = Todo::list(&pool).await?; //<-- This line generates an error
Ok(items)
}
}
Here I define the references
pub fn run(listener: TcpListener, db_pool: PgPool) -> Result<Server, std::io::Error> {
let data_db_pool = Data::new(db_pool);
//GraphQL
let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription)
.data(data_db_pool.clone()) //<- DB reference
.finish();
let server = HttpServer::new(move || {
App::new()
.app_data(db_pool.clone()) //<- DB reference
.data(schema.clone())
.route("/graphql", web::post().to(graphql))
.route("/graphql", web::get().to(graphql_playground))
})
.listen(listener)?
.run();
Ok(server)
}
What am I doing wrong? The complete code can be found here.
ctx.data::<T>() returns a Result wrapping a reference to T. You probably want.
let pool = ctx.data::<PgPool>()?;
// ^ return on Err, otherwise yield the &PgPool
let items = Todo::list(pool).await?;
// ^^^^ shouldn't need & here
Related
I have following Error:
error[E0277]: the trait bound `Bson: Borrow<News>` is not satisfied
--> src\handlers.rs:46:36
|
46 | let inserted = coll.insert_one(serialized_news, None).await.unwrap();
| ---------- ^^^^^^^^^^^^^^^ the trait `Borrow<News>` is not implemented for `Bson`
| |
| required by a bound introduced by this call
|
note: required by a bound in `mongodb::Collection::<T>::insert_one`
--> C:\Users\User\.cargo\registry\src\github.com-1ecc6299db9ec823\mongodb-2.3.1\src\coll\mod.rs:1279:19
|
1279 | doc: impl Borrow<T>,
| ^^^^^^^^^ required by this bound in `mongodb::Collection::<T>::insert_one`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `backend` due to previous error
My handler code containing the post_news function that throws this error:
use crate::structs::News;
use axum::{extract::Path, extract::State, http::StatusCode, response::IntoResponse, Json};
use bson::oid::ObjectId;
use futures::stream::StreamExt;
use mongodb::{bson::doc, options::FindOptions, Client, Collection};
pub async fn get_all(State(client): State<Client>) -> impl IntoResponse {
let coll: Collection<News> = client.database("axum").collection::<News>("news");
let mut options = FindOptions::default();
options.limit = Some(1);
options.sort = Some(doc! {
"title": 1
});
let mut cursor = coll
.find(None, options)
.await
.expect("could not load news data.");
let mut rows: Vec<News> = Vec::new();
while let Some(doc) = cursor.next().await {
rows.push(doc.expect("could not load news info."));
}
(StatusCode::OK, Json(rows))
}
pub async fn get_one(Path(id): Path<u64>) {}
pub async fn post_news(
State(client): State<Client>,
Json(payload): Json<News>,
) -> impl IntoResponse {
let coll: Collection<News> = client.database("axum").collection::<News>("news");
let news = News {
id: ObjectId::new(),
title: payload.title.to_string(),
short_description: payload.short_description.to_string(),
};
let serialized_news = bson::to_bson(&news).unwrap();
let inserted = coll.insert_one(serialized_news, None).await.unwrap();
(StatusCode::CREATED, Json(news))
}
pub async fn handler_404() -> impl IntoResponse {
(StatusCode::NOT_FOUND, "nothing to see here")
}
mod structs {
use bson::{self, oid::ObjectId};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct News {
#[serde(rename = "_id")]
pub id: ObjectId,
pub title: String,
pub short_description: String,
}
}
The error occurs in this line in the post_news function:
let inserted = coll.insert_one(serialized_news, None).await.unwrap();
Tried to add Clone and Debug to my struct and that didn't help
When encountering an error, you need to dig in and figure out why the error occurs, rather than just fiddling left and right.
In this case, the first step is looking at the function you are attempting to call Collection::insert_one:
pub async fn insert_one(
&self,
doc: impl Borrow<T>,
options: impl Into<Option<InsertOneOptions>>
) -> Result<InsertOneResult>
And double checking what Borrow is by following the link -- just to make sure it's indeed std::borrow::Borrow (spoiler: it is).
So, since your coll is of type Collection<News>, you need a type implementing Borrow<News> -- as mentioned by the error message -- which means amongst others News and &News.
Hence, as shown in the documentation of Collection, you should just insert your News object, without first serializing it:
let inserted = coll.insert_one(news, None).await.unwrap();
Collection will take care of serializing and deserializing as appropriate, no need to do the bson step yourself.
This is my async function that uses rust to connect to an existing mongoDB database. Is there a way to return / export the client variable / object and make it usable in other Files / Functions?
async fn connect_to_db() -> Result<(), Box<dyn Error>> {
// Load the MongoDB connection string from an environment variable (or string):
let client_uri = "mongodb://localhost:27017";
let options =
ClientOptions::parse_with_resolver_config(&client_uri, ResolverConfig::cloudflare())
.await?;
let client = Client::with_options(options)?;
let db = client.database("fiesta");
// Select Collection(s)
let user_col: Collection<User> = db.collection("users");
let skills_col: Collection<Skill> = db.collection("skills");
//ok.
Ok(())
}
Help would be appreciated.
Dependencies:
rocketrs
mongodb
tokio & serde
I have tried multiple things such as changing the lifetimes and changing the return type of the function, but to no avail.
I'm learning rust, and I have been following the rust book. Usually, I try to implement something to strengthen my knowledge.
Recently I was looking into how to use the MongoDb create to create an instace and return the name of the database.
Here's a sample of what I have:
A folder containing a implementation to build a mongo connection string.
A .env file
A main function to get the info I need.
mongo_connection.rs
#[derive(Debug)]
pub struct ConnectionString {
pub username: String,
pub password: String,
pub cluster: String,
}
impl ConnectionString {
pub fn build_connection_string() -> String {
return format!("mongodb+srv://{}:{}#a{}.k1jklnx.mongodb.net/?retryWrites=true&w=majority",
Self.username, Self.password, Self.cluster)
}
}
main.rs
mod database;
use crate::database::mongo_connection;
use mongodb::{Client, options::ClientOptions};
use std::error::Error;
use dotenv::dotenv;
use std::env;
use tokio;
async fn create_database_connection() -> Client {
dotenv().ok(); //Loading environment variables from .env file
let connection_parameters = mongo_connection::ConnectionString{
username: env::var("USERNAME").expect("No username found on .env"),
password: env::var("PASSWORD").expect("No password found on .env"),
cluster: env::var("CLUSTER").expect("No cluster found on .env")
};
let mut url: String = mongo_connection::ConnectionString::build_connection_string();
println!("{}", url);
let options = ClientOptions::parse(&url).await?;
return Client::with_options(options).await;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = create_database_connection().await?;
let db = client.database(&"runt");
println!("{:?}", db.name());
Ok(())
}
The problem I am facing is that I need to return a connection client. But I keep getting the Error:
let options = ClientOptions::parse(&url).await?;
^ cannot use the `?` operator in an async function that returns `Client`
If I change the result of the function to something like: Result<Client, dyn Eq> then I get the error:
async fn create_database_connection() -> Result<Client, dyn Eq> {
`Eq` cannot be made into an object
^^^^^^^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter
|
Any advice on how to solve the error or any workaround is much appreciated.
You could change the return type to either Result<Client, mongodb::error::Error> or Result<Client, Box<dyn Error>>. Either should work, but here's the code with the first version:
async fn create_database_connection() -> Result<Client, mongodb::error::Error> {
dotenv().ok(); //Loading environment variables from .env file
let connection_parameters = mongo_connection::ConnectionString{
username: env::var("USERNAME").expect("No username found on .env"),
password: env::var("PASSWORD").expect("No password found on .env"),
cluster: env::var("CLUSTER").expect("No cluster found on .env")
};
let mut url: String = mongo_connection::ConnectionString::build_connection_string();
println!("{}", url);
let options = ClientOptions::parse(&url).await?;
return Client::with_options(options).await;
}
In case you want to use the Box<dyn Error> version, the return value also needs a .into() to convert the Result<Client, mongodb::error::Error> of Client::with_options() into a Result<Client, Box<dyn Error>>.
It's a very simple project to learn how to use mongodb with Rust. I'm using the official mongodb driver here: https://github.com/mongodb/mongo-rust-driver. The problem is that if I'm using aggregate, I cannot read the result
// main.rs
use mongodb::bson::{doc, Bson};
use mongodb::{options::AggregateOptions, options::ClientOptions, Client};
use std::error::Error;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Load the MongoDB connection string:
let client_uri = "mongodb://127.0.0.1:27017";
// A Client is needed to connect to MongoDB:
let mut options = ClientOptions::parse(&client_uri).await?;
options.app_name = Some("testing".to_string());
let client = Client::with_options(options)?;
// get the collection here
let items = client.database("my_database").collection("inventory");
// aggregate options and pipeline
let pipeline = vec![doc! {"$match": {"name": "FOO"}}];
let options = AggregateOptions::builder().allow_disk_use(true).build();
// I'm using tokio for async-await library
let data = items
.aggregate(pipeline, options)
.await
.map_err(|e| println!("{}", e));
// data is a Result<mongodb::Cursor> type
match data {
Ok(cursor) => {
// I want to iterate the returned documents here
// this doesn't compiles
while let Some(doc) = cursor.next().await {
println!("{}", doc?)
}
},
Err(e) => println!("{:?}", e),
}
The code above returns an error. It complains that the cursor has no next() function within.
while let Some(doc) = cursor.next().await {
| ^^^^ method not found in `mongodb::Cursor`
I read the manual book for mongodb::Cursor here: https://docs.rs/mongodb/1.2.1/mongodb/struct.Cursor.html
and aggregate function here https://docs.rs/mongodb/1.2.1/mongodb/struct.Collection.html#method.aggregate
As you can see the aggregate method should return Result<Cursor>. As the manual stated:
A cursor can be used like any other Stream. The simplest way is just
to iterate over the documents it yields:
while let Some(doc) = cursor.next().await {
println!("{}", doc?)
}
So why is it doesn't work ?
My dependencies in Cargo.toml:
[dependencies]
tokio = { version = "0.2", features = ["macros", "rt-threaded"] }
serde = { version = "1.0", features = ["derive"] }
mongodb = "1.2.0"
If I print the cursor println!("{:?}", cursor);. It contains data in it. How to get the data out from this cursor ?
I found it! Just add use tokio::stream::StreamExt; on top of the file and the rest is good to go.
...all the other methods that an Stream has are available on Cursor as
well. This includes all of the functionality provided by StreamExt,
which provides similar functionality to the standard library Iterator
trait.
// main.rs
use mongodb::bson::{doc, Bson};
use mongodb::{options::AggregateOptions, options::ClientOptions, Client};
use std::error::Error;
use tokio;
// don't forget this!
use tokio::stream::StreamExt;
Fixed it by adding use futures::stream::StreamExt;
I'm trying to parse some data that I cached using Haneke Swift. I've cached the data and have written the parser to accomplish this. This parser is in a separate class called AssembleCalendar().
Using Haneke's example code for fetching, I've tried with complete and utter failure to actually return a value from the closure.
My attempt
func getScheduledItems() -> [ScheduledItem] {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
}
print(scheduledItem) // array returns nil
return scheduledItem // returns nil
}
What I know
I understand that this is an asynchronous issue. My code isn't waiting for my AssembleCalendar() parser to finish. It's just running each line and returns nil long before my scheduledItem receives a value. I've tried many, many solutions and read quite a few examples online but I cannot figure out how to retrieve a value from this closure in this scenario.
My question
How can I get .fetch() to return a value before my function hits nil?
update:
Here's my code in context:
class Schedule {
var items : [ScheduledItem]
init() {
items = getScheduledItems() // Schedule.getScheduledItems()
}
func getScheduledItems(completion: (items: [ScheduledItem]) -> ()) {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
completion(items: scheduledItem)
}
}
}
Fetch() is using a completion handler, this means that the block of code called in there is actually executing AFTER your return statement has been executed. This is why it is returning nil. To get around this, you can use your own completion handler to return the information:
class Schedule {
var items = [ScheduledItem]()
init(items: [ScheduledItem]) {
self.items = items
}
class func getScheduledItems(completion: (items: [ScheduledItem]) -> ()) {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
completion(items: scheduledItem)
}
}
}
Then initialise the class using this:
Schedule.getScheduledItems { (items) -> () in
var schedule = Schedule(items: items)
}