Repository: lipanski/mockito (Rust)

Issue:
values with spaces from serde_urlencoded don't match Matcher::UrlEncoded
trivial example to reproduce:
```rust
use serde::Serialize;
use serde_urlencoded;
use mockito;

#[derive(Serialize)]
struct Form {
    pub key: String,
}

#[test]
pub fn bug_demo() {
    let _mock = mockito::mock("POST", "/")
        // mockito expects this to be "value%20with%20spaces!"
       .match_body(mockito::Matcher::UrlEncoded("key".into(), "value with spaces!".into()))
       .create();
    
    reqwest::blocking::Client::new()
        .post(&mockito::server_url())
        // encoded by serde_urlencoded to "value+with+spaces!"
        .form(&Form { key: "value with spaces!".into() })
        .send()
        .unwrap()
        .error_for_status()
        .unwrap(); // <- This panics because mockito responds 501 Not Implemented
}
```


Discussion:
this is on:
- rust 1.43.1
- mockito 0.25.2
- serde 1.0.111
- serde_urlencoded 0.6.1 (latest)
- serde_urlencoded 0.5.5 (pinned by `reqwest`)

perhaps a potential solution might be to use a dedicated URL encoding / decoding library (like `serde_urlencoded` rather than `percent-encoding`?
@cakekindel fair point. would you like to open a PR to switch the matcher to using `serde_urlencoded`? otherwise I'll have a look when I get some time.

id love to! ill set aside some time soonish to do so.
@cakekindel great, thank you!



Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 4629fe1..a8c1ef0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,8 +25,8 @@ serde_json = "1.0.17"
 difference = "2.0"
 colored = { version = "1.6", optional = true }
 log = "0.4.6"
-percent-encoding = "2.1.0"
 assert-json-diff = "1.0.3"
+serde_urlencoded = "0.6.1"
 
 [dev-dependencies]
 env_logger = "0.7"
diff --git a/src/lib.rs b/src/lib.rs
index f8e94c0..f09ad7f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -558,11 +558,11 @@ mod server;
 type Request = request::Request;
 type Response = response::Response;
 
-use percent_encoding::percent_decode;
 use rand::distributions::Alphanumeric;
 use rand::{thread_rng, Rng};
 use regex::Regex;
 use std::cell::RefCell;
+use std::collections::HashMap;
 use std::convert::{From, Into};
 use std::fmt;
 use std::io;
@@ -753,18 +753,15 @@ impl Matcher {
                 let actual: serde_json::Value = serde_json::from_str(other).unwrap();
                 assert_json_include_no_panic(&actual, &expected).is_ok()
             }
-            Matcher::UrlEncoded(ref expected_field, ref expected_value) => other
-                .split('&')
-                .map(|pair| {
-                    let mut parts = pair.splitn(2, '=');
-                    let field =
-                        percent_decode(parts.next().unwrap().as_bytes()).decode_utf8_lossy();
-                    let value =
-                        percent_decode(parts.next().unwrap_or("").as_bytes()).decode_utf8_lossy();
-
-                    (field.to_string(), value.to_string())
-                })
-                .any(|(ref field, ref value)| field == expected_field && value == expected_value),
+            Matcher::UrlEncoded(ref expected_field, ref expected_value) => {
+                serde_urlencoded::from_str::<HashMap<String, String>>(other)
+                    .map(|params: HashMap<_, _>| {
+                        params.into_iter().any(|(ref field, ref value)| {
+                            field == expected_field && value == expected_value
+                        })
+                    })
+                    .unwrap_or(false)
+            }
             Matcher::Any => true,
             Matcher::AnyOf(ref matchers) => matchers.iter().any(|m| m.matches_value(other)),
             Matcher::AllOf(ref matchers) => matchers.iter().all(|m| m.matches_value(other)),


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index 01ecf83..9066ff8 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -1296,6 +1296,19 @@ fn test_match_partial_query_by_urlencoded_all_of() {
     assert_eq!("HTTP/1.1 501 Mock Not Found\r\n", status_line);
 }
 
+#[test]
+fn test_match_query_with_non_percent_url_escaping() {
+    let _m = mock("GET", "/hello")
+        .match_query(Matcher::AllOf(vec![
+            Matcher::UrlEncoded("num ber".into(), "o ne".into()),
+            Matcher::UrlEncoded("hello".into(), "world".into()),
+        ]))
+        .create();
+
+    let (status_line, _, _) = request("GET /hello?hello=world&something=else&num+ber=o+ne", "");
+    assert_eq!("HTTP/1.1 200 OK\r\n", status_line);
+}
+
 #[test]
 fn test_match_missing_query() {
     let _m = mock("GET", "/hello").match_query(Matcher::Missing).create();