about summary refs log tree commit diff stats
path: root/crates/rocie-server/src/api/get
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rocie-server/src/api/get')
-rw-r--r--crates/rocie-server/src/api/get/product.rs43
1 files changed, 38 insertions, 5 deletions
diff --git a/crates/rocie-server/src/api/get/product.rs b/crates/rocie-server/src/api/get/product.rs
index 9356a68..55e5d91 100644
--- a/crates/rocie-server/src/api/get/product.rs
+++ b/crates/rocie-server/src/api/get/product.rs
@@ -1,10 +1,29 @@
-use actix_web::{HttpResponse, Responder, Result, get, web};
+use actix_web::{HttpRequest, HttpResponse, Responder, Result, get, web};
+use log::info;
+use percent_encoding::percent_decode_str;
 
 use crate::{
     app::App,
     storage::sql::product::{Product, ProductId, ProductIdStub},
 };
 
+/// A String, that is not url-decoded on parse.
+struct UrlEncodedString(String);
+
+impl UrlEncodedString {
+    /// Percent de-encode a given string
+    fn percent_decode(&self) -> Result<String, std::str::Utf8Error> {
+        percent_decode_str(self.0.replace('+', "%20").as_str())
+            .decode_utf8()
+            .map(|s| s.to_string())
+            .inspect(|s| info!("Decoded `{}` as `{s}`", self.0))
+    }
+
+    fn from_str(inner: &str) -> Self {
+        Self(inner.to_owned())
+    }
+}
+
 /// Get Product by id
 #[utoipa::path(
     responses(
@@ -40,14 +59,20 @@ pub(crate) async fn product_by_id(
         ("name" = String, description = "Name of the product" ),
     )
 )]
-// TODO: Html decode the name, before use. Otherwise `Milk 2` will not work, as it is send as
-// `Milk+2` <2025-10-21>
 #[get("/product/by-name/{name}")]
 pub(crate) async fn product_by_name(
     app: web::Data<App>,
+    req: HttpRequest,
     name: web::Path<String>,
 ) -> Result<impl Responder> {
-    let name = name.into_inner();
+    let _name = name;
+
+    let name = UrlEncodedString::from_str(
+        req.path()
+            .strip_prefix("/product/by-name/")
+            .expect("Will always exists"),
+    );
+    let name = name.percent_decode()?;
 
     match Product::from_name(&app, name).await? {
         Some(product) => Ok(HttpResponse::Ok().json(product)),
@@ -68,9 +93,17 @@ pub(crate) async fn product_by_name(
 #[get("/product/by-part-name/{name}")]
 pub(crate) async fn product_suggestion_by_name(
     app: web::Data<App>,
+    req: HttpRequest,
     name: web::Path<String>,
 ) -> Result<impl Responder> {
-    let name = name.into_inner();
+    let _name = name;
+
+    let name = UrlEncodedString::from_str(
+        req.path()
+            .strip_prefix("/product/by-part-name/")
+            .expect("Will always exists"),
+    );
+    let name = &name.percent_decode()?;
 
     let all = Product::get_all(&app).await?;