From f8d5ad6b5349843d16ec391c711b7b05eddce428 Mon Sep 17 00:00:00 2001
From: Jonas Platte <jplatte@users.noreply.github.com>
Date: Tue, 21 Jul 2020 01:54:26 +0200
Subject: [PATCH] Make web::Path a tuple struct with a public inner value
 (#1594)

Co-authored-by: Rob Ede <robjtede@icloud.com>
---
 CHANGES.md        |  3 +++
 MIGRATION.md      | 20 +++++++++++++++++
 README.md         |  4 ++--
 src/lib.rs        |  4 ++--
 src/types/path.rs | 57 +++++++++++++++++++++++------------------------
 5 files changed, 55 insertions(+), 33 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 4b6697a7..5783da75 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,10 +4,13 @@
 ### Changed
 * `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set
   using `App::data`. [#1610]
+* `web::Path` now has a public representation: `web::Path(pub T)` that enables
+  destructuring. [#1594]
 
 ### Fixed
 * Memory leak of app data in pooled requests. [#1609]
 
+[#1594]: https://github.com/actix/actix-web/pull/1594
 [#1609]: https://github.com/actix/actix-web/pull/1609
 [#1610]: https://github.com/actix/actix-web/pull/1610
 
diff --git a/MIGRATION.md b/MIGRATION.md
index d2e9735f..7459b5b2 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -12,6 +12,26 @@
 * `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
   `u64` instead of a `usize`.
 
+* Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
+  destructuring or `.into_inner()`. For example:
+
+  ```rust
+  // Previously:
+  async fn some_route(path: web::Path<(String, String)>) -> String {
+    format!("Hello, {} {}", path.0, path.1)
+  }
+
+  // Now (this also worked before):
+  async fn some_route(path: web::Path<(String, String)>) -> String {
+    let (first_name, last_name) = path.into_inner();
+    format!("Hello, {} {}", first_name, last_name)
+  }
+  // Or (this wasn't previously supported):
+  async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String {
+    format!("Hello, {} {}", first_name, last_name)
+  }
+  ```
+
 ## 2.0.0
 
 * `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
diff --git a/README.md b/README.md
index 4d6bac29..4f6a1619 100644
--- a/README.md
+++ b/README.md
@@ -61,8 +61,8 @@ Code:
 use actix_web::{get, web, App, HttpServer, Responder};
 
 #[get("/{id}/{name}/index.html")]
-async fn index(info: web::Path<(u32, String)>) -> impl Responder {
-    format!("Hello {}! id:{}", info.1, info.0)
+async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
+    format!("Hello {}! id:{}", name, id)
 }
 
 #[actix_web::main]
diff --git a/src/lib.rs b/src/lib.rs
index eb46af66..8776a62b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,8 +9,8 @@
 //! use actix_web::{get, web, App, HttpServer, Responder};
 //!
 //! #[get("/{id}/{name}/index.html")]
-//! async fn index(info: web::Path<(u32, String)>) -> impl Responder {
-//!     format!("Hello {}! id:{}", info.1, info.0)
+//! async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
+//!     format!("Hello {}! id:{}", name, id)
 //! }
 //!
 //! #[actix_web::main]
diff --git a/src/types/path.rs b/src/types/path.rs
index 82050171..dbb5f3ee 100644
--- a/src/types/path.rs
+++ b/src/types/path.rs
@@ -25,8 +25,8 @@ use crate::FromRequest;
 /// /// extract path info from "/{username}/{count}/index.html" url
 /// /// {username} - deserializes to a String
 /// /// {count} -  - deserializes to a u32
-/// async fn index(info: web::Path<(String, u32)>) -> String {
-///     format!("Welcome {}! {}", info.0, info.1)
+/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
+///     format!("Welcome {}! {}", username, count)
 /// }
 ///
 /// fn main() {
@@ -61,20 +61,18 @@ use crate::FromRequest;
 ///     );
 /// }
 /// ```
-pub struct Path<T> {
-    inner: T,
-}
+pub struct Path<T>(pub T);
 
 impl<T> Path<T> {
     /// Deconstruct to an inner value
     pub fn into_inner(self) -> T {
-        self.inner
+        self.0
     }
 }
 
 impl<T> AsRef<T> for Path<T> {
     fn as_ref(&self) -> &T {
-        &self.inner
+        &self.0
     }
 }
 
@@ -82,31 +80,31 @@ impl<T> ops::Deref for Path<T> {
     type Target = T;
 
     fn deref(&self) -> &T {
-        &self.inner
+        &self.0
     }
 }
 
 impl<T> ops::DerefMut for Path<T> {
     fn deref_mut(&mut self) -> &mut T {
-        &mut self.inner
+        &mut self.0
     }
 }
 
 impl<T> From<T> for Path<T> {
     fn from(inner: T) -> Path<T> {
-        Path { inner }
+        Path(inner)
     }
 }
 
 impl<T: fmt::Debug> fmt::Debug for Path<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.inner.fmt(f)
+        self.0.fmt(f)
     }
 }
 
 impl<T: fmt::Display> fmt::Display for Path<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.inner.fmt(f)
+        self.0.fmt(f)
     }
 }
 
@@ -120,8 +118,8 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
 /// /// extract path info from "/{username}/{count}/index.html" url
 /// /// {username} - deserializes to a String
 /// /// {count} -  - deserializes to a u32
-/// async fn index(info: web::Path<(String, u32)>) -> String {
-///     format!("Welcome {}! {}", info.0, info.1)
+/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
+///     format!("Welcome {}! {}", username, count)
 /// }
 ///
 /// fn main() {
@@ -173,7 +171,7 @@ where
 
         ready(
             de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
-                .map(|inner| Path { inner })
+                .map(Path)
                 .map_err(move |e| {
                     log::debug!(
                         "Failed during Path extractor deserialization. \
@@ -290,21 +288,22 @@ mod tests {
         resource.match_path(req.match_info_mut());
 
         let (req, mut pl) = req.into_parts();
-        let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
+        let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
             .await
             .unwrap();
-        assert_eq!((res.0).0, "name");
-        assert_eq!((res.0).1, "user1");
+        assert_eq!(res.0, "name");
+        assert_eq!(res.1, "user1");
 
-        let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request(
-            &req, &mut pl,
-        )
-        .await
-        .unwrap();
-        assert_eq!((res.0).0, "name");
-        assert_eq!((res.0).1, "user1");
-        assert_eq!((res.1).0, "name");
-        assert_eq!((res.1).1, "user1");
+        let (Path(a), Path(b)) =
+            <(Path<(String, String)>, Path<(String, String)>)>::from_request(
+                &req, &mut pl,
+            )
+            .await
+            .unwrap();
+        assert_eq!(a.0, "name");
+        assert_eq!(a.1, "user1");
+        assert_eq!(b.0, "name");
+        assert_eq!(b.1, "user1");
 
         let () = <()>::from_request(&req, &mut pl).await.unwrap();
     }
@@ -329,7 +328,7 @@ mod tests {
         let s = s.into_inner();
         assert_eq!(s.value, "user2");
 
-        let s = Path::<(String, String)>::from_request(&req, &mut pl)
+        let Path(s) = Path::<(String, String)>::from_request(&req, &mut pl)
             .await
             .unwrap();
         assert_eq!(s.0, "name");
@@ -344,7 +343,7 @@ mod tests {
         assert_eq!(s.as_ref().key, "name");
         assert_eq!(s.value, 32);
 
-        let s = Path::<(String, u8)>::from_request(&req, &mut pl)
+        let Path(s) = Path::<(String, u8)>::from_request(&req, &mut pl)
             .await
             .unwrap();
         assert_eq!(s.0, "name");