Reqwest not Posting to Slack Channel

Hi, I am trying to curl this json message to a slack channel, so I tried the following code:

let body = format!("payload={}", message);
let client = reqwest::blocking::Client::new();
let response = client.post(slackchan)
     .json(&body)
     .send()
     .unwrap();

Where slackchan is the link to the slack channel, and body is the string to be curled. However, when I run the code, it doesn't get sent to the slack channel. I know the link works, because in the ruby version:

body = {"text" => message}.to_json
`curl -XPOST #{slackchan} -d 'payload=#{body}'`

puts body

The body gets curled. Additionally, I know that body is not an empty string since I tried printing it before the reqwest post. I figure I am not using reqwest properly, or perhaps there's a different crate that would suit my needs better(I would prefer using reqwest since I need the string body to be formatted as a json file)?
Any help would be greatly appreciated. Thanks for your time.

Let's see what curl -d does:

Sends the specified data in a POST request to the HTTP server, in the same way that a browser does when a user has filled in an HTML form and presses the submit button. This will cause curl to pass the data to the server using the content-type application/x-www-form-urlencoded.

I haven't used reqwest, but my guess is you should send a multipart request (side note: request, not response). That appears to be the equivalent of -d, and it lets you submit a form field called payload.

let form = reqwest::multipart::Form::new()
    .text("payload", serde_json::to_string(message)?);
let request = client.post(slackchan)
    .multipart(form)
    .send()
    .unwrap();

Like the Ruby code, the JSON encoding happens first before constructing the request.

Also, you'll need the JSON to look like {"text": <...>}. Make sure it's wrapped in that envelope.

2 Likes

I am having trouble using multipart, because when I tried the code you suggested, the error: "could not find multipart in reqwest". Under dependencies I have:

reqwest = "0.11.4"

And I tried both:

use reqwest::blocking::multipart;
use reqwest:::multipart;

But, it says "no multipart in root". Additionally,

.multipart(form)

Gives the error "method not found in reqwest::blocking::RequestBuilder".

The docs state that this function needs to be enabled by the feature flag multipart. You can enable this dependency feature by adding something like the following to your Cargo.toml

[depencencies]
reqwest = { version = "0.11", features = ["multipart"] }
1 Like

Thanks, that worked for me. However, now I'm getting the following error: "

.text("payload", serde_json::to_string(&message));
    |          ^^^^ the trait `From<Result<std::string::String, serde_json::Error>>` is not implemented for `Cow<'static, str>`

I think it has to do with the fact that I am formatting message as a JSON. How would I go about dealing with this?

Unpack the Result with ? or .expect or the like to handle failure.

To help understand that error, .text is written in a really flexible, albeit complicated, way. It accepts anything that can be converted into a Cow<'static, str>. A Cow<str> is a reference type that can be constructed both from borrowed &str references and from owned Strings. By accepting Cows -- or anything convertible into a Cow -- it is effectively letting you pass a &str or a String, whichever you happen to have.

It makes for a complicated signature and error messages but don't hold it against them. They're trying to be helpful, I promise.

What I'm getting at is that all the Cow business should be ignored. The important part is that serde_json is wrapping it in a Result<_> because the conversion could fail. Unpack it and you'll be good to go. The String inside it is convertible to Cow<&str> so that should be all you need to do.

2 Likes

Sorry I don't think I was very clear. The error is from the following line:

let form = reqwest::blocking::multipart::Form::new()
        .text("payload", serde_json::to_string(&message));

And the problem is:

error[E0277]: the trait bound `Cow<'static, str>: From<Result<std::string::String, serde_json::Error>>` is not satisfied
   --> src/main.rs:246:10
    |
246 |         .text("payload", serde_json::to_string(&body));
    |          ^^^^ the trait `From<Result<std::string::String, serde_json::Error>>` is not implemented for `Cow<'static, str>`
    |
    = help: the following implementations were found:
              <Cow<'a, CStr> as From<&'a CStr>>
              <Cow<'a, CStr> as From<&'a CString>>
              <Cow<'a, CStr> as From<CString>>
              <Cow<'a, OsStr> as From<&'a OsStr>>
            and 15 others
    = note: required because of the requirements on the impl of `Into<Cow<'static, str>>` for `Result<std::string::String, serde_json::Error>`

Which I believe means that it expects .text() to contain a static and str instead of the current values which is a String, and a serde_json value. I tried .expect(), but the method isn't found for reqwest:blocking:multipart:Form.

You need to put the .expect("...") on the expression serde_json::to_string(&message), not on Form::new. This is because serde_json::to_string returns a Result value, which will be an Err if serialization failed. After that you should be good to go since Cow<'static, str> does implement From<String>.

3 Likes

Thanks for the clarification, the following code compiles and runs now:

let body = format!("payload={}", message);
let client = reqwest::blocking::Client::new();
let form = reqwest::blocking::multipart::Form::new()
     .text("payload", serde_json::to_string(&body).expect("could not be formatted"));
let request = client.post(slackchan)
     .multipart(form)
     .send()
     .unwrap(); 

However, I still don't see the message curled to the slack channel. I'm unsure as to why I don't see the output. Just to be clear, this is using slack's webhook app.

I think you want serde_json::to_string(&message) not serde_json::to_string(&body)? Otherwise the value of the payload field will start with payload= which seems redundant, plus the Display formatting of message might not be the JSON you want (and anyway it will be wrapped in an extra level of quotes/escaping by serde_json::to_string).

2 Likes

Yes, you're right. I changed it to &message. However, I'm still unsure how to get the output to show in the slackchannel.

Is this the API you're using? That doesn't seem to use multipart requests, you would just do

let response = client.post(slackchan)
     .json(&message)
     .send()
     .unwrap(); 

That's assuming the Serialize impl for whatever message is produces an object like { "text": "blah blah blah", "other_field": "foo" }—you could use a custom wrapper type or construct message as a serde_json::Value to achieve that. I don't see anything about a payload field.

Payload isn't supposed to be a field. It's just part of the string message that I want to output to the slack channel. The code you just posted is what I initially tried(if you look at my first post), but that did not send anything to the slack channel.

I think the problem with your initial code is that you were just sending a JSON string as the body, not an object with a "text" field. If message is a String or &str, this should do the trick (untested):

let body = format!("payload={}", message);
let object = serde_json::json!({ "text": body });
let client = reqwest::blocking::Client::new();
let response = client.post(slackchan)
     .json(&object)
     .send()
     .unwrap();

Sorry for the previous bad suggestion(s), I was a little slow getting up to speed.

1 Like

Thank you! That did the trick.
And don't worry about the suggestions it was a very long thread and I don't think I was being very clear/articulate.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.