From b6b4c0565502e78bbe88e23cf0efc2b4857293ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KARASZI=20Istv=C3=A1n?= Date: Wed, 13 May 2026 14:19:16 +0200 Subject: [PATCH 1/5] Add custom data-converter implementations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: KARASZI István --- test/temporal/converter/metadata_test.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/temporal/converter/metadata_test.clj b/test/temporal/converter/metadata_test.clj index 64a7b04..761048a 100644 --- a/test/temporal/converter/metadata_test.clj +++ b/test/temporal/converter/metadata_test.clj @@ -13,6 +13,7 @@ (deftest metadata? (are [input] (is (sut/metadata? input)) {} + sut/empty {"custom" (->byte-string "data")}) (are [input] (is (not (sut/metadata? input))) From a707abfee309e797708e98c0b113d2fb3908522b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KARASZI=20Istv=C3=A1n?= Date: Tue, 27 Jan 2026 18:06:18 +0100 Subject: [PATCH 2/5] Make encoder selectable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: KARASZI István --- src/temporal/converter/default.clj | 48 ++++++++++++++++++-- src/temporal/converter/encoding.clj | 14 ++++++ test/temporal/converter/default_test.clj | 57 ++++++++++++++++++++++-- 3 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 src/temporal/converter/encoding.clj diff --git a/src/temporal/converter/default.clj b/src/temporal/converter/default.clj index a3b88aa..7da2015 100644 --- a/src/temporal/converter/default.clj +++ b/src/temporal/converter/default.clj @@ -1,19 +1,61 @@ (ns temporal.converter.default (:require + [temporal.converter.encoding :as encoding] [temporal.converter.json :as json] [temporal.converter.nippy :as nippy]) (:import - [io.temporal.common.converter DefaultDataConverter NullPayloadConverter PayloadConverter])) + [io.temporal.common.converter + DataConverterException + DefaultDataConverter + NullPayloadConverter + PayloadAndFailureDataConverter + PayloadConverter] + [java.lang.reflect Field] + [java.util Map])) + +(set! *warn-on-reflection* true) (def standard-converters [(NullPayloadConverter.) (nippy/create) (json/create)]) +(defn get-field ^Field [field] + (doto (.getDeclaredField PayloadAndFailureDataConverter field) + (.setAccessible true))) + +(def ^Field converters-map-field (get-field "convertersMap")) + +(def ^Field serialization-context-field (get-field "serializationContext")) + +(defn encoded->payload + "This is a custom re-implementation of the PayloadAndFailureDataConverter/toPayload" + [^PayloadAndFailureDataConverter data-converter {:keys [value encoding]}] + (let [converters-map (.get converters-map-field data-converter) + converter (.get ^Map converters-map encoding)] + (when-not converter + (throw (DataConverterException. + (str "No PayloadConverter is registered for this encoding: " encoding)))) + + (let [serialization-context (get serialization-context-field data-converter) + converter (if serialization-context + (.withContext ^PayloadConverter converter serialization-context) + converter) + result (.toData ^PayloadConverter converter value)] + (when-not (.isPresent result) + (throw (DataConverterException. + (str "Cannot encode value with the selected converter:" value)))) + + result))) + (defn create (^DefaultDataConverter [] (create standard-converters)) (^DefaultDataConverter [converters] - (DefaultDataConverter. - (into-array PayloadConverter converters)))) + (proxy [DefaultDataConverter] [(into-array PayloadConverter converters)] + (toPayload [value] + (let [^PayloadAndFailureDataConverter this this] + (if (encoding/tagged? value) + (encoded->payload this value) + (proxy-super toPayload value))))))) diff --git a/src/temporal/converter/encoding.clj b/src/temporal/converter/encoding.clj new file mode 100644 index 0000000..fe584a0 --- /dev/null +++ b/src/temporal/converter/encoding.clj @@ -0,0 +1,14 @@ +(ns temporal.converter.encoding) + +(defrecord Tagged [value encoding]) + +(def mapping + {:null "binary/null" + :nippy "binary/plain" + :json "json/plain"}) + +(defn tagged? [x] + (instance? Tagged x)) + +(defn with [value encoding] + (->Tagged value (get mapping encoding encoding))) diff --git a/test/temporal/converter/default_test.clj b/test/temporal/converter/default_test.clj index cd752ba..59f81ec 100644 --- a/test/temporal/converter/default_test.clj +++ b/test/temporal/converter/default_test.clj @@ -1,8 +1,10 @@ (ns temporal.converter.default-test (:require [clojure.test :refer [are deftest is]] + [jsonista.core :as json] [taoensso.nippy :as nippy] [temporal.converter.default :as sut] + [temporal.converter.encoding :as encoding] [temporal.converter.metadata :refer [->metadata]] [temporal.converter.payload :refer [->payload]]) (:import @@ -23,7 +25,14 @@ (and (= "binary/plain" (payload->encoding payload)) (= expected (-> payload (.. (getData) (toByteArray)) - nippy/thaw))))) + (nippy/thaw)))))) + +(defn json-payload? [expected] + (fn [^Payload payload] + (and (= "json/plain" (payload->encoding payload)) + (= expected (-> payload + (.. (getData) (toByteArray)) + (json/read-value json/keyword-keys-object-mapper)))))) (deftest to-payload-with-default-converters (let [data-converter (sut/create)] @@ -32,14 +41,39 @@ (.get) (predicate)) null-payload? nil + null-payload? (encoding/with nil :null) + null-payload? (encoding/with nil "binary/null") + (nippy-payload? :something) :something (nippy-payload? "Something") "Something" (nippy-payload? [1 2 3 4]) [1 2 3 4] - (nippy-payload? {:foo "bar"}) {:foo "bar"}))) + (nippy-payload? {:foo "bar"}) {:foo "bar"} + + (nippy-payload? :something) (encoding/with :something :nippy) + (nippy-payload? "Something") (encoding/with "Something" :nippy) + (nippy-payload? [1 2 3 4]) (encoding/with [1 2 3 4] :nippy) + (nippy-payload? {:foo "bar"}) (encoding/with {:foo "bar"} :nippy) + + (nippy-payload? :something) (encoding/with :something "binary/plain") + (nippy-payload? "Something") (encoding/with "Something" "binary/plain") + (nippy-payload? [1 2 3 4]) (encoding/with [1 2 3 4] "binary/plain") + (nippy-payload? {:foo "bar"}) (encoding/with {:foo "bar"} "binary/plain") + + (json-payload? nil) (encoding/with nil :json) + (json-payload? "Something") (encoding/with "Something" :json) + (json-payload? [1 2 3 4]) (encoding/with [1 2 3 4] :json) + (json-payload? {:foo "bar"}) (encoding/with {:foo "bar"} :json) + + (json-payload? nil) (encoding/with nil "json/plain") + (json-payload? "Something") (encoding/with "Something" "json/plain") + (json-payload? [1 2 3 4]) (encoding/with [1 2 3 4] "json/plain") + (json-payload? {:foo "bar"}) (encoding/with {:foo "bar"} "json/plain")))) (deftest to-payload-with-custom-converters (let [data-converter (sut/create [(NullPayloadConverter.)])] - (is (null-payload? (.. data-converter (toPayload nil) (get)))) + (are [input-value] (null-payload? (.. data-converter (toPayload input-value) (get))) + nil + (encoding/with nil :null)) (are [input-value] (thrown-with-msg? DataConverterException #"No PayloadConverter is registered with this DataConverter" @@ -47,7 +81,22 @@ :something "Something" [1 2 3 4] - {:foo "bar"}))) + {:foo "bar"}) + + (are [input-value] (thrown-with-msg? DataConverterException + #"Cannot encode value with the selected converter" + (.toPayload data-converter input-value)) + (encoding/with :something :null) + (encoding/with :something "binary/null")) + + (are [input-value] (thrown-with-msg? DataConverterException + #"No PayloadConverter is registered for this encoding" + (.toPayload data-converter input-value)) + (encoding/with nil :nippy) + (encoding/with nil "binary/plain") + + (encoding/with nil :json) + (encoding/with nil "json/plain")))) (def metadata-null (->metadata "binary/null")) From 037e8c45240330e42fbb6fd4c59b844a53592e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KARASZI=20Istv=C3=A1n?= Date: Tue, 27 Jan 2026 18:37:52 +0100 Subject: [PATCH 3/5] Extract get-converter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: KARASZI István --- src/temporal/converter/default.clj | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/temporal/converter/default.clj b/src/temporal/converter/default.clj index 7da2015..70e8662 100644 --- a/src/temporal/converter/default.clj +++ b/src/temporal/converter/default.clj @@ -28,25 +28,25 @@ (def ^Field serialization-context-field (get-field "serializationContext")) +(defn get-converter [^PayloadAndFailureDataConverter data-converter encoding] + (let [converters-map (.get converters-map-field data-converter) + converter (.get ^Map converters-map encoding)] + (when converter + (if-let [serialization-context (get serialization-context-field data-converter)] + (.withContext ^PayloadConverter converter serialization-context) + converter)))) + (defn encoded->payload "This is a custom re-implementation of the PayloadAndFailureDataConverter/toPayload" [^PayloadAndFailureDataConverter data-converter {:keys [value encoding]}] - (let [converters-map (.get converters-map-field data-converter) - converter (.get ^Map converters-map encoding)] - (when-not converter - (throw (DataConverterException. - (str "No PayloadConverter is registered for this encoding: " encoding)))) - - (let [serialization-context (get serialization-context-field data-converter) - converter (if serialization-context - (.withContext ^PayloadConverter converter serialization-context) - converter) - result (.toData ^PayloadConverter converter value)] - (when-not (.isPresent result) + (if-let [converter (get-converter data-converter encoding)] + (let [result (.toData ^PayloadConverter converter value)] + (if (.isPresent result) + result (throw (DataConverterException. - (str "Cannot encode value with the selected converter:" value)))) - - result))) + (str "Cannot encode value with the selected converter: " value))))) + (throw (DataConverterException. + (str "No PayloadConverter is registered for this encoding: " encoding))))) (defn create (^DefaultDataConverter [] From e3e150535d650d465a2f938742f02a29617d50ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KARASZI=20Istv=C3=A1n?= Date: Fri, 6 Feb 2026 13:43:10 +0100 Subject: [PATCH 4/5] Override withContext as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: KARASZI István --- src/temporal/converter/default.clj | 16 +++++ test/temporal/converter/default_test.clj | 89 ++++++++++++++---------- 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/temporal/converter/default.clj b/src/temporal/converter/default.clj index 70e8662..c48a74f 100644 --- a/src/temporal/converter/default.clj +++ b/src/temporal/converter/default.clj @@ -24,8 +24,12 @@ (doto (.getDeclaredField PayloadAndFailureDataConverter field) (.setAccessible true))) +(def ^Field converters-field (get-field "converters")) + (def ^Field converters-map-field (get-field "convertersMap")) +(def ^Field failure-converter-field (get-field "failureConverter")) + (def ^Field serialization-context-field (get-field "serializationContext")) (defn get-converter [^PayloadAndFailureDataConverter data-converter encoding] @@ -52,8 +56,20 @@ (^DefaultDataConverter [] (create standard-converters)) + (^DefaultDataConverter [converters source-data-converter] + (let [data-converter (create converters)] + (doseq [^Field field [converters-field converters-map-field failure-converter-field]] + (->> (.get field source-data-converter) + (.set field data-converter))) + data-converter)) + (^DefaultDataConverter [converters] (proxy [DefaultDataConverter] [(into-array PayloadConverter converters)] + (withContext [context] + (let [data-converter (create converters this)] + (.set serialization-context-field data-converter context) + data-converter)) + (toPayload [value] (let [^PayloadAndFailureDataConverter this this] (if (encoding/tagged? value) diff --git a/test/temporal/converter/default_test.clj b/test/temporal/converter/default_test.clj index 59f81ec..48d81c5 100644 --- a/test/temporal/converter/default_test.clj +++ b/test/temporal/converter/default_test.clj @@ -1,6 +1,7 @@ (ns temporal.converter.default-test (:require - [clojure.test :refer [are deftest is]] + [clojure.template :refer [do-template]] + [clojure.test :refer [are deftest is testing]] [jsonista.core :as json] [taoensso.nippy :as nippy] [temporal.converter.default :as sut] @@ -9,7 +10,13 @@ [temporal.converter.payload :refer [->payload]]) (:import [io.temporal.api.common.v1 Payload] - [io.temporal.common.converter DataConverterException EncodingKeys NullPayloadConverter])) + [io.temporal.common.converter + DataConverterException + EncodingKeys + NullPayloadConverter + PayloadAndFailureDataConverter] + [io.temporal.payload.context WorkflowSerializationContext] + [java.lang.reflect Field])) (defn payload->encoding [^Payload payload] (.. payload @@ -34,40 +41,52 @@ (.. (getData) (toByteArray)) (json/read-value json/keyword-keys-object-mapper)))))) +(def context + (WorkflowSerializationContext. "test" "test")) + (deftest to-payload-with-default-converters - (let [data-converter (sut/create)] - (are [predicate input-value] (-> data-converter - (.toPayload input-value) - (.get) - (predicate)) - null-payload? nil - null-payload? (encoding/with nil :null) - null-payload? (encoding/with nil "binary/null") - - (nippy-payload? :something) :something - (nippy-payload? "Something") "Something" - (nippy-payload? [1 2 3 4]) [1 2 3 4] - (nippy-payload? {:foo "bar"}) {:foo "bar"} - - (nippy-payload? :something) (encoding/with :something :nippy) - (nippy-payload? "Something") (encoding/with "Something" :nippy) - (nippy-payload? [1 2 3 4]) (encoding/with [1 2 3 4] :nippy) - (nippy-payload? {:foo "bar"}) (encoding/with {:foo "bar"} :nippy) - - (nippy-payload? :something) (encoding/with :something "binary/plain") - (nippy-payload? "Something") (encoding/with "Something" "binary/plain") - (nippy-payload? [1 2 3 4]) (encoding/with [1 2 3 4] "binary/plain") - (nippy-payload? {:foo "bar"}) (encoding/with {:foo "bar"} "binary/plain") - - (json-payload? nil) (encoding/with nil :json) - (json-payload? "Something") (encoding/with "Something" :json) - (json-payload? [1 2 3 4]) (encoding/with [1 2 3 4] :json) - (json-payload? {:foo "bar"}) (encoding/with {:foo "bar"} :json) - - (json-payload? nil) (encoding/with nil "json/plain") - (json-payload? "Something") (encoding/with "Something" "json/plain") - (json-payload? [1 2 3 4]) (encoding/with [1 2 3 4] "json/plain") - (json-payload? {:foo "bar"}) (encoding/with {:foo "bar"} "json/plain")))) + (do-template + [predicate input-value] + (let [data-converter (sut/create)] + (testing "withhout-context" + (is (predicate (-> data-converter + (.toPayload input-value) + (.get))))) + + (testing "with-context" + (let [data-converter (.withContext data-converter context)] + (is (= context (.get sut/serialization-context-field data-converter))) + (is (predicate (-> data-converter + (.toPayload input-value) + (.get))))))) + null-payload? nil + null-payload? (encoding/with nil :null) + null-payload? (encoding/with nil "binary/null") + + (nippy-payload? :something) :something + (nippy-payload? "Something") "Something" + (nippy-payload? [1 2 3 4]) [1 2 3 4] + (nippy-payload? {:foo "bar"}) {:foo "bar"} + + (nippy-payload? :something) (encoding/with :something :nippy) + (nippy-payload? "Something") (encoding/with "Something" :nippy) + (nippy-payload? [1 2 3 4]) (encoding/with [1 2 3 4] :nippy) + (nippy-payload? {:foo "bar"}) (encoding/with {:foo "bar"} :nippy) + + (nippy-payload? :something) (encoding/with :something "binary/plain") + (nippy-payload? "Something") (encoding/with "Something" "binary/plain") + (nippy-payload? [1 2 3 4]) (encoding/with [1 2 3 4] "binary/plain") + (nippy-payload? {:foo "bar"}) (encoding/with {:foo "bar"} "binary/plain") + + (json-payload? nil) (encoding/with nil :json) + (json-payload? "Something") (encoding/with "Something" :json) + (json-payload? [1 2 3 4]) (encoding/with [1 2 3 4] :json) + (json-payload? {:foo "bar"}) (encoding/with {:foo "bar"} :json) + + (json-payload? nil) (encoding/with nil "json/plain") + (json-payload? "Something") (encoding/with "Something" "json/plain") + (json-payload? [1 2 3 4]) (encoding/with [1 2 3 4] "json/plain") + (json-payload? {:foo "bar"}) (encoding/with {:foo "bar"} "json/plain"))) (deftest to-payload-with-custom-converters (let [data-converter (sut/create [(NullPayloadConverter.)])] From e3d50d47f25cc7d6c526cde1a979d6ffae1324d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KARASZI=20Istv=C3=A1n?= Date: Wed, 13 May 2026 14:25:30 +0200 Subject: [PATCH 5/5] Remove unused require MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: KARASZI István --- test/temporal/converter/optional_test.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/temporal/converter/optional_test.clj b/test/temporal/converter/optional_test.clj index 463b10c..f6991db 100644 --- a/test/temporal/converter/optional_test.clj +++ b/test/temporal/converter/optional_test.clj @@ -1,7 +1,7 @@ (ns temporal.converter.optional-test (:require [clojure.template :refer [do-template]] - [clojure.test :refer [are deftest is testing]] + [clojure.test :refer [deftest is testing]] [temporal.converter.optional :as sut]) (:import [java.util Optional]))