From 6f173d9a0f2bd515b5b15384cc03bc1a7e574949 Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Tue, 4 Jan 2022 17:34:15 +0200 Subject: [PATCH] PayloadSerialization can emit additional headers (#120) This can be used to sign the payload cryptographically and pass it as a header onto the request. --- .gitignore | 1 + .../test/scala/zio/webhooks/WebhookServerSpec.scala | 4 ++-- .../src/main/scala/zio/webhooks/WebhookServer.scala | 4 ++-- .../webhooks/backends/JsonPayloadSerialization.scala | 10 +++++----- .../scala/zio/webhooks/internal/RetryDispatcher.scala | 4 ++-- .../scala/zio/webhooks/internal/WebhookDispatch.scala | 2 +- webhooks/src/main/scala/zio/webhooks/package.scala | 7 +++++-- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 7f94bdc3..105b094c 100755 --- a/.gitignore +++ b/.gitignore @@ -467,3 +467,4 @@ website/node_modules website/build website/i18n/en.json website/static/api +.idea diff --git a/webhooks-test/src/test/scala/zio/webhooks/WebhookServerSpec.scala b/webhooks-test/src/test/scala/zio/webhooks/WebhookServerSpec.scala index cc24396b..d33c722a 100644 --- a/webhooks-test/src/test/scala/zio/webhooks/WebhookServerSpec.scala +++ b/webhooks-test/src/test/scala/zio/webhooks/WebhookServerSpec.scala @@ -882,7 +882,7 @@ object WebhookServerSpecUtil { ) } - val jsonContentHeaders: Chunk[(String, String)] = Chunk(("Accept", "*/*"), ("Content-Type", "application/json")) + val jsonContentHeaders: Chunk[HttpHeader] = Chunk(("Accept", "*/*"), ("Content-Type", "application/json")) val jsonPayloadPattern: String = """(?:\[\{\"event\":\"payload\d+\"}(?:,\{\"event\":\"payload\d+\"})*\])""" @@ -909,7 +909,7 @@ object WebhookServerSpecUtil { WebhooksProxy.live ) - val plaintextContentHeaders: Chunk[(String, String)] = Chunk(("Accept", "*/*"), ("Content-Type", "text/plain")) + val plaintextContentHeaders: Chunk[HttpHeader] = Chunk(("Accept", "*/*"), ("Content-Type", "text/plain")) sealed trait ScenarioInterest[A] object ScenarioInterest { diff --git a/webhooks/src/main/scala/zio/webhooks/WebhookServer.scala b/webhooks/src/main/scala/zio/webhooks/WebhookServer.scala index b352b14b..db710c6a 100644 --- a/webhooks/src/main/scala/zio/webhooks/WebhookServer.scala +++ b/webhooks/src/main/scala/zio/webhooks/WebhookServer.scala @@ -47,8 +47,8 @@ final class WebhookServer private ( * marked failed. */ private def deliver(dispatch: WebhookDispatch): UIO[Unit] = { - val request = - WebhookHttpRequest(dispatch.url, serializePayload(dispatch.payload, dispatch.contentType), dispatch.headers) + val (payload, headers) = serializePayload(dispatch.payload, dispatch.contentType) + val request = WebhookHttpRequest(dispatch.url, payload, dispatch.headers ++ headers) for { response <- httpClient.post(request).either _ <- (dispatch.deliverySemantics, response) match { diff --git a/webhooks/src/main/scala/zio/webhooks/backends/JsonPayloadSerialization.scala b/webhooks/src/main/scala/zio/webhooks/backends/JsonPayloadSerialization.scala index a7977960..ea6b187e 100644 --- a/webhooks/src/main/scala/zio/webhooks/backends/JsonPayloadSerialization.scala +++ b/webhooks/src/main/scala/zio/webhooks/backends/JsonPayloadSerialization.scala @@ -1,6 +1,6 @@ package zio.webhooks.backends -import zio.{ Has, ULayer, ZLayer } +import zio.{ Chunk, Has, ULayer, ZLayer } import zio.webhooks._ object JsonPayloadSerialization { @@ -11,16 +11,16 @@ object JsonPayloadSerialization { case Some(WebhookContentMimeType(contentType)) if contentType.toLowerCase == "application/json" => webhookPayload match { case WebhookPayload.Single(event) => - event.content + (event.content, Chunk.empty) case WebhookPayload.Batched(events) => - "[" + events.map(_.content).mkString(",") + "]" + ("[" + events.map(_.content).mkString(",") + "]", Chunk.empty) } case _ => webhookPayload match { case WebhookPayload.Single(event) => - event.content + (event.content, Chunk.empty) case WebhookPayload.Batched(events) => - events.map(_.content).mkString + (events.map(_.content).mkString, Chunk.empty) } } } diff --git a/webhooks/src/main/scala/zio/webhooks/internal/RetryDispatcher.scala b/webhooks/src/main/scala/zio/webhooks/internal/RetryDispatcher.scala index 51bee7de..bc50bedc 100644 --- a/webhooks/src/main/scala/zio/webhooks/internal/RetryDispatcher.scala +++ b/webhooks/src/main/scala/zio/webhooks/internal/RetryDispatcher.scala @@ -72,8 +72,8 @@ private[webhooks] final case class RetryDispatcher( * for retries when the endpoint begins to return `200` status codes. */ private def retryEvents(dispatch: WebhookDispatch, batchQueue: Option[Queue[WebhookEvent]]): UIO[Unit] = { - val request = - WebhookHttpRequest(dispatch.url, serializePayload(dispatch.payload, dispatch.contentType), dispatch.headers) + val (payload, headers) = serializePayload(dispatch.payload, dispatch.contentType) + val request = WebhookHttpRequest(dispatch.url, payload, dispatch.headers ++ headers) for { response <- httpClient.post(request).either _ <- response match { diff --git a/webhooks/src/main/scala/zio/webhooks/internal/WebhookDispatch.scala b/webhooks/src/main/scala/zio/webhooks/internal/WebhookDispatch.scala index f4b89f4b..03839263 100644 --- a/webhooks/src/main/scala/zio/webhooks/internal/WebhookDispatch.scala +++ b/webhooks/src/main/scala/zio/webhooks/internal/WebhookDispatch.scala @@ -31,5 +31,5 @@ private[webhooks] final case class WebhookDispatch( events } - lazy val headers: Chunk[(String, String)] = payload.headers + lazy val headers: Chunk[HttpHeader] = payload.headers } diff --git a/webhooks/src/main/scala/zio/webhooks/package.scala b/webhooks/src/main/scala/zio/webhooks/package.scala index e75d9794..b17751d2 100644 --- a/webhooks/src/main/scala/zio/webhooks/package.scala +++ b/webhooks/src/main/scala/zio/webhooks/package.scala @@ -32,9 +32,12 @@ package object webhooks { .mergeTerminateRight(UStream.fromEffect(shutdownSignal.await.map(Right(_)))) .collectLeft + type HttpHeader = (String, String) + /** * [[SerializePayload]] is a function that takes a [[WebhookPayload]] and an optional - * [WebhookContentMimeType] and serializes it into a `String`. + * [WebhookContentMimeType] and serializes it into a pair of `String` and a chunk of [[HttpHeader]] to append + * to the outgoing request. */ - type SerializePayload = (WebhookPayload, Option[WebhookContentMimeType]) => String + type SerializePayload = (WebhookPayload, Option[WebhookContentMimeType]) => (String, Chunk[HttpHeader]) }