I'm just starting to learn Rust and built a sample api project using Rocket and rusqlite. I want to get all of the items in my database through a get endpoint. My table has an id field and a string which is a list of ids seperated by ,. After getting this back I want to split this string and go back to the database for each entry and then create a struct for it. I'm having trouble figuring this out in a functional way for rust. Does anyone have any advice for how to proceed? I want to manipulate the data myself and avoid orm tools like diseil.
data.rs
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
pub struct Item {
pub id: i32,
pub time_tp_prepare: i32,
pub name: String
}
#[derive(Serialize)]
pub struct Table {
pub id: i32,
pub items: Vec<Item>
}
#[derive(Serialize)]
pub struct StatusMessage {
pub message: String
}
database.rs
use rusqlite::Result;
pub struct ItemData {
pub id: i32,
pub time_tp_prepare: i32,
pub name: String
}
pub struct TableData {
pub id: i32,
pub itemIds: String
}
pub fn setup_db() -> Result<String, String>{
let db_connection = match rusqlite::Connection::open("data.sqlite") {
Ok(connection) => connection,
Err(_) => {
return Err("Cannot connect to database.".into());
}
};
match db_connection
.execute(
"create table if not exists item (
id integer primary key,
name varchar(64) not null,
preperation_time integer not null
);
create table if not exists restaurant_table (
id integer primary key,
items varchar(64) not null",
[]
) {
Ok(success) => Ok("Successfully created database tables.".into()),
Err(_) => return Err("Could not run create table sql".into())
}
}
main.rs
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
use rocket_contrib::json::Json;
use rusqlite::Result;
mod database;
mod data;
#[get("/api/get-all-tables-v1")]
fn get_all_tables() -> Result<Json<data::Table>, String> {
let db_connection = match rusqlite::Connection::open("data.sqlite") {
Ok(connection) => connection,
Err(_) => {
return Err("Cannot connect to database.".into());
}
};
let mut statement = match db_connection.prepare("select id, items from restaurant_table;") {
Ok(statement) => statement,
Err(_) => return Err("Failed to prepare query.".into())
};
let results = statement.query_map([], |row| {
Ok(database::TableData {
id: row.get(0)?,
itemIds: row.get(1)?
})
});
match results {
Ok(rows) => {
///// This is where I'm stuck on what to do next //////
let collection: rusqlite::Result<Vec<data::Item>> = rows.collect();
match collection {
Ok(items) => Ok(Json(data::Table { items })),
Err(_) => Err("Could not collect items.".into())
}
},
Err(_) => Err("Failed to fetch items.".into())
}
}
fn main() {
match database::setup_db() {
Ok(_) => luanch_server(),
Err(error) => eprintln!("Program failed to start because of Error {}.", error)
}
}
fn luanch_server() {
rocket::ignite().mount("/", routes![get_all_tables]).launch();
}
I figured it out and I will post the code here in case anyone finds it in the future. It is not great code and I ended up not using it but it was a good learning experience.
for row in rows.into_iter().flatten() {
let ids = row.itemIds.split(",");
for id in ids {
let mut item_statement = db_connection
.prepare("select * from item where id = :id;")
.expect("Failed to prepare query.");
let mut item_rows = item_statement
.query_named(rusqlite::named_params!{ ":id": id })
.expect("Select item statement failed");
while let Some(item_row) = item_rows
.next()
.expect("Row Failed.") {
let item = data::Item {
id: row.get(0),
time_to_prepare: row.get(1),
name: row.get(2)
};
if !tables.contains(item.id) {
tables.insert(item);
}
}
Related
I have a table called order_data which has timestamp field called created_on and i32 field order_id. I want to query those in different methods.
For created_on:
pub async fn fetch_last_created_on(pool: &Pool<Postgres>) -> Option<NaiveDateTime> {
let result = match query_as::<Postgres, OrderDb>("select max(created_on) as created_on from order_data")
.fetch_one(pool)
.await
{
Ok(result) => result.created_on,
Err(e) => {
error!("Error fetching data: {}", e);
None
}
};
result
}
And for order_id:
pub async fn fetch_all_order_ids(pool: &Pool<Postgres>) -> Option<HashSet<i32>> {
let result = match query_as::<Postgres, OrderDb>("select order_id from order_data")
.fetch_all(pool)
.await
{
Ok(result) => Some(result.iter().map(|order| order.order_id.unwrap()).collect()),
Err(e) => {
error!("Error fetching data: {}", e);
None
}
};
result
}
I've defined OrderDb as:
#[derive(FromRow)]
struct OrderDb {
order_id: Option<i32>,
created_on: Option<NaiveDateTime>,
}
But with this if use fetch_last_created_on it results in the following error
no column found for name: order_id
I could define two separate derive(FromRow) structs for each case, but is there a better way to handle it?
Do note that I'm not using macros but methods.
I am implementing a derive macro to reduce the amount of boilerplate I have to write for similar types.
I want the macro to operate on structs which have the following format:
#[derive(MyTrait)]
struct SomeStruct {
records: HashMap<Id, Record>
}
Calling the macro should generate an implementation like so:
impl MyTrait for SomeStruct {
fn foo(&self, id: Id) -> Record { ... }
}
So I understand how to generate the code using quote:
#[proc_macro_derive(MyTrait)]
pub fn derive_answer_fn(item: TokenStream) -> TokenStream {
...
let generated = quote!{
impl MyTrait for #struct_name {
fn foo(&self, id: #id_type) -> #record_type { ... }
}
}
...
}
But what is the best way to get #struct_name, #id_type and #record_type from the input token stream?
One way is to use the venial crate to parse the TokenStream.
use proc_macro2;
use quote::quote;
use venial;
#[proc_macro_derive(MyTrait)]
pub fn derive_answer_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Ensure it's deriving for a struct.
let s = match venial::parse_declaration(proc_macro2::TokenStream::from(item)) {
Ok(venial::Declaration::Struct(s)) => s,
Ok(_) => panic!("Can only derive this trait on a struct"),
Err(_) => panic!("Error parsing into valid Rust"),
};
let struct_name = s.name;
// Get the struct's first field.
let fields = s.fields;
let named_fields = match fields {
venial::StructFields::Named(named_fields) => named_fields,
_ => panic!("Expected a named field"),
};
let inners: Vec<(venial::NamedField, proc_macro2::Punct)> = named_fields.fields.inner;
if inners.len() != 1 {
panic!("Expected exactly one named field");
}
// Get the name and type of the first field.
let first_field_name = &inners[0].0.name;
let first_field_type = &inners[0].0.ty;
// Extract Id and Record from the type HashMap<Id, Record>
if first_field_type.tokens.len() != 6 {
panic!("Expected type T<R, S> for first named field");
}
let id = first_field_type.tokens[2].clone();
let record = first_field_type.tokens[4].clone();
// Implement MyTrait.
let generated = quote! {
impl MyTrait for #struct_name {
fn foo(&self, id: #id) -> #record { *self.#first_field_name.get(&id).unwrap() }
}
};
proc_macro::TokenStream::from(generated)
}
I am using the HashMap to store some of the value. Now I want to check whether HashMap contains keys, if not insert data else return HashMap is not empty. Below code explain that I am checking for product id, but I want to check for 2 keys product key and product url.
use std::collections::hash_map::Entry::Vacant;
pub struct Products<DB>
where
DB: DatabaseProvider,
{
database: DB,
products: HashMap<String, Product>,
}
pub struct Product {
pub product_id: String,
pub created: String,
pub product_description: String,
pub product_url: String,
}
pub async fn get_product_store_hashmap(&mut self) -> Result<()>
{
// calling the api here, once response is received, store in hashmap
let product = Product {
product_id: somedata,
created: somedata,
product_description: somedata,
product_url:somedata,
};
self.products.insert(product_id.clone(), product);
}
pub async fn insertProduct(&mut self, product:Product) -> Result<()> {
// How to check two keys are contains value.
if let Vacant(entry:Vacant::entry<(string,Product)) = self.product.entry(product_id) {
// insert the data
} else {
// retun no product id found
}
}
You can use map.contains_key to test.
Example:
fn insertProduct(map: &mut HashMap<String, Product>, product: Product) -> Result<()> {
if map.contains_key(product.product_id) || map.contains_key(product.product_url) {
Err()
} else {
map.insert(product.product_id, Product);
Ok(())
}
}
You can use Vacant and Occupied to check if key already exists or not and return result according using match
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Vacant, Occupied};
use std::io::ErrorKind;
fn main() {
let mut h: HashMap<&str, u8> = HashMap::new();
let value: &str = "a";
// h.entry(value).or_insert(1); // <- uncomment to get err result
let result = match h.entry(value) {
Vacant(entry) => {
entry.insert(1);
Ok(())
},
Occupied(_) => Err(ErrorKind::AlreadyExists)
};
println!("{:?}", result);
}
Playground
I have this struct:
pub struct Thing {
pub some_field: i32,
#[my_attr = some_value]
pub field_attr: String
}
How can I recover the data on the right side of the equals? I can recover perfectly fine the left side.
pub fn new(name: &Ident, raw_helper_attributes: &[Attribute], ty: &Type) -> syn::Result<Self> {
// Getting the name of attributes put in front of struct fields
let helper_attributes = raw_helper_attributes
.iter()
.map(|attribute| {
attribute
.path
.segments
.iter()
.map( |segment| {
&segment.ident
})
.collect::<Vec<_>>()
})
.flatten()
.collect::<Vec<_>>();
let attribute_type = if helper_attributes.len() == 1 {
let helper_attribute = helper_attributes[0];
Some(EntityFieldAnnotation::try_from(helper_attribute)?)
} else if helper_attributes.len() > 1 {
return Err(
syn::Error::new_spanned(
name,
"Field has more than one attribute"
)
);
} else { None };
Ok(
Self {
name: name.clone(),
field_type: ty.clone(),
attribute: attribute_type,
}
)
}
For short, I ommited the rest of the code of the macro for summarize.
Use Attribute::parse_meta():
let (path, value) = match attribute.parse_meta().unwrap() {
syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(s),
..
}) => (path, s.value()),
_ => panic!("malformed attribute syntax"),
};
I am a Rust beginner and was wondering how to access a struct's fields dynamically:
use std::collections::HashMap;
struct User {
email: String,
name: String,
}
impl User {
fn new(attributes: &HashMap<String,String>) -> User {
let mut model = User {
email: "",
name: "",
};
for (attr_name,attr_value) in attributes {
// assign value "attr_value" to attribute "attr_name"
// no glue how to do this
// in php would be: $model->{$attr_name} = $attr_value;
model.*attr_name *= attr_value;
}
model;
}
}
fn main() {
let mut map: HashMap::new();
map.insert("email",String::from("foo#bar.de"));
map.insert("name",String::from("John doe"));
user_model = User::new(&map);
println!("{:?}",user_model);
}
How it is possible to initialize a struct by given HashMap?
Unless you change your User to contain a HashMap then Rust can't do that kind of "magic" (or it will require some proc macro usage, which is not beginner friendly).
Instead you can use a match, and match all the keys and update the User fields:
for (attr_name, attr_value) in attributes {
match attr_name {
"email" => model.email = attr_value.clone(),
"name" => model.name = attr_value.clone(),
_ => {}
}
}
However, instead of having empty Strings, I'd suggest using Option<String>.
struct User {
email: Option<String>,
name: Option<String>,
}
Then you can simplify your whole new method to just:
fn new(attributes: &HashMap<String, String>) -> User {
User {
email: attributes.get("email").cloned(),
name: attributes.get("name").cloned(),
}
}
Since you have some mixed String and &'static str usage, along with Debug not being implemented. Then here is the complete example:
use std::collections::HashMap;
#[derive(Debug)]
struct User {
email: Option<String>,
name: Option<String>,
}
impl User {
fn new(attributes: &HashMap<String, String>) -> User {
User {
email: attributes.get("email").cloned(),
name: attributes.get("name").cloned(),
}
}
}
fn main() {
let mut map = HashMap::new();
map.insert(String::from("email"), String::from("foo#bar.de"));
map.insert(String::from("name"), String::from("John doe"));
let user_model = User::new(&map);
println!("{:?}", user_model);
}