Actix Web 4 always add "Ok" field - rust

Currently, I change actix_web from version 3 to 4.0.0-rc.1 and for every response, it always add "Ok"/"Err" field as below
{
"Ok": [
"item1",
"item2"
]
}
It should return:
[
"item1",
"item2"
]
This is handler for the API
pub async fn get_data(db: web::Data<Pool>) -> HttpResponse {
let res = web::block(move || db_get_data(db)).await;
match res {
Ok(data_vec) => HttpResponse::Ok().json(data_vec),
Err(_) => HttpResponse::BadRequest().finish()
}
}
fn db_get_data(db: web::Data<Pool>) -> Result<Vec<String>, ()> {
let items = vec!["item1".to_string(), "item2".to_string()];
Ok(items)
}
How could I solve this issue?

There are two layers of Result: one from actix_web::web::block and another from db_get_data. Try something like this:
let res = web::block(move || db_get_data(db)).await;
match res {
Ok(Ok(data_vec)) => HttpResponse::Ok().json(data_vec),
_ => HttpResponse::BadRequest().finish(),
}

Related

Is it possible to make this function more readable?

I have this function:
fn init_clients() -> (upload_executor::Executor, download_executor::Executor) {
let client_fs = if USE_CLOUD {
None
} else {
Some(Arc::new(adapter_fs::Client::new(".")))
};
let client_cloud = if USE_CLOUD {
Some(Arc::new(
adapter_s3::Client::new(
"https://s3.region.aws.com",
"region",
"bucket_name",
"KEY_ID",
"KEY_SECRET",
)
.unwrap(),
))
} else {
None
};
let client = match USE_CLOUD {
true => client_cloud
.as_ref()
.map(|o| o.clone() as Arc<dyn AdapterTrait>)
.unwrap(),
false => client_fs
.as_ref()
.map(|o| o.clone() as Arc<dyn AdapterTrait>)
.unwrap(),
};
let upload_executor = upload_executor::Executor::new(client.clone());
let download_executor = download_executor::Executor::new(client);
(upload_executor, download_executor)
}
I would like to know how to make it more readable, like:
let upload_client: TraitA + TraitB = if USE_CLOUD {
upload_client = fs::new()
} else {
upload_client = s3::new()
}
Is it possible?
If you merged the sections together you could create something like this. Also keep in mind that I haven't checked this in an IDE so I am not completely sure if I correctly converted to an anonymous trait.
fn init_s3_client() -> Arc<adapter_s3::Client> {
let client = adapter_s3::Client::new(
"https://s3.region.aws.com",
"region",
"bucket_name",
"KEY_ID",
"KEY_SECRET");
match client {
Ok(x) => Arc::new(x),
Err(err) => panic!("Failed to create s3 client: {:?}", err),
}
}
fn init_clients() -> (upload_executor::Executor, download_executor::Executor) {
let client = match USE_CLOUD {
true => init_s3_client() as Arc<dyn AdapterTrait>,
false => Arc::new(adapter_fs::Client::new(".")) as Arc<dyn AdapterTrait>,
};
let upload_executor = upload_executor::Executor::new(client.clone());
let download_executor = download_executor::Executor::new(client);
(upload_executor, download_executor)
}

How to append elements to a list in DynamoDB using Rust

I'm struggling using list_append programmatically in Rust.
I have a table called Humidities:
{
"id": 177,
"Measurements": [
49
]
}
and I want to add elements. For example:
{
"id": 177,
"Measurements": [
49,
53
]
}
This is a working solution for python, which I've found here :
table = get_dynamodb_resource().Table("table_name")
result = table.update_item(
Key={
'hash_key': hash_key,
'range_key': range_key
},
UpdateExpression="SET some_attr = list_append(some_attr, :i)",
ExpressionAttributeValues={
':i': [some_value],
},
ReturnValues="UPDATED_NEW"
)
if result['ResponseMetadata']['HTTPStatusCode'] == 200 and 'Attributes' in result:
return result['Attributes']['some_attr']
Based on the python solution I've tried this:
#[tokio::main]
async fn main() -> Result<(), Error> {
let Opt { base } = Opt::from_args();
let shared_config = make_config(base).await?;
let client = Client::new(&shared_config);
client
.update_item()
.table_name("Humidities")
.key("id", AttributeValue::N(177.to_string()))
.update_expression("SET i = list_append(Measurements, :i)")
.expression_attribute_values("i", AttributeValue::N(53.to_string()))
.send()
.await?;
Ok(())
}
However, the result is:
Error: Unhandled(Error { code: Some("ValidationException"), message: Some("ExpressionAttributeValues contains invalid key: Syntax error; key: \"i\""), request_id: Some("05RSFGFJEEDPO7850LI2T91RGRVV4KQNSO5AEMVJF66Q9ASUAAJG"), extras: {} })
What am I doing wrong?
The examples only demonstrate how to add a single item: https://github.com/awslabs/aws-sdk-rust/blob/main/examples/dynamodb/src/bin/add-item.rs
I've also tried this:
#[tokio::main]
async fn main() -> Result<(), Error> {
let Opt { base } = Opt::from_args();
let shared_config = make_config(base).await?;
let client = Client::new(&shared_config);
client
.update_item()
.table_name("Humidities")
.key("id", AttributeValue::N(177.to_string()))
.update_expression("set #Measurements = list_append(#Measurements, :value)")
.expression_attribute_names("#Measurements", "Measurements")
.expression_attribute_values("value", AttributeValue::N(53.to_string()))
.return_values(aws_sdk_dynamodb::model::ReturnValue::AllNew)
.send()
.await?;
Ok(())
}
with a look at: Append a new object to a JSON Array in DynamoDB using NodeJS
Same result, value is unknown:
Error: Unhandled(Error { code: Some("ValidationException"), message: Some("ExpressionAttributeValues contains invalid key: Syntax error; key: \"value\""), request_id: Some("1A8VEOEVSB7LMAB47H12N7IKC7VV4KQNSO5AEMVJF66Q9ASUAAJG"), extras: {} })
I've found a solution. There have been two problems:
list_append expects two lists
expression_attribute_values expected :value instead of value
Running example:
async fn main() -> Result<(), Error> {
let Opt { base } = Opt::from_args();
let shared_config = make_config(base).await?;
let client = Client::new(&shared_config);
client
.update_item()
.table_name("Humidities")
.key("id", AttributeValue::N(177.to_string()))
.update_expression("set #Measurements = list_append(#Measurements, :value)")
.expression_attribute_names("#Measurements", "Measurements")
.expression_attribute_values(
":value", // changed from "value" to ":value"
AttributeValue::L(vec![AttributeValue::N(53.to_string())]), // use a vector of numbers instead of a number
)
.return_values(aws_sdk_dynamodb::model::ReturnValue::AllNew)
.send()
.await?;
Ok(())
}

Match arms have incompatible types. Expected struc `NaiveDate`, found `()`

I'm new to Rust and am trying to wrap my head around error handling.
I'm trying to return error if parsing the date goes wrong, here is the function:
pub fn create_posts(contents: &Vec<String>) -> Result<Vec<Post>, CreatePostError> {
const TITLE_SEP: &str = "Title: ";
const DESC_SEP: &str = "Description: ";
const DATE_SEP: &str = "Date: ";
const TAGS_SEP: &str = "Tags: ";
let mut posts: Vec<Post> = Vec::new();
for entry in contents {
let lines = entry.lines().collect::<Vec<_>>();
let metadata = lines[0].contains(&TITLE_SEP)
&& lines[1].contains(&DESC_SEP)
&& lines[2].contains(&DATE_SEP)
&& lines[3].contains(&TAGS_SEP);
if metadata {
let date = &lines[2][DATE_SEP.len()..];
let parsed_date = match NaiveDate::parse_from_str(date, "%Y-%m-%d") {
Ok(parsed_date) => parsed_date,
Err(e) => eprintln!("Error: {:?}", CreatePostError::ParseError { inner_err: e }),
};
let tags: Vec<String> = lines[3][TAGS_SEP.len()..]
.split(", ")
.map(|s| s.to_string())
.collect();
let mut article_content = String::new();
for line in &lines[4..] {
article_content.push_str(line);
article_content.push_str("\n")
}
let post = Post {
title: lines[0][TITLE_SEP.len()..].to_string(),
description: lines[1][DESC_SEP.len()..].to_string(),
date: parsed_date,
tags,
content: article_content,
};
posts.push(post);
} else {
return Err(CreatePostError::MetadataError);
}
}
return Ok(posts);
}
You can see the full code here since i wrote custom errors: link
The problem I'm having is with this part:
let date = &lines[2][DATE_SEP.len()..];
let parsed_date = match NaiveDate::parse_from_str(date, "%Y-%m-%d") {
Ok(parsed_date) => parsed_date,
Err(e) => eprintln!("Error: {:?}", CreatePostError::ParseError { inner_err: e }),
};
I'm getting match arms have incompatible types. Expected struct NaiveDate, found ()
Here is my enum and impl for the error:
#[derive(Debug)]
pub enum CreatePostError {
ReadFileError { path: PathBuf },
MetadataError,
ParseError { inner_err: ParseError },
}
impl fmt::Display for CreatePostError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::ReadFileError { path } => write!(f, "Error reading file {path:?}"),
Self::MetadataError => write!(f, "Some metadata is missing"),
Self::ParseError { inner_err } => {
write!(f, "Error parsing date: {inner_err}")
}
}
}
}
impl From<chrono::format::ParseError> for CreatePostError {
fn from(e: chrono::format::ParseError) -> Self {
CreatePostError::ParseError { inner_err: e }
}
}
You probably want to have a result here, here is a way to do it:
let parsed_date = match NaiveDate::parse_from_str(date, "%Y-%m-%d") {
Ok(parsed_date) => Ok(parsed_date),
Err(e) => {eprintln!("Error: {:?}", &e);
Err(CreatePostError::ParseError { inner_err: e }}),
}?;
The ? is saying: if the thing before is an error, return it, and if it is Ok, unwrap it.
This pattern is so common that rust's result gives a lot of utilities to make this kind of things easier. Here, the map_err function would make it more straightforward: map_err
see:
let parsed_date = NaiveDate::parse_from_str(date, "%Y-%m-%d")
.map_err(|e| {
eprintln!("Error: {:?}", &e);
CreatePostError::ParseError { inner_err: e }})?;
But it is only a matter of preference and it might be a lot to digest if you are just beginning, so you can choose the way that you like the most.

Is it possible to reduce the number of match arms caused by generics?

I am "serializing" and "deserializing" a generic struct to and from an SQLite database. The struct has two members whose values are of generic types, V and T, both constrained to the DataType trait. When I want to reconstruct these from the information in the database, I haven't been able to find a way around specifying match arms for every combination of V and T. Given that I will eventually have around 20 data types, that means 20 * 20 = 400 match arms. Is there any way around this? An unsafe solution is also acceptable.
Here is a MWE with two data types:
// A somewhat boilerplaty, but working solution to
// store and retrieve a generically typed struct
// in a SQLite database.
use rusqlite::{params, Connection, Statement};
use rusqlite::types::{ToSql, FromSql, ValueRef};
// This trait needs to be implemented for every type
// the GenericStruct will hold as a value.
trait DataType : ToSql + FromSql {
type TargetType;
fn convert(value: &ValueRef) -> Self::TargetType;
fn data_type(&self) -> &'static str;
}
impl DataType for String {
type TargetType = String;
fn data_type(&self) -> &'static str {
"String"
}
fn convert(value: &ValueRef) -> Self::TargetType {
String::from(value.as_str().unwrap())
}
}
impl DataType for usize {
type TargetType = usize;
fn data_type(&self) -> &'static str {
"usize"
}
fn convert(value: &ValueRef) -> Self::TargetType {
usize::try_from(value.as_i64().unwrap()).unwrap()
}
}
// This is the generic struct that is persisted in SQLite.
#[derive(Debug)]
struct GenericStruct<V: DataType, T: DataType> {
value: V,
time: T
}
// This is just to keep the database stuff together.
struct Database<'db> {
pub add_struct: Statement<'db>,
pub get_struct: Statement<'db>
}
impl<'db> Database<'db> {
pub fn new<'con>(connection: &'con Connection) -> Database<'con> {
// the table will hold both the value and its Rust type
connection.execute_batch("
create table if not exists GenericStruct (
value any not null,
value_type text not null,
time any not null,
time_type text not null
)
").unwrap();
Database {
add_struct: connection.prepare("
insert into GenericStruct
(value, value_type, time, time_type)
values
(?, ?, ?, ?)
").unwrap(),
get_struct: connection.prepare("
select
value, value_type, time, time_type
from GenericStruct
").unwrap()
}
}
}
pub fn main() {
let sqlite = Connection::open("generic.db").unwrap();
let mut database = Database::new(&sqlite);
let g1 = GenericStruct {
value: String::from("Hello World"),
time: 20090921
};
let g2 = GenericStruct {
value: 42,
time: String::from("now")
};
// Add the two structs to the sqlite database
database.add_struct.execute(
params![&g1.value, &g1.value.data_type(), &g1.time, &g1.time.data_type()]
).unwrap();
database.add_struct.execute(
params![&g2.value, &g2.value.data_type(), &g2.time, &g2.time.data_type()]
).unwrap();
// Now there are two different types in the database.
// Retrieve the two structs again.
let mut rows = database.get_struct.query([]).unwrap();
while let Some(row) = rows.next().unwrap() {
let data_type = row.get_unwrap::<_, String>(1);
let time_type = row.get_unwrap::<_, String>(3);
// I want to lookup the converter instead
// of explicitly listing alternatives...
match (data_type.as_str(), time_type.as_str()) {
("String", "usize") => {
println!("{:?}", GenericStruct {
value: String::convert(&row.get_ref_unwrap(0)),
time: usize::convert(&row.get_ref_unwrap(2))
});
},
("usize", "String") => {
println!("{:?}", GenericStruct {
value: usize::convert(&row.get_ref_unwrap(0)),
time: String::convert(&row.get_ref_unwrap(2))
});
},
_ => ()
}
}
}
I have also set it up in a playground here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7bb2403d88c3318348ba50d90763c225
You can do that with the following (pretty complex) macro.
macro_rules! generate_match {
// First, we generate a table of permutations.
// Suppose we have the tuple (String, usize, ()).
// The table we generate will be the following:
// [
// [ String, usize, () ]
// [ usize, (), String ]
// [ (), String, usize ]
// ]
// Empty case
{ #generate_permutations_table
$row:ident
match ($e:expr)
table = [ $($table:tt)* ]
rest = [ ]
transformed = [ $($transformed:ty,)* ]
} => {
generate_match! { #permutate_entry
$row
match ($e) { }
table = [ $($table)* ]
}
};
{ #generate_permutations_table
$row:ident
match ($e:expr)
table = [ $($table:tt)* ]
rest = [ $current:ty, $($rest:ty,)* ]
transformed = [ $($transformed:ty,)* ]
} => {
generate_match! { #generate_permutations_table
$row
match ($e)
table = [
$($table)*
[ $current, $($rest,)* $($transformed,)* ]
]
rest = [ $($rest,)* ]
transformed = [ $($transformed,)* $current, ]
}
};
// For each entry in the table, we generate all combinations of the first type with the others.
// For example, for the entry [ String, usize, () ] we'll generate the following permutations:
// [
// (String, usize)
// (String, ())
// ]
// Empty case
{ #permutate_entry
$row:ident
match ($e:expr) { $($match_tt:tt)* }
table = [ ]
} => {
match $e {
$($match_tt)*
_ => {}
}
};
{ #permutate_entry
$row:ident
match ($e:expr) { $($match_tt:tt)* }
table = [
[ $current:ty, $($others:ty,)* ]
$($table:tt)*
]
} => {
generate_match! { #generate_arm
$row
match ($e) { $($match_tt)* }
table = [ $($table)* ]
current = [ $current ]
others = [ $($others,)* ]
}
};
// Finally, We generate `match` arms from each pair.
// For example, for the pair (String, usize):
// ("String", "usize") => {
// let value = GenericStruct {
// value: <String as DataType>::convert(&row.get_ref_unwrap(0)),
// time: <usize as DataType>::convert(&row.get_ref_unwrap(2)),
// };
// // Process `value...`
// }
// Empty case: permutate the next table entry.
{ #generate_arm
$row:ident
match ($e:expr) { $($match_tt:tt)* }
table = [ $($table:tt)* ]
current = [ $current:ty ]
others = [ ]
} => {
generate_match! { #permutate_entry
$row
match ($e) { $($match_tt)* }
table = [ $($table)* ]
}
};
{ #generate_arm
$row:ident
match ($e:expr) { $($match_tt:tt)* }
table = [ $($table:tt)* ]
current = [ $current:ty ]
others = [ $first_other:ty, $($others:ty,)* ]
} => {
generate_match! { #generate_arm
$row
match ($e) {
$($match_tt)*
(stringify!($current), stringify!($first_other)) => {
let value = GenericStruct {
value: <$current as DataType>::convert(&$row.get_ref_unwrap(0)),
time: <$first_other as DataType>::convert(&$row.get_ref_unwrap(2)),
};
// Here you actually do whatever you want with the value. Adjust for your needs.
println!("{:?}", value);
}
}
table = [ $($table)* ]
current = [ $current ]
others = [ $($others,)* ]
}
};
// Entry
(
match ($e:expr) from ($($ty:ty),+) in $row:ident
) => {
generate_match! { #generate_permutations_table
$row
match ($e)
table = [ ]
rest = [ $($ty,)+ ]
transformed = [ ]
}
};
}
Invoke with:
generate_match!(
match ((data_type.as_str(), time_type.as_str()))
from (String, usize /* more types... */)
in row
);
Playground.

How can I get the T from an Option<T> when using syn?

I'm using syn to parse Rust code. When I read a named field's type using field.ty, I get a syn::Type. When I print it using quote!{#ty}.to_string() I get "Option<String>".
How can I get just "String"? I want to use #ty in quote! to print "String" instead of "Option<String>".
I want to generate code like:
impl Foo {
pub set_bar(&mut self, v: String) {
self.bar = Some(v);
}
}
starting from
struct Foo {
bar: Option<String>
}
My attempt:
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
let data: Data = ast.data;
match data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => {
fields.named.iter().for_each(|field| {
let name = &field.ident.clone().unwrap();
let ty = &field.ty;
quote!{
impl Foo {
pub set_bar(&mut self, v: #ty) {
self.bar = Some(v);
}
}
};
});
}
_ => {}
},
_ => panic!("You can derive it only from struct"),
}
My updated version of the response from #Boiethios, tested and used in a public crate, with support of several syntaxes for Option:
Option
std::option::Option
::std::option::Option
core::option::Option
::core::option::Option
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
use syn::{GenericArgument, Path, PathArguments, PathSegment};
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
match *ty {
syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
_ => None,
}
}
// TODO store (with lazy static) the vec of string
// TODO maybe optimization, reverse the order of segments
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
let idents_of_path = path
.segments
.iter()
.into_iter()
.fold(String::new(), |mut acc, v| {
acc.push_str(&v.ident.to_string());
acc.push('|');
acc
});
vec!["Option|", "std|option|Option|", "core|option|Option|"]
.into_iter()
.find(|s| &idents_of_path == *s)
.and_then(|_| path.segments.last())
}
extract_type_path(ty)
.and_then(|path| extract_option_segment(path))
.and_then(|path_seg| {
let type_params = &path_seg.arguments;
// It should have only on angle-bracketed param ("<String>"):
match *type_params {
PathArguments::AngleBracketed(ref params) => params.args.first(),
_ => None,
}
})
.and_then(|generic_arg| match *generic_arg {
GenericArgument::Type(ref ty) => Some(ty),
_ => None,
})
}
You should do something like this untested example:
use syn::{GenericArgument, PathArguments, Type};
fn extract_type_from_option(ty: &Type) -> Type {
fn path_is_option(path: &Path) -> bool {
leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments.iter().next().unwrap().ident == "Option"
}
match ty {
Type::Path(typepath) if typepath.qself.is_none() && path_is_option(typepath.path) => {
// Get the first segment of the path (there is only one, in fact: "Option"):
let type_params = typepath.path.segments.iter().first().unwrap().arguments;
// It should have only on angle-bracketed param ("<String>"):
let generic_arg = match type_params {
PathArguments::AngleBracketed(params) => params.args.iter().first().unwrap(),
_ => panic!("TODO: error handling"),
};
// This argument must be a type:
match generic_arg {
GenericArgument::Type(ty) => ty,
_ => panic!("TODO: error handling"),
}
}
_ => panic!("TODO: error handling"),
}
}
There's not many things to explain, it just "unrolls" the diverse components of a type:
Type -> TypePath -> Path -> PathSegment -> PathArguments -> AngleBracketedGenericArguments -> GenericArgument -> Type.
If there is an easier way to do that, I would be happy to know it.
Note that since syn is a parser, it works with tokens. You cannot know for sure that this is an Option. The user could, for example, type std::option::Option, or write type MaybeString = std::option::Option<String>;. You cannot handle those arbitrary names.

Resources