diff --git a/core/src/main/scala/sttp/client3/internal/ToCurlConverter.scala b/core/src/main/scala/sttp/client3/internal/ToCurlConverter.scala index dafc3c90d1..e4f2f0160d 100644 --- a/core/src/main/scala/sttp/client3/internal/ToCurlConverter.scala +++ b/core/src/main/scala/sttp/client3/internal/ToCurlConverter.scala @@ -7,16 +7,20 @@ import sttp.model.MediaType class ToCurlConverter[R <: RequestT[Identity, _, _]] { def apply(request: R): String = { - val params = List(extractOptions(_), extractMethod(_), extractHeaders(_), extractBody(_)) + val params = List(extractMethod(_), extractUrl(_), extractHeaders(_), extractBody(_), extractOptions(_)) .map(addSpaceIfNotEmpty) .reduce((acc, item) => r => acc(r) + item(r)) .apply(request) - s"""curl$params '${request.uri}'""" + s"""curl$params""" } private def extractMethod(r: R): String = { - s"-X ${r.method.method}" + s"--request ${r.method.method}" + } + + private def extractUrl(r: R): String = { + s"--url '${r.uri}'" } private def extractHeaders(r: R): String = { @@ -24,9 +28,9 @@ class ToCurlConverter[R <: RequestT[Identity, _, _]] { // filtering out compression headers so that the results are human-readable, if possible .filterNot(_.name.equalsIgnoreCase(HeaderNames.AcceptEncoding)) .collect { case Header(k, v) => - s"""-H '$k: $v'""" + s"""--header '$k: $v'""" } - .mkString(" ") + .mkString(newline) } private def extractBody(r: R): String = { @@ -37,7 +41,7 @@ class ToCurlConverter[R <: RequestT[Identity, _, _]] { .toMap .get(HeaderNames.ContentType) .forall(_ == MediaType.ApplicationXWwwFormUrlencoded.toString) => - s"""-F '${text.replace("'", "\\'")}'""" + s"""--form '${text.replace("'", "\\'")}'""" case StringBody(text, _, _) => s"""--data '${text.replace("'", "\\'")}'""" case ByteArrayBody(_, _) => s"--data-binary " case ByteBufferBody(_, _) => s"--data-binary " @@ -58,19 +62,21 @@ class ToCurlConverter[R <: RequestT[Identity, _, _]] { case _ => s"--data-binary " } } - .mkString(" ") + .mkString(newline) } private def extractOptions(r: R): String = { if (r.options.followRedirects) { - s"-L --max-redirs ${r.options.maxRedirects}" + s"--location${newline}--max-redirs ${r.options.maxRedirects}" } else { "" } } private def addSpaceIfNotEmpty(fInput: R => String): R => String = - t => if (fInput(t).isEmpty) "" else s" ${fInput(t)}" + t => if (fInput(t).isEmpty) "" else s"${newline}${fInput(t)}" + + private def newline: String = " \\\n " } object ToCurlConverter { diff --git a/core/src/test/scala/sttp/client3/ToCurlConverterTest.scala b/core/src/test/scala/sttp/client3/ToCurlConverterTest.scala index b3ad2e8c41..2b00a11be0 100644 --- a/core/src/test/scala/sttp/client3/ToCurlConverterTest.scala +++ b/core/src/test/scala/sttp/client3/ToCurlConverterTest.scala @@ -12,45 +12,45 @@ class ToCurlConverterTest extends AnyFlatSpec with Matchers with ToCurlConverter it should "convert base request" in { basicRequest .get(uri"$localhost") - .toCurl shouldBe """curl -L --max-redirs 32 -X GET 'http://localhost'""" + .toCurl shouldBe "curl \\\n --request GET \\\n --url 'http://localhost' \\\n --location \\\n --max-redirs 32" } it should "convert request with method to curl" in { - basicRequest.get(localhost).toCurl should include("-X GET") - basicRequest.post(localhost).toCurl should include("-X POST") - basicRequest.put(localhost).toCurl should include("-X PUT") - basicRequest.delete(localhost).toCurl should include("-X DELETE") - basicRequest.patch(localhost).toCurl should include("-X PATCH") - basicRequest.head(localhost).toCurl should include("-X HEAD") - basicRequest.options(localhost).toCurl should include("-X OPTIONS") + basicRequest.get(localhost).toCurl should include("--request GET") + basicRequest.post(localhost).toCurl should include("--request POST") + basicRequest.put(localhost).toCurl should include("--request PUT") + basicRequest.delete(localhost).toCurl should include("--request DELETE") + basicRequest.patch(localhost).toCurl should include("--request PATCH") + basicRequest.head(localhost).toCurl should include("--request HEAD") + basicRequest.options(localhost).toCurl should include("--request OPTIONS") } it should "convert request with header" in { basicRequest.header("User-Agent", "myapp").get(localhost).toCurl should include( - """-H 'User-Agent: myapp'""" + """--header 'User-Agent: myapp'""" ) } it should "convert request with body" in { basicRequest.body(Map("name" -> "john", "org" -> "sml")).post(localhost).toCurl should include( - """-H 'Content-Type: application/x-www-form-urlencoded' -H 'Content-Length: 17' -F 'name=john&org=sml'""" + "--header 'Content-Type: application/x-www-form-urlencoded' \\\n --header 'Content-Length: 17' \\\n --form 'name=john&org=sml'" ) basicRequest.body("name=john").post(localhost).toCurl should include( - """-H 'Content-Type: text/plain; charset=utf-8' -H 'Content-Length: 9' --data 'name=john'""" + "--header 'Content-Type: text/plain; charset=utf-8' \\\n --header 'Content-Length: 9' \\\n --data 'name=john'" ) basicRequest.body("name=john", StandardCharsets.ISO_8859_1.name()).post(localhost).toCurl should include( - """-H 'Content-Type: text/plain; charset=ISO-8859-1' -H 'Content-Length: 9' --data 'name=john'""" + "--header 'Content-Type: text/plain; charset=ISO-8859-1' \\\n --header 'Content-Length: 9' \\\n --data 'name=john'" ) basicRequest.body("name='john'").post(localhost).toCurl should include( - """-H 'Content-Type: text/plain; charset=utf-8' -H 'Content-Length: 11' --data 'name=\'john\''""" + "--header 'Content-Type: text/plain; charset=utf-8' \\\n --header 'Content-Length: 11' \\\n --data 'name=\\'john\\''" ) basicRequest.body("name=\"john\"").post(localhost).toCurl should include( - """-H 'Content-Type: text/plain; charset=utf-8' -H 'Content-Length: 11' --data 'name="john"'""" + "--header 'Content-Type: text/plain; charset=utf-8' \\\n --header 'Content-Length: 11' \\\n --data 'name=\"john\"'" ) } it should "convert request with options" in { - basicRequest.followRedirects(false).get(localhost).toCurl should not include "-L" + basicRequest.followRedirects(false).get(localhost).toCurl should not include "--location" basicRequest.maxRedirects(11).get(localhost).toCurl should include("--max-redirs 11") } @@ -66,7 +66,7 @@ class ToCurlConverterTest extends AnyFlatSpec with Matchers with ToCurlConverter it should "render multipart form data if content is a plain string" in { basicRequest.multipartBody(multipart("k1", "v1"), multipart("k2", "v2")).post(localhost).toCurl should include( - """--form 'k1=v1' --form 'k2=v2'""" + "--form 'k1=v1' \\\n --form 'k2=v2'" ) } }