diff --git a/src/gosura/helpers/error.clj b/src/gosura/helpers/error.clj index d806404..d4ae2e2 100644 --- a/src/gosura/helpers/error.clj +++ b/src/gosura/helpers/error.clj @@ -7,3 +7,11 @@ (error nil resolver-errors)) ([resolved-value resolver-errors] (resolve-as resolved-value resolver-errors))) + +(defn error-response + [throwable] + {:message (ex-message throwable) + :info (str throwable) + :type (.getName (class throwable)) + :stacktrace (->> (.getStackTrace throwable) + (map str))}) diff --git a/src/gosura/helpers/resolver2.clj b/src/gosura/helpers/resolver2.clj index 93e17bc..c667c37 100644 --- a/src/gosura/helpers/resolver2.clj +++ b/src/gosura/helpers/resolver2.clj @@ -1,6 +1,6 @@ (ns gosura.helpers.resolver2 "gosura.helpers.resolver의 v2입니다." - (:require [com.walmartlabs.lacinia.resolve :refer [resolve-as]] + (:require [com.walmartlabs.lacinia.resolve :refer [resolve-as] :as resolve] [failjure.core :as f] [gosura.auth :as auth] [gosura.helpers.error :as error] @@ -20,16 +20,29 @@ [catch-exceptions? body] (if catch-exceptions? `(try - ~@body + ~body (catch Exception e# (log/error e#) (resolve-as nil - {:message (ex-message e#) - :info (str e#) - :type (.getName (class e#)) - :stacktrace (->> (.getStackTrace e#) - (map str))}))) + (error/error-response e#)))) + body)) + +(defn transform-body + [body return-camel-case?] + (cond-> body + return-camel-case? (update-resolver-result transform-keys->camelCaseKeyword))) + +(defmacro wrap-async-body + [async? return-camel-case? body] + (if async? + `(let [result# (resolve/resolve-promise)] + (prom/future + (try + (resolve/deliver! result# (transform-body ~body ~return-camel-case?)) + (catch Throwable t# + (resolve/deliver! result# nil (error/error-response t#))))) + result#) body)) (defmacro wrap-resolver-body @@ -47,10 +60,11 @@ " [{:keys [this ctx arg parent]} option args body] (let [{:keys [auth kebab-case? return-camel-case? required-keys-in-parent - filters decode-ids-by-keys catch-exceptions?] + filters decode-ids-by-keys catch-exceptions? async?] :or {kebab-case? true return-camel-case? true catch-exceptions? true + async? false required-keys-in-parent []}} option result (gensym 'result_) auth-filter-opts `(auth/->auth-result ~auth ~ctx) @@ -67,9 +81,10 @@ (if (or (nil? ~auth-filter-opts) (and ~auth-filter-opts (not (f/failed? ~auth-filter-opts)))) - (let [~result (do (let ~let-mapping (wrap-catch-body ~catch-exceptions? ~body)))] - (cond-> ~result - ~return-camel-case? (update-resolver-result transform-keys->camelCaseKeyword))) + (let [~result (do (let ~let-mapping (->> ~@body + (wrap-async-body ~async? ~return-camel-case?) + (wrap-catch-body ~catch-exceptions?))))] + (transform-body ~result ~return-camel-case?)) (resolve-as nil {:message "Unauthorized"}))))) @@ -114,7 +129,7 @@ * :parent-id: 부모로부터 전달되는 id 정보 예) {:pre-fn relay/decode-global-id->db-id :prop :id :agg :id} {:prop :user-id :agg :id} * :pre-fn: 전처리 * :prop: 부모로부터 전달 받는 키값 - * :agg: 데이터를 모으는 키값 + * :agg: 데이터를 모으는 키값 ## 반환 * 객체 목록 " @@ -161,7 +176,7 @@ * :parent-id: 부모로부터 전달되는 id 정보 예) {:pre-fn relay/decode-global-id->db-id :prop :id :agg :id} {:prop :user-id :agg :id} * :pre-fn: 전처리 * :prop: 부모로부터 전달 받는 키값 - * :agg: 데이터를 모으는 키값 + * :agg: 데이터를 모으는 키값 ## 반환 * 객체 하나 " diff --git a/test/gosura/helpers/resolver_test.clj b/test/gosura/helpers/resolver_test.clj index 88543c1..e4383ee 100644 --- a/test/gosura/helpers/resolver_test.clj +++ b/test/gosura/helpers/resolver_test.clj @@ -1,6 +1,7 @@ (ns gosura.helpers.resolver-test (:require [clojure.test :refer [are deftest is run-tests testing]] [gosura.helpers.resolver :as gosura-resolver] + [com.walmartlabs.lacinia.resolve :as resolve] [gosura.helpers.resolver2 :as gosura-resolver2]) (:import [clojure.lang ExceptionInfo])) @@ -79,12 +80,10 @@ (get-in ctx [:identity :cc])) (def auth-column-name :userId) -(def arg-in-resolver (atom {})) (gosura-resolver2/defresolver test-resolver {:auth [user-auth auth-column-name]} [ctx arg parent] - (reset! arg-in-resolver arg) {:ctx ctx :arg arg :parent parent}) @@ -100,11 +99,19 @@ :arg :userId) "1")))) (testing "arg/parent가 default True로 kebab-case 설정이 잘 동작한다" - (let [ctx {:identity {:id "1"}} - arg {:intArg 1 - :strArg "str"} - parent {} - _ (test-resolver ctx arg parent)] + (let [arg-in-resolver (atom {}) + _ (gosura-resolver2/defresolver test-resolver-1 + {:auth [user-auth auth-column-name]} + [ctx arg parent] + (reset! arg-in-resolver arg) + {:ctx ctx + :arg arg + :parent parent}) + ctx {:identity {:id "1"}} + arg {:intArg 1 + :strArg "str"} + parent {} + _ (test-resolver-1 ctx arg parent)] (is (= @arg-in-resolver {:int-arg 1 :str-arg "str" :user-id "1"})))) @@ -148,7 +155,6 @@ {:auth [user-auth auth-column-name] :filters {:country-code get-country-code}} [ctx arg parent] - (reset! arg-in-resolver arg) {:ctx ctx :arg arg :parent parent}) @@ -219,16 +225,16 @@ (is (= (-> result :arg) {:testCol nil})))) (testing "에러가 던져졌을 때 GraphQL errors를 반환한다" - (let [_ (gosura-resolver2/defresolver test-resolver-8 - [_ctx _arg _parent] - (throw (ex-info "something wrong!" {}))) - ctx {} - arg {} - parent {} - resolved (test-resolver-8 ctx arg parent) - message (get-in resolved [:resolved-value :data :message]) - info (get-in resolved [:resolved-value :data :info]) - type' (get-in resolved [:resolved-value :data :type]) + (let [_ (gosura-resolver2/defresolver test-resolver-8 + [_ctx _arg _parent] + (throw (ex-info "something wrong!" {}))) + ctx {} + arg {} + parent {} + resolved (test-resolver-8 ctx arg parent) + message (get-in resolved [:resolved-value :data :message]) + info (get-in resolved [:resolved-value :data :info]) + type' (get-in resolved [:resolved-value :data :type]) stacktrace (get-in resolved [:resolved-value :data :stacktrace])] (are [expected result] (= expected result) "something wrong!" message @@ -236,14 +242,43 @@ (some? type') true (some? stacktrace) true))) (testing "catch-exceptions? 설정이 false일 때 에러가 던져지면 그대로 throw한다" - (let [_ (gosura-resolver2/defresolver test-resolver-9 - {:catch-exceptions? false} - [_ctx _arg _parent] - (throw (ex-info "something wrong!" {}))) + (let [_ (gosura-resolver2/defresolver test-resolver-8 + {:catch-exceptions? false} + [_ctx _arg _parent] + (throw (ex-info "something wrong!" {}))) ctx {} arg {} parent {}] - (is (thrown? ExceptionInfo (test-resolver-9 ctx arg parent)))))) + (is (thrown? ExceptionInfo (test-resolver-8 ctx arg parent))))) + (testing "async? 설정이 false 일 때, 원래 동작을 잘 보장한다" + (let [_ (gosura-resolver2/defresolver test-resolver-9 + {:async? false} + [ctx arg parent] + {:ctx ctx + :arg arg + :parent parent}) + ctx {:identity {:id "1"}} + arg {:intArg 1 + :strArg "str"} + parent {} + result (test-resolver-9 ctx arg parent)] + (is (= result {:ctx {:identity {:id "1"}} + :arg {:intArg 1 + :strArg "str"} + :parent {}})))) + (testing "async? 설정이 true 일 때, Promise를 잘 반환한다" + (let [_ (gosura-resolver2/defresolver test-resolver-10 + {:async? true} + [ctx arg parent] + {:ctx ctx + :arg arg + :parent parent}) + ctx {:identity {:id "1"}} + arg {:intArg 1 + :strArg "str"} + parent {} + resolved (test-resolver-10 ctx arg parent)] + (is (satisfies? resolve/ResolverResultPromise resolved))))) (comment (run-tests))