get title from Zotero item - rust

Still very new to Rust, trying to understand how to extract the title of a JournalArticle using the Zotero crate.
I've got this, and can confirm the item is retrieved successfully:
let zc = ZoteroCredentials::new();
let z = ZoteroInit::set_user(&zc.api_id, &zc.api_key);
let item = z.get_item(item_id, None).unwrap();
From here, I see that an item.data is an ItemType, specifically a JournalArticleData. But I'm fundamentally not quite understanding how to either a) serialize this to JSON, or b) access .title as a property.
For context, this would be the result of a Rocket GET route.
Any help would much appreciated!

It sounds like the part you're missing is how to use pattern matching on an enum. I'm not familiar with zotero so this is all based on the docs, with verbose type annotations to be explicit about what I think I'm doing:
use zotero::data_structure::item::{Item, ItemType, JournalArticleData};
let item: Item = z.get_item(item_id, None).unwrap();
// Now we must extract the JournalArticle from the ItemType, which is an enum
// and therefore requires pattern matching.
let article: JournalArticleData = match item.data {
ItemType::JournalArticle(a) => a,
something_else => todo!("handle wrong type of item"),
}
let title: String = article.title;
(The match could also be written as an if let just as well.)
You could also use pattern matching to go through the entire structure, rather than only the enum which requires it:
match z.get_item(item_id, None).unwrap() {
Item {
data: ItemType::JournalArticle(JournalArticleData {
title,
..
}),
..
} => {
// Use the `title` variable here
},
something_else => todo!("handle wrong type of item"),
}

Related

How to define a macro vecvec to initialize a vector of vectors?

Just like vec![2,3,4], can we define a similar macro vecvec to initialize vector of vector. Eg.
let vv0 = vecvec![[2,3,4],[5,6,7]]; // vec of 2 vecs
let vv1 = vecvec![[1,2,3]];
let vv2 = vecvec![[1,2,3], []];
let vv3 = vecvec![[1,3,2]; 2];
You just need to think through the problem. You really only have 2 main cases. The first case being if elements are listed (Ex: a, b, c) and the second where a single value and length are given (Ex: a; b). We can even check our work by reading the documentation for vec!. In the documentation we can see vec! is defined as follows:
macro_rules! vec {
() => { ... };
($elem:expr; $n:expr) => { ... };
($($x:expr),+ $(,)?) => { ... };
}
As you can see, they have 3 cases. We didn't specify the the case were no items are included, but that does not really matter since your macro can call vec! and have it handle that case for you.
We can just copy the cases in their macro and add the functionality inside. The only other issue that might stop you is that [a, b, c] is an expression in of itself. Luckily we can just skip that by specifying items as requiring brackets and pick out the items ourselves before passing them off to vec!.
macro_rules! vecvec {
([$($elem:expr),*]; $n:expr) => {{
let mut vec = Vec::new();
vec.resize_with($n, || vec![$($elem),*]);
vec
}};
($([$($x:expr),*]),* $(,)?) => {
vec![$(vec![$($x),*]),*]
};
}
Instead of defining a new macro. You can initialize the vector of the vector.
In the example below, I'm explicitly setting type. It's not necessary but a good practice.
let vv0:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5,6,7]];
let vv1:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5]];
let vv2:Vec<Vec<u32>> = vec![vec![],vec![5,6,7]];
let vv3:Vec<Vec<u32>> = vec![vec![2,3,4],vec![]];

error handling when unwrapping several try_into calls

I have a case where I need to parse some different values out from a vector.
I made a function for it, that returns a option, which either should give a option or a None, depending on whether the unwrapping succeeds.
Currently it looks like this:
fn extract_edhoc_message(msg : Vec<u8>)-> Option<EdhocMessage>{
let mtype = msg[0];
let fcnt = msg[1..3].try_into().unwrap();
let devaddr = msg[3..7].try_into().unwrap();
let msg = msg[7..].try_into().unwrap();
Some(EdhocMessage {
m_type: mtype,
fcntup: fcnt,
devaddr: devaddr,
edhoc_msg: msg,
})
}
But, I would like to be able to return a None, if any of the unwrap calls fail.
I can do that by pattern matching on each of them, and then explicitly return a None, if anything fails, but that would a lot of repeated code.
Is there any way to say something like:
"if any of these unwraps fail, return a None?"
This is exactly what ? does. It's even shorter than the .unwrap() version:
fn extract_error_message(msg: Vec<u8>) -> Option<EdhocMessage> {
let m_type = msg[0];
let fcntup = msg[1..3].try_into().ok()?;
let devaddr = msg[3..7].try_into().ok()?;
let edhoc_msg = msg[7..].try_into().ok()?;
Some(EdhocMessage {
m_type,
fcntup,
devaddr,
edhoc_msg
})
}
See this relevant part of the Rust Book.

Why can't this be done with if?

I'm trying to handle errors received from an async call:
let res: Result<TcpStream, Box<dyn std::error::Error>> = session.runtime().borrow_mut().block_on(async {
let fut = TcpStream::connect(session.configuration().socket()).await?;
Ok(fut)
});
I tried to do it the old school way with an if but the compiler didn't like it:
if res.is_err() {
return Err(res);
}
After some googling I came across this:
let mut stream = match res {
Ok(res) => res,
Err(res) => return Err(res),
};
which feels very much the same but with Rusts' equivalent of a switch statement. Why can't I use the if?
if res.is_err() { return res } should work. Result is an enum with two variants: Ok which by convention holds a "successful" result, and Err which holds error information. As John pointed out, wrapping the existing Result (which happens to hold an Err) in another Err result doesn't make sense - or, more precisely, doesn't match the return type of the function.
When you use match, you unpack the result into its constituent values, and then in the error case re-pack it into a new result. Note that instead of the match statement use can use the ? operator, which would compress the declaration to just:
let mut stream = res?;

Idiomatic rust way to properly parse Clap ArgMatches

I'm learning rust and trying to make a find like utility (yes another one), im using clap and trying to support command line and config file for the program's parameters(this has nothing to do with the clap yml file).
Im trying to parse the commands and if no commands were passed to the app, i will try to load them from a config file.
Now I don't know how to do this in an idiomatic way.
fn main() {
let matches = App::new("findx")
.version(crate_version!())
.author(crate_authors!())
.about("find + directory operations utility")
.arg(
Arg::with_name("paths")
...
)
.arg(
Arg::with_name("patterns")
...
)
.arg(
Arg::with_name("operation")
...
)
.get_matches();
let paths;
let patterns;
let operation;
if matches.is_present("patterns") && matches.is_present("operation") {
patterns = matches.values_of("patterns").unwrap().collect();
paths = matches.values_of("paths").unwrap_or(clap::Values<&str>{"./"}).collect(); // this doesn't work
operation = match matches.value_of("operation").unwrap() { // I dont like this
"Append" => Operation::Append,
"Prepend" => Operation::Prepend,
"Rename" => Operation::Rename,
_ => {
print!("Operation unsupported");
process::exit(1);
}
};
}else if Path::new("findx.yml").is_file(){
//TODO: try load from config file
}else{
eprintln!("Command line parameters or findx.yml file must be provided");
process::exit(1);
}
if let Err(e) = findx::run(Config {
paths: paths,
patterns: patterns,
operation: operation,
}) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
There is an idiomatic way to extract Option and Result types values to the same scope, i mean all examples that i have read, uses match or if let Some(x) to consume the x value inside the scope of the pattern matching, but I need to assign the value to a variable.
Can someone help me with this, or point me to the right direction?
Best Regards
Personally I see nothing wrong with using the match statements and folding it or placing it in another function. But if you want to remove it there are many options.
There is the ability to use the .default_value_if() method which is impl for clap::Arg and have a different default value depending on which match arm is matched.
From the clap documentation
//sets value of arg "other" to "default" if value of "--opt" is "special"
let m = App::new("prog")
.arg(Arg::with_name("opt")
.takes_value(true)
.long("opt"))
.arg(Arg::with_name("other")
.long("other")
.default_value_if("opt", Some("special"), "default"))
.get_matches_from(vec![
"prog", "--opt", "special"
]);
assert_eq!(m.value_of("other"), Some("default"));
In addition you can add a validator to your operation OR convert your valid operation values into flags.
Here's an example converting your match arm values into individual flags (smaller example for clarity).
extern crate clap;
use clap::{Arg,App};
fn command_line_interface<'a>() -> clap::ArgMatches<'a> {
//Sets the command line interface of the program.
App::new("something")
.version("0.1")
.arg(Arg::with_name("rename")
.help("renames something")
.short("r")
.long("rename"))
.arg(Arg::with_name("prepend")
.help("prepends something")
.short("p")
.long("prepend"))
.arg(Arg::with_name("append")
.help("appends something")
.short("a")
.long("append"))
.get_matches()
}
#[derive(Debug)]
enum Operation {
Rename,
Append,
Prepend,
}
fn main() {
let matches = command_line_interface();
let operation = if matches.is_present("rename") {
Operation::Rename
} else if matches.is_present("prepend"){
Operation::Prepend
} else {
//DEFAULT
Operation::Append
};
println!("Value of operation is {:?}",operation);
}
I hope this helps!
EDIT:
You can also use Subcommands with your specific operations. It all depends on what you want to interface to be like.
let app_m = App::new("git")
.subcommand(SubCommand::with_name("clone"))
.subcommand(SubCommand::with_name("push"))
.subcommand(SubCommand::with_name("commit"))
.get_matches();
match app_m.subcommand() {
("clone", Some(sub_m)) => {}, // clone was used
("push", Some(sub_m)) => {}, // push was used
("commit", Some(sub_m)) => {}, // commit was used
_ => {}, // Either no subcommand or one not tested for...
}

PHP-like string parsing

I'm writing a mini-console of sorts and I'm trying to figure out how to extract things from a link. For example, in PHP this is a request variable
so:
http://somelink.com/somephp.php?variable1=10&variable2=20
Then PHP figures out the url parameters and assigns them to a variable.
How would I parse something like this in Swift?
So, given the string I'd want to take: variable1=10 and variable2=20 etc, is there a simple way to do this? I tried googling around but didn't really know what I was searching for.
I have a really horrible hacky way of doing this but it's not really extendable.
You’d be wanting NSURLComponents:
import Foundation
let urlStr = "http://somelink.com/somephp.php?variable1=10&variable2=20"
let components = NSURLComponents(string: urlStr)
components?.queryItems?.first?.name // Optional("variable1")
components?.queryItems?.first?.value // Optional("10")
You might find it helpful to add a subscript operator for the query items:
extension NSURLComponents {
subscript(queryItemName: String) -> String? {
// of course, if you do this a lot,
// cache it in a dictionary instead
for item in self.queryItems ?? [] {
if item.name == queryItemName {
return item.value
}
}
return nil
}
}
if let components = NSURLComponents(string: urlStr) {
components["variable1"] ?? "No value"
}

Resources