This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-connectors.git


The following commit(s) were added to refs/heads/main by this push:
     new 26d689382 AWS SPI: preserve content-type parameters and drop 
unreachable map entry (#1905)
26d689382 is described below

commit 26d689382dd0cdca85db47fa710b04b310b5e446
Author: PJ Fanning <[email protected]>
AuthorDate: Thu Oct 1 15:29:18 2026 +0100

    AWS SPI: preserve content-type parameters and drop unreachable map entry 
(#1905)
    
    Motivation:
    tryCreateCustomContentType split content-type values only on '/', so a
    value with parameters (e.g. "text/plain; charset=UTF-8") produced a
    custom binary media type whose subtype embedded the parameters
    ("plain; charset=UTF-8") instead of a properly modeled ContentType.
    The contentTypeMap also carried an entry keyed
    "application/x-www-form-urlencoded; charset-UTF-8" (typo: '-' instead
    of '='); the key is unreachable either way because the lookup value is
    the re-rendered parsed Content-Type header, and pekko-http renders the
    fixed-charset form-urlencoded media type without a charset parameter.
    
    Modification:
    tryCreateCustomContentType now tries ContentType.parse first and only
    falls back to the legacy '/'-split custom binary media type when proper
    parsing fails. Remove the unreachable typo'd map entry with a comment
    explaining why no charset-suffixed key is needed.
    
    Result:
    Content-type values with parameters keep their parameters in the
    request entity's ContentType; unparseable values retain the previous
    fallback behavior.
    
    Tests:
    - sbt "aws-spi-pekko-http/Test/testOnly 
org.apache.pekko.stream.connectors.awsspi.PekkoHttpClientSpec" - 14 passed (3 
new tests); the parameters-preservation test fails without the fix (verified)
    - sbt "aws-spi-pekko-http/mimaReportBinaryIssues" - no issues
    - scalafmt --mode diff-ref=origin/main - clean
    
    References:
    None - found during a review of the aws-spi-pekko-http client
---
 .../stream/connectors/awsspi/PekkoHttpClient.scala | 17 ++++++++++-----
 .../connectors/awsspi/PekkoHttpClientSpec.scala    | 25 +++++++++++++++++++++-
 2 files changed, 36 insertions(+), 6 deletions(-)

diff --git 
a/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
 
b/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
index 528d724a7..1c6b1aa41 100644
--- 
a/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
+++ 
b/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
@@ -167,10 +167,16 @@ object PekkoHttpClient {
 
   private[awsspi] def tryCreateCustomContentType(contentTypeStr: String): 
ContentType = {
     logger.debug("Try to parse content type from {}", contentTypeStr)
-    val mainAndsubType = contentTypeStr.split('/')
-    if (mainAndsubType.length == 2)
-      ContentType(MediaType.customBinary(mainAndsubType(0), mainAndsubType(1), 
Compressible))
-    else throw new RuntimeException(s"Could not parse custom content type 
'$contentTypeStr'.")
+    // Prefer a proper parse so that values with parameters (e.g. "text/plain; 
charset=UTF-8")
+    // keep their parameters instead of ending up embedded in the subtype.
+    ContentType.parse(contentTypeStr) match {
+      case Right(contentType) => contentType
+      case Left(_)            =>
+        val mainAndsubType = contentTypeStr.split('/')
+        if (mainAndsubType.length == 2)
+          ContentType(MediaType.customBinary(mainAndsubType(0), 
mainAndsubType(1), Compressible))
+        else throw new RuntimeException(s"Could not parse custom content type 
'$contentTypeStr'.")
+    }
   }
 
   private[awsspi] def buildConnectionPoolSettings(
@@ -247,7 +253,8 @@ object PekkoHttpClient {
     "application/x-amz-json-1.0" -> xAmzJson,
     "application/x-amz-json-1.1" -> xAmzJson11,
     "application/x-amz-cbor-1.1" -> xAmzCbor11, // used by Kinesis
-    "application/x-www-form-urlencoded; charset-UTF-8" -> formUrlEncoded,
+    // No charset-suffixed key is needed: `application/x-www-form-urlencoded` 
is a fixed-charset
+    // media type, so a parsed Content-Type header always renders without a 
charset parameter.
     "application/x-www-form-urlencoded" -> formUrlEncoded,
     "application/xml" -> applicationXml)
 
diff --git 
a/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
 
b/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
index d07b892c3..efab9223c 100644
--- 
a/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
+++ 
b/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
@@ -26,7 +26,7 @@ import com.typesafe.config.ConfigFactory
 
 import org.apache.pekko
 import pekko.http.scaladsl.model.headers.`Content-Type`
-import pekko.http.scaladsl.model.{ ContentTypes, HttpMethods, MediaTypes }
+import pekko.http.scaladsl.model.{ ContentTypes, HttpHeader, HttpMethods, 
MediaTypes }
 import pekko.http.scaladsl.settings.{ ClientConnectionSettings, 
ConnectionPoolSettings }
 import org.reactivestreams.Subscriber
 import org.scalatest.OptionValues
@@ -70,6 +70,29 @@ class PekkoHttpClientSpec extends AnyWordSpec with Matchers 
with OptionValues {
       messages should contain(s"Try to parse content type from 
$contentTypeStr")
     }
 
+    "parse custom content type with parameters preserving the parameters" in {
+      val contentType = 
PekkoHttpClient.tryCreateCustomContentType("text/plain; charset=UTF-8")
+      contentType.mediaType.mainType shouldBe "text"
+      contentType.mediaType.subType shouldBe "plain"
+      contentType.charsetOption.map(_.value) shouldBe Some("UTF-8")
+    }
+
+    "fall back to a custom binary content type when proper parsing fails" in {
+      val contentType = PekkoHttpClient.tryCreateCustomContentType("foo/bar 
baz")
+      contentType.mediaType.mainType shouldBe "foo"
+      contentType.mediaType.subType shouldBe "bar baz"
+    }
+
+    "map form-urlencoded content type with charset to the predefined content 
type" in {
+      val header = HttpHeader.parse("Content-Type", 
"application/x-www-form-urlencoded; charset=UTF-8") match {
+        case HttpHeader.ParsingResult.Ok(h, _) => h
+        case error                             => fail(s"could not parse 
header: $error")
+      }
+      val contentType = 
PekkoHttpClient.contentTypeHeaderToContentType(Some(header))
+      contentType.mediaType.subType shouldBe "x-www-form-urlencoded"
+      contentType shouldBe PekkoHttpClient.formUrlEncoded
+    }
+
     "remove 'ContentType' return 'ContentLength' separate from sdk headers" in 
{
       val headers = new java.util.HashMap[String, java.util.List[String]]
       headers.put("Content-Type", Collections.singletonList("application/xml"))


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to