My simple rust program uses jsonpath crate to lookup some values in a json document.
The code that I am using is the following
use serde::Deserialize;
extern crate jsonpath;
extern crate serde_json;
use jsonpath::Selector;
use serde_json::Value;
use std::any::{Any, TypeId};
fn main() {
let jsondoc = r#"
{
"a": 10,
"b": "a string",
"c" : false,
"point" : {
"x" : 1,
"y": 2
}
}
"#;
let json: Value = serde_json::from_str(jsondoc).unwrap(); // Parse JSON document
let selector1 = Selector::new("$.a").unwrap(); // Create a JSONPath selector
let result1: Vec<f64> = selector1.find(&json)
.map(|t| t.as_f64().unwrap())
.collect();
println!("{:?}", result1);
let selector2 = Selector::new("$.b").unwrap(); // Create a JSONPath selector
let result2: Vec<&str> = selector2.find(&json)
.map(|t| t.as_str().unwrap())
.collect();
println!("{:?}", result2);
let selector3 = Selector::new("$.c").unwrap(); // Create a JSONPath selector
let result3: Vec<bool> = selector3.find(&json)
.map(|t| t.as_bool().unwrap())
.collect();
println!("{:?}", result3);
}
A concern is that if I change the json value "a" from 10 to "ten" (different data type) the code crashes in this statement: map(|t| t.as_f64().unwrap()) (cannot unwrap)
How do I protect the code in order to avoid panics?
As mentioned in comments, the problem here is that unwrap panics on errors. The solution is to propagate your result around your program, bubbling it up until you want to handle it.
There's one complication, though: as_*() methods don't return a Result, but rather an Option. If we want to treat it as an error case and propogate it around the program, turning it into a Result will make that much nicer.
Here's a simple way to do that using ok_or:
let selector3 = Selector::new("$.c").unwrap(); // Create a JSONPath selector
let result3: Vec<bool> = selector3.find(&json)
.map(|t| t.as_bool().ok_or("expected boolean, found something else").unwrap())
.collect();
println!("{:?}", result3);
If you want to make this error message better and/or include more information in it, I think making a custom error enum is probably the best solution. There's a lot of detail on that in this answer to a related question about error handling, but in short, if you create your own error type containing more information, you can easily make the errors much more readable:
use snafu::Snafu; // 0.6.8
#[derive(Debug, Snafu)]
enum MyError {
#[snafu(display("expected {}, found {}", value))]
WrongValueType {
expected: &'static str,
actual: serde_json::Value,
}
}
let selector3 = Selector::new("$.c").unwrap(); // Create a JSONPath selector
let result3: Vec<bool> = selector3.find(&json)
.map(|t| t.as_bool().ok_or_else(|| MyError::WrongValueType { expected: "boolean", actual: t.clone() }).unwrap())
.collect();
println!("{:?}", result3);
This uses the snafu crate, but there are other options (or it can be done manually). Again, see the above linked answer for more information.
Alright - we have a Result. Now, we need to propogate it up. This adds some boilerplate, but fortunately, it shouldn't have to change the style all that much. In particular, Iterator::collect can be used to collect into anything implementing FromIterator, and Result implements FromIterator.
Replacing each map with something like this will iterate until either all results are found Ok(), or one is an Err:
let selector3 = Selector::new("$.c").unwrap(); // Create a JSONPath selector
let result3: Vec<bool> = selector3.find(&json)
.map(|t| t.as_bool().ok_or("expected boolean, found something else"))
// &'static str is our error type
.collect::<Result<Vec<bool>, &'static str>>()
.unwrap();
println!("{:?}", result3);
We're still unwrapping, so it'll still panic, but that brings the error one step up. To deal with it at the top level, you'll need to stick this into a function returning a Result itself, and then match on that result to deal with the error case. It's a common pattern to do this by turning our main function into just error handling, and then to make our "real main" a separate function returning a Result. Here's an application of that on your snippet of code:
// `Box<dyn std::error::Error>` encapsulates "any error" without giving
// access to the details
fn try_main() -> Result<(), Box<dyn std::error::Error>> {
let jsondoc = r#"
{
"a": 10,
"b": "a string",
"c" : false,
"point" : {
"x" : 1,
"y": 2
}
}
"#;
let json: Value = serde_json::from_str(jsondoc)?; // Parse JSON document
let selector1 = Selector::new("$.a").unwrap(); // Create a JSONPath selector
let result1: Vec<f64> = selector1.find(&json)
.map(|t| t.as_f64().ok_or("expected boolean, found something else"))
.collect::<Result<_, _>>()?;
println!("{:?}", result1);
// ... remaining code can be translated as this one.
}
fn main() {
match try_main() {
Ok(()) => {}
Err(e) => {
eprintln!("Error occurred: {}", e);
std::process::exit(1);
}
}
}
Related
I can't figure out how to do import- and instancing-lines such that they tolerate non-existing files/modules and structs.
I tried making a macro that unwraps into such lines based on what files it finds in the directory, using a crate I found that had promise - include_optional - which allows to check for existence of files already at compile-time (since it's a macro).
However, I can't figure out how to use it properly in a macro, neither did I manage to use it without macro using the example at bottom of the docs conditional compilation chapter.
if cfg!(unix) { "unix" } else if cfg!(windows) { "windows" } else { "unknown" } (from the docs)
vs
if include_optional::include_bytes_optional!("day1.rs").is_some() { Some(&day01::Day01 {}) } else { None } // assume day1.rs and thus Day01 are non-existent (my attempt at doing same thing)
My if-statement compiles both cases, including the unreachable code (causing a compilation error), despite how according to the the docs it supposedly doesn't for cfg! ("conditional compilation").
Essentially, what I want is something of this form:
// Macro to generate code based on how many files/structs has been created
// There are anywhere between 1-25 days
get_days_created!;
/* // Let's assume 11 have been created so far, then the macro should evaluate to this code:
* mod day1;
* use day1 as day0#;
* // ...
* mod day11;
* use day11 as day11;
*
* // ...
* fn main() -> Result<(), i32> {
* let days : Vec<&dyn Day> = vec![
* &day01::Day01 {},
* // ...
* &day11::Day11 {},
* ];
* // ...
* }
*/
The solution is to create a proc_macro. These function similar to regular macros except they allow you to write a function of actual code they should execute, instead being given (and returning) a 'TokenStream' to parse the given tokens (and, respectively, what tokens the macro should expand to).
To create a proc_macro, the first and most important piece of information you need to know is that you can't do this anywhere. Instead, you need to create a new library, and in its Cargo.toml file you need to set proc-macro = true. Then you can declare them in its lib.rs. An example TOML would look something like this:
[package]
name = "some_proc_macro_lib"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
glob = "0.3.0"
regex = "1.7.0"
Then you can create your macros in this library as regular functions, with the #[proc_macro] attribute/annotation. Here's an example lib.rs with as few dependencies as possible. For my exact question, the input TokenStream is irrelevant and can be ignored, and instead you want to generate and return a new one:
use proc_macro::TokenStream;
use glob::glob;
use regex::Regex;
#[proc_macro]
pub fn import_days(_: TokenStream) -> TokenStream {
let mut stream = TokenStream::new();
let re = Regex::new(r".+(\d+)").unwrap();
for entry in glob("./src/day*.rs").expect("Failed to read pattern") {
if let Ok(path) = entry {
let prefix = path.file_stem().unwrap().to_str().unwrap();
let caps = re.captures(prefix);
if let Some(caps) = caps {
let n: u32 = caps.get(1).unwrap().as_str().parse().unwrap();
let day = &format!("{}", prefix);
let day_padded = &format!("day{:0>2}", n);
stream.extend(format!("mod {};", day).parse::<TokenStream>().unwrap());
if n < 10 {
stream.extend(format!("use {} as {};", day, day_padded).parse::<TokenStream>().unwrap());
}
}
}
}
return proc_macro::TokenStream::from(stream);
}
The question could be considered answered with this already, but the answer can and should be further expanded on in my opinion. And as such I will do so.
Some additional explanations and suggestions, beyond the scope of the question
There are however quite a few other crates beside proc_macro that can aid you with both parsing the input stream, and building the output one. Of note are the dependencies syn and quote, and to aid them both there's the crate proc_macro2.
The syn crate
With syn you get helpful types, methods and macros for parsing the input Tokenstream. Essentially, with a struct Foo implementing syn::parse::Parse and the macro let foo = syn::parse_macro_input!(input as Foo) you can much more easily parse it into a custom struct thanks to syn::parse::ParseStream. An example would be something like this:
use proc_macro2::Ident;
use syn;
use syn::parse::{Parse, ParseStream};
#[derive(Debug, Default)]
struct Foo {
idents: Vec<Ident>,
}
impl syn::parse::Parse for Foo {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut foo= Foo::default();
while !input.is_empty() {
let fn_ident = input.parse::<Ident>()?;
foo.idents.push(fn_ident);
// Optional comma: Ok vs Err doesn't matter. Just consume if it exists and ignore failures.
input.parse::<syn::token::Comma>().ok();
}
return Ok(foo);
}
}
Note that the syn::Result return-type allows for nice propagation of parsing-errors when using the sugary ? syntax: input.parse::<SomeType>()?
The quote crate
With quote you get a helpful macro for generating a tokenstream more akin to how macro_rules does it. As an argument you write essentially regular code, and tell it to use the value of variables by prefixing with #.
Do note that you can't just pass it variables containing strings and expect it to expand into identifiers, as strings resolve to the value "foo" (quotes included). ie. mod "day1"; instead of mod day1;. You need to turn them into either:
a proce_macro2::Ident
syn::Ident::new(foo_str, proc_macro2::Span::call_site())
or a proc_macro2::TokenStream
foo_str.parse::<TokenStream>().unwrap()
The latter also allows to convert longer strings with more than a single Ident, and manages things such as literals etc., making it possible to skip the quote! macro entirely and just use this tokenstream directly (as seen in import_days).
Here's an example that creates a struct with dynamic name, and implements a specific trait for it:
use proc_macro2::TokenStream;
use quote::quote;
// ...
let mut stream = TokenStream::new();
stream.extend(quote!{
#[derive(Debug)]
pub struct #day_padded_upper {}
impl Day for #day_padded_upper {
#trait_parts
}
});
return proc_macro::TokenStream::from(stream);
Finally, on how to implement my question
This 'chapter' is a bit redundant, as I essentially answered it with the first two code-snippets (.toml and fn import_days), and the rest could have been considered an exercise for the reader. However, while the question is about reading the filesystem at compile-time in a macro to 'dynamically' change its expansion (sort of), I wrote it in a more general form asking how to achieve a specific result (as old me didn't know macro's could do that). So for completion I'll include this 'chapter' nevertheless.
There is also the fact that the last macro in this 'chapter' - impl_day (which wasn't mentioned at all in the question) - serves as a good example of how to achieve two adjacent but important and relevant tasks.
Retrieving and using call-site's filename.
Parsing the input TokenStream using the syn dependency as shown above.
In other words: knowing all the above, this is how you can create macros for importing all targeted files, instantiating structs for all targeted files, as well as to declare + define the struct from current file's name.
Importing all targeted files:
See import_days above at the start.
Instantiating Vec with structs from all targeted files:
#[proc_macro]
pub fn instantiate_days(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
let re = Regex::new(r".+(\d+)").unwrap();
let mut stream = TokenStream::new();
let mut block = TokenStream::new();
for entry in glob("./src/day*.rs").expect("Failed to read pattern") {
match entry {
Ok(path) => {
let prefix = path.file_stem().unwrap().to_str().unwrap();
let caps = re.captures(prefix);
if let Some(caps) = caps {
let n: u32 = caps.get(1).unwrap().as_str().parse().unwrap();
let day_padded = &format!("day{:0>2}", n);
let day_padded_upper = &format!("Day{:0>2}", n);
let instance = &format!("&{}::{} {{}}", day_padded, day_padded_upper).parse::<TokenStream>().unwrap();
block.extend(quote!{
v.push( #instance );
});
}
},
Err(e) => println!("{:?}", e),
}
}
stream.extend(quote!{
{
let mut v: Vec<&dyn Day> = Vec::new();
#block
v
}
});
return proc_macro::TokenStream::from(stream);
}
Declaring and defining struct for current file invoking this macro:
#[derive(Debug, Default)]
struct DayParser {
parts: Vec<Ident>,
}
impl Parse for DayParser {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut day_parser = DayParser::default();
while !input.is_empty() {
let fn_ident = input.parse::<Ident>()?;
// Optional, Ok vs Err doesn't matter. Just consume if it exists.
input.parse::<syn::token::Comma>().ok();
day_parser.parts.push(fn_ident);
}
return Ok(day_parser);
}
}
#[proc_macro]
pub fn impl_day(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut stream = TokenStream::new();
let span = Span::call_site();
let binding = span.source_file().path();
let file = binding.to_str().unwrap();
let re = Regex::new(r".*day(\d+).rs").unwrap();
let caps = re.captures(file);
if let Some(caps) = caps {
let n: u32 = caps.get(1).unwrap().as_str().parse().unwrap();
let day_padded_upper = format!("Day{:0>2}", n).parse::<TokenStream>().unwrap();
let day_parser = syn::parse_macro_input!(input as DayParser);
let mut trait_parts = TokenStream::new();
for (k, fn_ident) in day_parser.parts.into_iter().enumerate() {
let k = k+1;
let trait_part_ident = format!("part_{}", k).parse::<TokenStream>().unwrap();
// let trait_part_ident = proc_macro::Ident::new(format!("part_{}", k).as_str(), span);
trait_parts.extend(quote!{
fn #trait_part_ident(&self, input: &str) -> Result<String, ()> {
return Ok(format!("Part {}: {:?}", #k, #fn_ident(input)));
}
});
}
stream.extend(quote!{
#[derive(Debug)]
pub struct #day_padded_upper {}
impl Day for #day_padded_upper {
#trait_parts
}
});
} else {
// don't generate anything
let str = format!("Tried to implement Day for a file with malformed name: file = \"{}\" , re = \"{:?}\"", file, re);
println!("{}", str);
// compile_error!(str); // can't figure out how to use these
}
return proc_macro::TokenStream::from(stream);
}
I am saving in append mode a stream of events on a YAML log file, where each event is represented by an indivual document, like this:
---
type: event
id: 1
---
type: trigger
id: 2
At some point later I want to iterate on these events, parsing each via serde_yaml. To my understanding though, serde_yaml doesn't seem to support parsing multiple documents from a single reader, as none of the available methods mention it, and trying to parse multiple documents at once results in a MoreThanOneDocument error.
use std::io::{self, BufRead};
use serde_yaml;
use serde::{self, Deserialize};
#[derive(Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Message {
Event { id: i32 },
Trigger { id: i32},
}
fn main() -> io::Result<()> {
let yaml = "---\ntype: event\nid: 1\n---\n\ntype: trigger\nid: 2";
let v: Message = serde_yaml::from_reader(yaml.as_bytes()).unwrap();
println!("{:?}", v);
Ok(())
}
I'm totally new to Rust, so maybe I completely missed the point of serde and just did not understand how to do it.
How would you parse such YAML, please?
I cooked up something that looks like a working solution, but I think I'll try to post it among the answers instead, because I don't want to bias other answers too much towards my solution. I kindly encourage you to have a look at it as well however, any feedback is welcome.
The documentation of serde_yaml::Deserializer shows an example very similar to yours. It would work like this:
use serde::Deserialize;
#[derive(Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Message {
Event { id: i32 },
Trigger { id: i32 },
}
fn main() {
let yaml = "---\ntype: event\nid: 1\n---\ntype: trigger\nid: 2\n";
for document in serde_yaml::Deserializer::from_str(yaml) {
let v = Message::deserialize(document).unwrap();
println!("{:?}", v);
}
}
I really hope to find a native solution by using serde and serde_yaml only, but until then the way I got it working is as follows.
trait BufReaderYamlExt {
fn read_next_yaml(&mut self) -> io::Result<Option<String>>;
}
impl<T: io::Read> BufReaderYamlExt for io::BufReader<T> {
fn read_next_yaml(&mut self) -> io::Result<Option<String>> {
const sep : &str = "\n---\n";
let mut doc = String::with_capacity(200);
while self.read_line(&mut doc)? > 0 {
if doc.len() > sep.len() && doc.ends_with(sep) {
doc.truncate(doc.len() - sep.len());
break;
}
}
if !doc.is_empty() {
doc.shrink_to_fit();
Ok(Some(doc))
} else {
Ok(None)
}
}
}
The trait extends the BufReader with an extra method that returns an optional owned String (or None at the end of the stream) containing just the portion with a single YAML document.
By iterating on it one could then apply serde_json::from_str() to parse the document into a Message struct.
fn main() -> io::Result<()> {
let yaml = "---\ntype: event\nid: 1\n\n---\n\ntype: trigger\nid: 2\n";
let mut r = io::BufReader::new(yaml.as_bytes());
while let Some(next) = r.read_next_yaml()? {
let d: Message = serde_yaml::from_str(&next).unwrap();
println!("parsed: {:?}", d);
}
Ok(())
}
I've made available the full source on the rust playground as well.
How can I get the value of a struct which is returned in a Result from another function? Example below.
#[derive(Debug)]
pub struct Keypair(ed25519_dalek::Keypair);
pub fn keypair_from_seed(seed: &[u8]) -> Result<Keypair, Box<dyn error::Error>> {
let dalek_keypair = ed25519_dalek::Keypair { secret, public };
Ok(Keypair(dalek_keypair))
}
fn main(){
//here seed_bytes is mnemonics
let sk = keypair_from_seed(&seed_bytes);
//sk contains the secret key and public key, i want to extract it different secret key & public key
}
If you feel very confident
let sk = keypair_from_seed(&seed_bytes).unwrap();
or
let sk = keypair_from_seed(&seed_bytes).expect("my own failure message");
However, it is recommended to proceed like this
if let Ok(sk) = keypair_from_seed(&seed_bytes) {
// ... use sk ...
} else {
// ... sk is not available, may be should
// we warn the user, ask for an alternative ...
}
or, if you want to explicitly handle the error
match keypair_from_seed(&seed_bytes) {
Ok(sk) => {
// ... use sk ...
},
Err(e) => {
// ... sk is not available, and e explains why ...
},
}
Note that, if the function containing these lines is also
able to return an error, you can just propagate it with
the ? notation (if the error returned by
keypair_from_seed() is convertible into the error returned
by your function)
let sk = keypair_from_seed(&seed_bytes)?;
see
unwrap,
expect,
if let,
match,
?
Lets look the definition of Result in Rust documentation
enum Result<T, E> {
Ok(T),
Err(E),
}
So a Result is either Ok which contains a value with type T, or Err which contains a value with type E.
You have couple options to extract the value.
1- result.unwrap() or result.expect("error message")
This function returns the Ok value if result is Ok or panics the program (program is terminated). If you are sure that it doesn't contain error or you just want to write the correct case first and deal with error handling later it makes sense but you shouldn't use it all the time since it directly crashes the app when the value is not Ok.
You can use it like this
let val = result.unwrap();
// or
let val = result.expect("oops not Ok");
Only difference of expect you can provide the error message yourself instead of the standard error message of unwrap.
2- Pattern matching
In Rust, pattern matching is used for enum types so that user can do the necessary thing based on the current variant of the enum. You can use it like this
match result {
Ok(val) => {
// Use val here....
},
Err(err) => {
// Do something with the error if you want
}
}
If you are going to handle only one variant, you can also use if let statement like this
if let Some(val) = result {
// Do something with val
}
The returned result from the function is of the type Result<Keypair, Box<dyn error::Error>>.
There are multiple ways to extract a result from the Result container. Basically rust wants you to check for any errors and handle it. If no errors, you can extract the result and use it.
if let Ok(sk) = keypair_from_seed(&seed) {
let public = sk.0.public;
let secret = sk.0.secret;
/* use your keys */
}
Notice the sk.0 since you are using a struct of a tuple type. If your struct had multiple variables, something like
pub struct KeyTuple(ed25519_dalek::Keypair, i32, &str);
You would have used it as
let kt = keytuple_from_seed(&seed).unwrap();
let kp: ed25519_dalek::Keypair = kt.0;
let le: i32 = kt.1;
let st: &str = kt.2;
I have working example of a simple loop (mostly taken from the odbc crate's example):
use std::io;
use odbc::*;
use odbc_safe::AutocommitOn;
fn main(){
let env = create_environment_v3().map_err(|e| e.unwrap()).unwrap();
let conn = env.connect_with_connection_string(CONN_STRING).unwrap();
let mut stmt = Statement::with_parent(&conn).unwrap();
loop {
let mut sql_text = String::new();
println!("Please enter SQL statement string: ");
io::stdin().read_line(&mut sql_text).unwrap();
stmt = match stmt.exec_direct(&sql_text).unwrap() {
Data(mut stmt) => {
let cols = stmt.num_result_cols().unwrap();
while let Some(mut cursor) = stmt.fetch().unwrap() {
for i in 1..(cols + 1) {
match cursor.get_data::<&str>(i as u16).unwrap() {
Some(val) => print!(" {}", val),
None => print!(" NULL"),
}
}
println!();
}
stmt.close_cursor().unwrap()
}
NoData(stmt) => {println!("Query executed, no data returned"); stmt}
}
}
}
I don't want to create new Statements for each query, as I just can .close_cursor().
I'd like to extract the loop's body to a function, like this:
fn exec_stmt(stmt: Statement<Allocated, NoResult, AutocommitOn>) {
//loop's body here
}
But I just can't! The .exec_direct() method mutably consumes my Statement and returns another. I tried different ways to pass Statement arg to the function (borrow, RefCell, etc), but they all fail when using in a loop. I am still new to Rust, so most likely I just don't know something, or does the .exec_direct's Statement consumption makes it impossible?
There's no nice way to move and then move back values through parameters. It's probably best to copy what .exec_direct does and just make the return type of your function a statement as well.
The usage would then look like this:
let mut stmt = Statement::with_parent(&conn).unwrap();
loop {
stmt = exec_stmt(stmnt);
}
and your function signature would be:
fn exec_stmt(stmt: Statement<...>) -> Statement<...> {
match stmt.exec_direct() {
...
}
}
I probably wouldn't recommend this, but if you really wanted to get it to work you could use Option and the .take() method.
fn exec_stmt(some_stmt: &mut Option<Statement<...>>) {
let stmt = some_stmt.take().unwrap();
// do stuff ...
some_stmt.replace(stmt);
}
The odbc-safe crate tried to have each state transition of ODBC reflected in a different type. The odbc-api crate also tries to protect you from errors, but is a bit more subtle about it. Your use case would be covered by the the Preallocated struct.
The analog example from the odbc-api documentation looks like this:
use odbc_api::{Connection, Error};
use std::io::{self, stdin, Read};
fn interactive(conn: &Connection) -> io::Result<()>{
let mut statement = conn.preallocate().unwrap();
let mut query = String::new();
stdin().read_line(&mut query)?;
while !query.is_empty() {
match statement.execute(&query, ()) {
Err(e) => println!("{}", e),
Ok(None) => println!("No results set generated."),
Ok(Some(cursor)) => {
// ...print cursor contents...
},
}
stdin().read_line(&mut query)?;
}
Ok(())
}
This will allow you to declare a function without any trouble:
use odbc_api::Preallocated;
fn exec_statement(stmt: &mut Preallocated) {
// loops body here
}
This is what I have, but I want to avoid using unwrap on my reqwest values:
extern crate base64;
extern crate reqwest;
use serde_json;
use serde_json::json;
pub fn perform_get(id: String) -> serde_json::value::Value {
let client = reqwest::Client::builder().build().unwrap();
let url = String::from("SomeURL");
let res = client.get(&url).send().unwrap().text();
let mut v = json!(null);
match res {
Ok(n) => {
v = serde_json::from_str(&n).unwrap();
}
Err(r) => {
println!("Something wrong happened {:?}", r);
}
}
v
}
fn main() {
println!("Hi there! i want the function above to return a result instead of a Serde value so I can handle the error in main!");
}
Here is a link to a rust playground example
The official Rust book, The Rust Programming Language, is freely available online. It has an entire chapter on using Result, explaining introductory topics such as the Result enum and how to use it.
How to return a Result containing a serde_json::Value?
The same way you return a Result of any type; there's nothing special about Value:
use serde_json::json; // 1.0.38
pub fn ok_example() -> Result<serde_json::value::Value, i32> {
Ok(json! { "success" })
}
pub fn err_example() -> Result<serde_json::value::Value, i32> {
Err(42)
}
If you have a function that returns a Result, you can use the question mark operator (?) to exit early from a function on error, returning the error. This is a concise way to avoid unwrap or expect:
fn use_them() -> Result<(), i32> {
let ok = ok_example()?;
println!("{:?}", ok);
let err = err_example()?;
println!("{:?}", err); // Never executed, we always exit due to the `?`
Ok(()) // Never executed
}
This is just a basic example.
Applied to your MCVE, it would look something like:
use reqwest; // 0.9.10
use serde_json::Value; // 1.0.38
type Error = Box<dyn std::error::Error>;
pub fn perform_get(_id: String) -> Result<Value, Error> {
let client = reqwest::Client::builder().build()?;
let url = String::from("SomeURL");
let res = client.get(&url).send()?.text()?;
let v = serde_json::from_str(&res)?;
Ok(v)
}
Here, I'm using the trait object Box<dyn std::error::Error> to handle any kind of error (great for quick programs and examples). I then sprinkle ? on every method that could fail (i.e. returns a Result) and end the function with an explicit Ok for the final value.
Note that the panic and the never-used null value can be removed with this style.
See also:
What is this question mark operator about?
Rust proper error handling (auto convert from one error type to another with question mark)
Rust return result error from fn
Return value from match to Err(e)
What is the idiomatic way to handle/unwrap nested Result types?
better practice to return a Result
See also:
Should I avoid unwrap in production application?
If you are in the user side I would suggest to use Box<dyn std::error::Error>, this allow to return every type that implement Error, ? will convert the concrete error type to the dynamic boxed trait, this add a little overhead when there is an error but when error are not expected or really rare this is not a big deal.
use reqwest;
use serde_json::value::Value;
use std::error::Error;
fn perform_get(_id: String) -> Result<Value, Box<dyn Error>> {
let client = reqwest::Client::builder().build()?;
let url = String::from("SomeURL");
let res = client.get(&url).send()?.text()?;
let v = serde_json::from_str(&res)?;
Ok(v)
// last two line could be serde_json::from_str(&res).map_err(std::convert::Into::into)
}
fn main() {
println!("{:?}", perform_get("hello".to_string()));
}
This produce the following error:
Err(Error { kind: Url(RelativeUrlWithoutBase), url: None })
The kind smart folks over at Rust Discord helped me solve this one. (user noc)
extern crate base64;
extern crate reqwest;
pub fn get_jira_ticket() -> Result<serde_json::value::Value, reqwest::Error> {
let client = reqwest::Client::builder().build().unwrap();
let url = String::from("SomeURL");
let res = client.get(&url).send().and_then(|mut r| r.json());
res
}
fn main() {
println!("This works");
}
The key part was this in the header for the return
-> Result<serde_json::value::Value, reqwest::Error>
And this here to actually return the data.
client.get(&url).send().and_then(|mut r| r.json());