Skip to content

Conversation

@MATATAT
Copy link

@MATATAT MATATAT commented Aug 28, 2025

Added support for URL encoded forms to framework. This was based off a quick extension trait that I was using in a project I'm working on.

Alternatively, I wrote a separate version of this that follows the httpmock interface more closely. It was a lot more invasive than this change. Here is a link to the diff if you'd like to consider that approach as well.

@declark1
Copy link
Collaborator

Thanks for the contribution @MATATAT! I'll have a look today (both versions) and provide feedback later.

FYI, the DCO check is failing as our organization requires commits to be signed. I think git rebase HEAD~3 --signoff should fix it.

Signed-off-by: Matt Macdonald <matt.david.macdonald@gmail.com>
Signed-off-by: Matt Macdonald <matt.david.macdonald@gmail.com>
Signed-off-by: Matt Macdonald <matt.david.macdonald@gmail.com>
Copy link
Collaborator

@declark1 declark1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @MATATAT!

Since form data is body content, this can be simplified to be an additional When body convenience method for the existing BodyMatcher, instead of adding a new matcher and FormBody struct.

I would suggest the following implementation for When (accepting multiple kv-pairs for convenience):

/// Body convenience methods.
impl When {
    // ...

    /// URL encoded form body.
    pub fn form(
        self,
        params: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
    ) -> Self {
        let params = params
            .into_iter()
            .map(|(key, value)| (key.into(), value.into()))
            .collect::<Vec<(String, String)>>();
        let body = serde_urlencoded::to_string(params).unwrap();
        self.push(matchers::body(Body::bytes(body)));
        self
    }
}

Usage:

    let mut mocks = MockSet::new();
    mocks.mock(|when, then| {
        when.post()
            .header("content-type", "application/x-www-form-urlencoded")
            .form([("hello", "world")]);
        then.text("hello world");
    });

I tested and this appears to work, let me know what you think.

@MATATAT
Copy link
Author

MATATAT commented Aug 29, 2025

Thanks @declark1, let me take a look at this. I believe I had something like this before and there was an issue where the matcher didn't end up being consistent since the HashMap doesn't enforce insertion order. So when the body matcher would compare the byte order was sporadically inconsistent.

I'll look back through my commits when I was doing this via an extension trait and see if I left any comments about this (or if my implementation was different).

@MATATAT
Copy link
Author

MATATAT commented Sep 18, 2025

Hey! Sorry it took me a while to circle back around on this.

The problem I was running into (and was able to repro this with your suggested changes) are that using the Body matcher the values must maintain their insert order. Since reqwest is also mapping a HashMap this can get out of order fairly easily. Also, with your proposed input pattern, you can pass a HashMap or HashSet into the When builder, which will cause this to fail.

I do like your approach to the inputs, so I'd like to incorporate that, but I do think there will need to be a separate matcher for the form body.

I might have some time this weekend so I'll make some changes and push another commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants