keep the output from a post request inside a string variable - rust

Hi I explain the problem
I'm actually sending a request to the OpenApi and the reply is an url that is basically an image generated by OpenAI.
I would like to keep this url inside a variable after sending a request rather than get this url in the terminal with the println!("{:?}", res);
I need to use this url in an other file after. it's why it's more interesting for me to keep this url in a string variable after the request.
use exitfailure::ExitFailure;
use reqwest::{
header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE},
Url,
};
use serde_derive::{Deserialize, Serialize};
use std::collections::hash_map::*;
// use std::env;
#[derive(Serialize, Deserialize, Debug)]
struct GenerationImage {}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
url: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Response {
data: [Data; 1],
}
impl GenerationImage {
async fn post(api_key: &str) -> Result<(), ExitFailure> {
let key = format!("Bearer {}", api_key);
let prompt = "a cat behind a tree".to_string();
let url = "https://api.openai.com/v1/images/generations";
let mut map = HashMap::new();
map.insert("prompt", prompt);
let endpoint = Url::parse(url)?;
let client = reqwest::Client::new();
let res = client
.post(endpoint)
.header(AUTHORIZATION, key)
.header(CONTENT_TYPE, "application/json")
.header(ACCEPT, "application/json")
.json(&map)
.send()
.await?
.json::<Response>()
.await;
println!("{:?}", res);
Ok(())
}
}
fn main() {
let api_key = "API";
GenerationImage::post(&api_key);
}

To convert an item that implements Debug into the corresponding String you can do this:
let s = format!("{:?}", res);

Related

How to get error propagated back to caller with tide and glommio?

I am experimenting with tide and glommio using Rust. Consider following code:
use std::io::Result;
use glommio::prelude::*;
use tide::Request;
use tide::Response;
use tide::http::mime;
use tide::prelude::*;
#[async_std::main]
async fn main() -> Result<()> {
let mut app = tide::new();
app.at("/orders/shoes").post(order_shoes);
let builder = LocalExecutorPoolBuilder::new(16);
let handles = builder.on_all_shards(|| async move {
app.listen("127.0.0.1:8080").await;
}).unwrap();
handles.join_all();
Ok(())
}
#[derive(Debug, Deserialize)]
struct Animal {
name: String,
legs: u8,
}
pub async fn order_shoes(mut req: Request<()>) -> tide::Result {
let Animal { name, legs } = req.body_json().await?;
let res = Response::builder(203)
.body("Hi")
.header("custom-header", "value")
.content_type(mime::HTML)
.build();
Ok(res)
}
Now in case the request has legs greater than 255 then it should respond with error but I am unable to figure out how to return that to caller.

How to get the body of a Response in actix_web unit test?

I'm building a web API service with Rust and actix_web.
I want to test a route and check if the received response body is what I expect. But I'm struggling with converting the received body ResponseBody<Body> into JSON or BSON. The called route actually returns application/json.
let mut app = test::init_service(App::new()
.data(AppState { database: db.clone() })
.route("/recipes/{id}", web::post().to(add_one_recipe))
).await;
let payload = create_one_recipe().as_document().unwrap().clone();
let req = test::TestRequest::post()
.set_json(&payload).uri("/recipes/new").to_request();
let mut resp = test::call_service(&mut app, req).await;
let body: ResponseBody<Body> = resp.take_body(); // Here I want the body as Binary, String, JSON, or BSON. The response is actually application/json.
The actix/examples repository achieves this by defining a new trait called BodyTest...
Actix Web 3
trait BodyTest {
fn as_str(&self) -> &str;
}
impl BodyTest for ResponseBody<Body> {
fn as_str(&self) -> &str {
match self {
ResponseBody::Body(ref b) => match b {
Body::Bytes(ref by) => std::str::from_utf8(&by).unwrap(),
_ => panic!(),
},
ResponseBody::Other(ref b) => match b {
Body::Bytes(ref by) => std::str::from_utf8(&by).unwrap(),
_ => panic!(),
},
}
}
}
After which you may simply do:
assert_eq!(resp.response().body().as_str(), "Your name is John");
Actix Web 4
This trait is now much simpler (you could skip entirely):
trait BodyTest {
fn as_str(&self) -> &str;
}
impl BodyTest for Bytes {
fn as_str(&self) -> &str {
std::str::from_utf8(self).unwrap()
}
}
And to use it:
let body = to_bytes(resp.into_body()).await.unwrap();
assert_eq!(body.as_str(), "Your name is John");
Reference to full code these excerpts were taken from: https://github.com/actix/examples/blob/master/forms/form/src/main.rs
Having a look at Body and ResponseBody, this looks like the approach:
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Greet {
name: String,
}
async fn greet() -> impl Responder {
let body = serde_json::to_string(&Greet {
name: "Test".to_owned(),
})
.unwrap();
HttpResponse::Ok()
.content_type("application/json")
.body(body)
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/", web::get().to(greet)))
.bind("127.0.0.1:8000")?
.run()
.await
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{body::Body, test, web, App};
use serde_json::json;
#[actix_rt::test]
async fn test_greet_get() {
let mut app = test::init_service(App::new().route("/", web::get().to(greet))).await;
let req = test::TestRequest::with_header("content-type", "application/json").to_request();
let mut resp = test::call_service(&mut app, req).await;
let body = resp.take_body();
let body = body.as_ref().unwrap();
assert!(resp.status().is_success());
assert_eq!(
&Body::from(json!({"name":"Test"})), // or serde.....
body
);
}
}
running 1 test
test tests::test_greet_get ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
This works for testing in one go, the body and status code :
let req = test::TestRequest::get()
.uri(&format!("/brand/{}", 999))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let body = test::read_body(resp).await;
assert_eq!(actix_web::web::Bytes::from("Item not found"), body);
#[actix_rt::test]
pub async fn test_index() {
let mut app = test::init_service(App::new().service(ping)).await;
let req = test::TestRequest::get().uri("/ping").to_request();
let result = test::read_response(&mut app, req).await;
assert_eq!(result, Bytes::from_static(b"PONG"));
}
Please see the doc

Sending "application/x-www-form-urlencoded" data in place of JSON over network

The definition of struct which I use to serialize over network
pub struct NetworkData {
id: String,
status: String,
details: <Data Structure>,
}
Now there's a function which accepts this structure, serializes it
and sends over the network.
fn send_data(data: NetworkData ...) -> ... {
let data = serde_json::to_string(&data).expect("serialize issue");
let mut request = Request::new(reqwest::Method::POST, url);
*request.body_mut() = Some(data.into());
self.inner
.execute(request)
...
}
Now I want to send "x-www-form-urlencoded" data over network which should
change this function as follows :-
fn send_data(data: NetworkData ...) -> ... {
// How should I change this?????
//let data = serde_json::to_string(&data).expect("serialize issue");
let mut request = Request::new(reqwest::Method::POST, url);
let content_type = HeaderValue::from_str(&format!("{}", "application/x-www-form-urlencoded",))
.expect("Header value creation bug");
request
.headers_mut()
.insert(header::CONTENT_TYPE, content_type);
*request.body_mut() = Some(data.into());
self.inner
.execute(request)
...
}
But how should I organize my "data" to fit into this picture.
You can most likely use the serde_urlencoded crate in exactly the same way you did with the JSON.
I have no idea what your <Data Structure> looks like, since you haven't provided it, but the serde_urlencoded crate only supports primitive types, so if you have more fancy things, you'll have to come up with your own transformation; x-www-form-urlencoded is just a set of key=value pairs. Anyway, here's a working sample:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct NetworkData {
id: String,
status: String,
data: u32,
}
fn main() {
let data = NetworkData {
id: "ID".into(),
status: "Status".into(),
data: 42,
};
let data = serde_urlencoded::to_string(&data).expect("serialize issue");
println!("{}", data);
}
playground

Calling async reqwest from with actix-web

In my actix-web-server, I'm trying to use reqwest to call an external server, and then return the response back to the user.
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
use futures::Future;
use lazy_static::lazy_static;
use reqwest::r#async::Client as HttpClient;
#[macro_use] extern crate serde_json;
#[derive(Debug, Deserialize)]
struct FormData {
title: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct Response {
title: String,
}
fn main() {
HttpServer::new(|| {
App::new()
.route("/validate", web::post().to(validator))
})
.bind("127.0.0.1:8000")
.expect("Can not bind to port 8000")
.run()
.unwrap();
}
fn validator(form: web::Form<FormData>) -> impl Responder {
let _resp = validate(form.title.clone());
HttpResponse::Ok()
}
pub fn validate(title: String) -> impl Future<Item=String, Error=String> {
let url = "https://jsonplaceholder.typicode.com/posts";
lazy_static! {
static ref HTTP_CLIENT: HttpClient = HttpClient::new();
}
HTTP_CLIENT.post(url)
.json(
&json!({
"title": title,
})
)
.send()
.and_then(|mut resp| resp.json())
.map(|json: Response| {
println!("{:?}", json);
json.title
})
.map_err(|error| format!("Error: {:?}", error))
}
This has two issues:
println!("{:?}", json); never appears to run, or at least I never see any output.
I get _resp back, which is a Future, and I don't understand how I can wait for that to resolve so I can pass a string back to the Responder
For reference:
$ curl -data "title=x" "https://jsonplaceholder.typicode.com/posts"
{
"title": "x",
"id": 101
}
To make the future block until it is resolved you have to call wait on it, but that's not ideal.
You can make your validator function return a future and in the route call to_async instead of to. The framework will poll and send the response when the future is resolved.
Also you should consider using the http client that comes with actix web and reduce one dependency from your application.
fn main() {
HttpServer::new(|| {
App::new()
.route("/validate", web::post().to_async(validator))
})
.bind("127.0.0.1:8000")
.expect("Can not bind to port 8000")
.run()
.unwrap();
}
fn validator(form: web::Form<FormData>) -> impl Future<Item=String, Error=String> {
let url = "https://jsonplaceholder.typicode.com/posts";
lazy_static! {
static ref HTTP_CLIENT: HttpClient = HttpClient::new();
}
HTTP_CLIENT.post(url)
.json(
&json!({
"title": form.title.clone(),
})
)
.send()
.and_then(|mut resp| resp.json())
.map(|json: Response| {
println!("{:?}", json);
HttpResponse::Ok().body(Body::from(json.title))
})
.map_err(|error| format!("Error: {:?}", error))
}

How to create a TOML file from Rust?

I have collected all my data into a vector and I need to create a TOML file with that data. I have managed to create and open a file:
let mut file = try!(File::create("servers.toml"));
My vector<(string,(string, u32))> contains the following data, which should look like this in TOML.
[server.A]
Ipaddr="192.168.4.1"
Port no=4476
[server.B]
......
I have a lot of data which needs to be written in TOML and I know TOML is a text file. How is encoder used for?
This uses the TOML crate for the structure and serialization. The main benefit is that values should be properly escaped.
use std::fs;
use toml::{map::Map, Value}; // 0.5.1
fn to_toml(v: Vec<(String, (String, u32))>) -> Value {
let mut servers = Map::new();
for (name, (ip_addr, port)) in v {
let mut server = Map::new();
server.insert("Ipaddr".into(), Value::String(ip_addr));
server.insert("Port no".into(), Value::Integer(port as i64));
servers.insert(name, Value::Table(server));
}
let mut map = Map::new();
map.insert("server".into(), Value::Table(servers));
Value::Table(map)
}
fn main() {
let v = vec![
("A".into(), ("192.168.4.1".into(), 4476)),
("B".into(), ("192.168.4.8".into(), 1234)),
];
let toml_string = toml::to_string(&to_toml(v)).expect("Could not encode TOML value");
println!("{}", toml_string);
fs::write("servers.toml", toml_string).expect("Could not write to file!");
}
You can also use this with Serde's automatic serialization and deserialization to avoid dealing with the low-level details:
use serde::Serialize; // 1.0.91
use std::{collections::BTreeMap, fs};
use toml; // 0.5.1
#[derive(Debug, Default, Serialize)]
struct Servers<'a> {
servers: BTreeMap<&'a str, Server<'a>>,
}
#[derive(Debug, Serialize)]
struct Server<'a> {
#[serde(rename = "Ipaddr")]
ip_addr: &'a str,
#[serde(rename = "Port no")]
port_no: i64,
}
fn main() {
let mut file = Servers::default();
file.servers.insert(
"A",
Server {
ip_addr: "192.168.4.1",
port_no: 4476,
},
);
file.servers.insert(
"B",
Server {
ip_addr: "192.168.4.8",
port_no: 1234,
},
);
let toml_string = toml::to_string(&file).expect("Could not encode TOML value");
println!("{}", toml_string);
fs::write("servers.toml", toml_string).expect("Could not write to file!");
}

Resources