reqwest send multipart form with very large attachment - rust

As this answer explains, it's possible to use Body::wrap_stream(read_stream) to POST the contents of a file without first reading the entire contents into memory.
How can we do the same thing as part of a reqwest::multipart::Form? The following code fails with the error the trait `From<&mut dyn Stream<Item = std::result::Result<Vec<u8>, std::io::Error>>>` is not implemented for `Body` .
let metadata_json = "{ \"file_owner\": \"bob smith\" }";
let metadata_part = reqwest::multipart::Part::text(metadata_json);
let read_stream : Stream<Item = std::io::Result<Vec<u8>> = my_file_stream;
let stream_part = reqwest::multipart::Part::stream(read_stream);
let multipart_form = reqwest::multipart::Form::new()
.part("metadata", metadata_part)
.part("file", stream_part);
I tried supplying an implementation of From that just calls Body::wrap_stream but it's forbidden since neither From nor Body is defined in my own code.

I believe you need to use Body::wrap_stream like this:
let stream_part = reqwest::multipart::Part::stream(Body::wrap_stream(read_stream));

Related

Change relative location for PathBuf

I have a few PathBufs in my Rust application:
let mut dog_path = PathBuf::from("./animals/dog.png");
let mut cow_path = PathBuf::from("./animals/bovine/cow.jpg");
How could I change these PathBufs so that they're being referred to from the ./animals directory?
// an operation on dog_path
// same operation on cow_path
assert_eq!(PathBuf::from("./dog.png"), dog_path);
assert_eq!(PathBuf::from("./bovine/cow.jpg"), cow_path);
I think you want Path::strip_prefix:
let dog_path = PathBuf::from("./animals/dog.png");
let cow_path = PathBuf::from("./animals/bovine/cow.jpg");
let dog_path_rel = dog_path.strip_prefix("./animals").unwrap();
let cow_path_rel = cow_path.strip_prefix("./animals").unwrap();
assert_eq!(Path::new("dog.png"), dog_path_rel);
assert_eq!(Path::new("bovine/cow.jpg"), cow_path_rel);
But that won't include the leading ./. If that's important to you, you can add it manually:
let dog_path_prefixed = Path::new("./").join(dog_path_rel);
let cow_path_prefixed = Path::new("./").join(cow_path_rel);
assert_eq!(PathBuf::from("./dog.png"), dog_path_prefixed);
assert_eq!(PathBuf::from("./bovine/cow.jpg"), cow_path_prefixed);
playground
Note that strip_prefix returns a Result, meaning it could fail if the path doesn't begin with the given prefix. You may want to handle this case instead of unwraping the result (causing your program to exit with a panic), or you may want to use .expect("your message here") instead to provide a meaningful error message.
If you want a general solution you could look at relative-path crate. It looks like it provide the functionality you want.
use std::path::PathBuf;
use relative_path::RelativePath;
fn main() {
let dog_path = PathBuf::from("./animals/dog.png");
let cow_path = PathBuf::from("./animals/bovine/cow.jpg");
let dog_path = RelativePath::from_path(&dog_path).unwrap();
let cow_path = RelativePath::from_path(&cow_path).unwrap();
let animals_dir = RelativePath::new("./animals");
let dog_path = animals_dir.relative(&dog_path).to_path(".");
let cow_path = animals_dir.relative(&cow_path).to_path(".");
assert_eq!(PathBuf::from("./dog.png"), dog_path);
assert_eq!(PathBuf::from("./bovine/cow.jpg"), cow_path);
}
This is a quick draft, but it shows how to do in a generic way what you are trying to accomplish. I think it could be further optimized, but I literally found this crate 10 minutes ago.

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.

Cannot get Hash::get_mut() and File::open() to agree about mutability

During a lengthy computation, I need to look up some data in a number of different files. I cannot know beforehand how many or which files exactly, but chances are high that each file is used many times (on the order of 100 million times).
In the first version, I opened the file (whose name is an intermediate result of the computation) each time for lookup.
In the second version, I have a HashMap<String, Box<File>> where I remember already open files and open new ones lazily on demand.
I couldn't manage to handle the mutable stuff that arises from the need to have Files to be mutable. I got something working, but it looks overly silly:
let path = format!("egtb/{}.egtb", self.signature());
let hentry = hash.get_mut(&self.signature());
let mut file = match hentry {
Some(f) => f,
None => {
let rfile = File::open(&path);
let wtf = Box::new(match rfile {
Err(ioe) => return Err(format!("could not open EGTB file {} ({})", path, ioe)),
Ok(opened) => opened,
});
hash.insert(self.signature(), wtf);
// the following won't work
// wtf
// &wtf
// &mut wtf
// So I came up with the following, but it doesn't feel right, does it?
hash.get_mut(&self.signature()).unwrap()
}
};
Is there a canonical way to get a mut File from File::open() or File::create()? In the manuals, this is always done with:
let mut file = File:open("foo.txt")?;
This means my function would have to return Result<_, io::Error> and I can't have that.
The problem seems to be that with the hash-lookup Some(f) gives me a &mut File but the Ok(f) from File::open gives me just a File, and I don't know how to make a mutable reference from that, so that the match arm's types match. I have no clear idea why the version as above at least compiles, but I'd very much like to learn how to do that without getting the File from the HashMap again.
Attempts to use wtf after it has been inserted into the hashmap fail to compile because the value was moved into the hashmap. Instead, you need to obtain the reference into the value stored in the hashmap. To do so without a second lookup, you can use the entry API:
let path = format!("egtb/{}.egtb", self.signature());
let mut file = match hash.entry(self.signature()) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
let rfile = File::open(&path)
.map_err(|_| format!("could not open EGTB file {} ({})", path, ioe))?;
e.insert(Box::new(rfile))
}
};
// `file` is `&mut File`, use it as needed
Note that map_err() allows you to use ? even when your function returns a Result not immediately compatible with the one you have.
Also note that there is no reason to box the File, a HashMap<String, File> would work just as nicely.

How can I pull data out of an Option for independent use?

Is there a way to 'pull' data out of an Option? I have an API call that returns Some(HashMap). I want to use the HashMap as if it weren't inside Some and play with the data.
Based on what I've read, it looks like Some(...) is only good for match comparisons and some built-in functions.
Simple API call pulled from crate docs:
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = reqwest::blocking::get("https://httpbin.org/ip")?
.json::<HashMap<String, String>>()?;
println!("{:#?}", resp.get("origin"));
Ok(())
}
Result:
Some("75.69.138.107")
if let Some(origin) = resp.get("origin") {
// use origin
}
If you can guarantee that it's impossible for the value to be None, then you can use:
let origin = resp.get("origin").unwrap();
Or:
let origin = resp.get("origin").expect("This shouldn't be possible!");
And, since your function returns a Result:
let origin = resp.get("origin").ok_or("This shouldn't be possible!")?;
Or with a custom error type:
let origin = resp.get("origin").ok_or(MyError::DoesntExist)?;
The most common way is with if let:
if let Some(origin) = resp.get("origin") {
origin.do_stuff()
}
For more fine grained control, you can use pattern matching:
match resp.get("origin") {
Some(origin) => origin.do_stuff(),
None => panic!("origin not found!")
}
You could also use unwrap, which will give you the underlying value of the option, or panic if it is None:
let origin = resp.get("origin").unwrap();
You can customize the panic message with expect:
let origin = resp.get("origin").expect("Oops!");
Or compute a default value with unwrap_or:
let origin = resp.get("origin").unwrap_or(&String::from("192.168.0.1"));
You can also return an error instead of panicking:
let origin = resp.get("origin").ok_or(Error::UnknownOrigin)?;
Your options are a plenty.
if let Some(origin) = resp.get("origin") {
// do stuff using origin
}
origin = resp.get("origin").unwrap()
// will panic if None
resp.get("origin").map(|origin| {
// do stuff using inner value, returning another option
})
resp.get("origin").and_then(|origin| {
// same as map but short-circuits if there is no inner value
})

How do I parse a page with html5ever, modify the DOM, and serialize it?

I would like to parse a web page, insert anchors at certain positions and render the modified DOM out again in order to generate docsets for Dash. Is this possible?
From the examples included in html5ever, I can see how to read an HTML file and do a poor man's HTML output, but I don't understand how I can modify the RcDom object I retrieved.
I would like to see a snippet inserting an anchor element (<a name="foo"></a>) to an RcDom.
Note: this is a question regarding Rust and html5ever specifically ... I know how to do it in other languages or simpler HTML parsers.
Here is some code that parses a document, adds an achor to the link and prints the new document:
extern crate html5ever;
use html5ever::{ParseOpts, parse_document};
use html5ever::tree_builder::TreeBuilderOpts;
use html5ever::rcdom::RcDom;
use html5ever::rcdom::NodeEnum::Element;
use html5ever::serialize::{SerializeOpts, serialize};
use html5ever::tendril::TendrilSink;
fn main() {
let opts = ParseOpts {
tree_builder: TreeBuilderOpts {
drop_doctype: true,
..Default::default()
},
..Default::default()
};
let data = "<!DOCTYPE html><html><body></body></html>".to_string();
let dom = parse_document(RcDom::default(), opts)
.from_utf8()
.read_from(&mut data.as_bytes())
.unwrap();
let document = dom.document.borrow();
let html = document.children[0].borrow();
let body = html.children[1].borrow(); // Implicit head element at children[0].
{
let mut a = body.children[0].borrow_mut();
if let Element(_, _, ref mut attributes) = a.node {
attributes[0].value.push_tendril(&From::from("#anchor"));
}
}
let mut bytes = vec![];
serialize(&mut bytes, &dom.document, SerializeOpts::default()).unwrap();
let result = String::from_utf8(bytes).unwrap();
println!("{}", result);
}
This prints the following:
<html><head></head><body></body></html>
As you can see, we can navigate through the child nodes via the children attribute.
And we can change an attribute present in the vector of attributes of an Element.

Resources