Skip to content

Instantly share code, notes, and snippets.

@awkay
Created August 30, 2022 22:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save awkay/86a3d7b3be912f79961e7bffba91a6f2 to your computer and use it in GitHub Desktop.
Save awkay/86a3d7b3be912f79961e7bffba91a6f2 to your computer and use it in GitHub Desktop.
Pathom Wrapper Macros (Pathom 2)
(ns app.lib.pathom.connect-macros
(:require
[app.lib.pathom.registry :refer [register!]]
[clojure.spec.alpha :as s]
[com.fulcrologic.fulcro.algorithms.do-not-use :as futil]
[com.wsscode.pathom.connect :as pc]
[com.wsscode.pathom.core :as p]
[taoensso.timbre :as log]
[taoensso.tufte :as tufte]))
(def ^:dynamic *disable-permissions* false)
(s/def ::check (s/or :sym symbol? :expr list?))
(s/def ::mutation-args (s/cat
:sym simple-symbol?
:doc (s/? string?)
:arglist vector?
:config map?
:body (s/* any?)))
(defn defpathom-backend-endpoint* [endpoint args update-database?]
(let [{:keys [sym arglist doc config body]} (futil/conform! ::mutation-args args)
internal-fn-sym (symbol (str (name sym) "__internal-fn__"))
fqsym (if (namespace sym)
sym
(symbol (name (ns-name *ns*)) (name sym)))
{:keys [check ex-return]} config
config (dissoc config :check :ex-return)
env-arg (first arglist)
params-arg (second arglist)
ex-msg (str fqsym " unauthorized, " check " violated")]
(when (nil? check)
(throw (IllegalArgumentException. "Config map MUST contain a :check key")))
`(do
;; Use this internal function so we can dynamically update a resolver in
;; dev without having to restart the whole pathom parser.
(defn ~internal-fn-sym [env# params#]
(let [~env-arg env#
~params-arg params#
result# (if (or *disable-permissions* (~check {:env env# :params params#}))
(do
(when *disable-permissions*
(log/debug "Permissions disabled. Allowing resolver"))
(log/debug "Permissions passed for" (quote ~fqsym))
(tufte/p (quote ~fqsym)
~@body))
(do
(log/error ~ex-msg {:params params#
:query-params (:query-params env#)})
nil))]
;; Pathom doesn't expect nils
(cond
(sequential? result#) (vec (remove nil? result#))
(nil? result#) {}
:else result#)))
(~endpoint ~(cond-> sym
doc (with-meta {:doc doc})) [env# params#]
~config
(~internal-fn-sym env# params#))
(app.lib.pathom.registry/register! ~sym)
::done)))
(defmacro
^{:doc "Defines a server-side PATHOM mutation.
Example:
(defmutation do-thing
\"Optional docstring\"
[params]
{::pc/input [:param/name] ; PATHOM config (optional)
::pc/output [:result/prop]
:check security-check}
...) ; actual action (required)"
:arglists '([sym docstring? arglist config & body])} defmutation
[& args]
(defpathom-backend-endpoint* `pc/defmutation args true))
(defmacro ^{:doc "Defines a pathom resolver but with authorization.
Example:
(defresolver resolver-name [env input]
{::pc/input [:customer/id]
:check s.checks/ownership
...}
{:customer/name \"Bob\"})
"
:arglists '([sym docstring? arglist config & body])} defresolver
[& args]
(defpathom-backend-endpoint* `pc/defresolver args false))
(defn final
"Tell Pathom the result is complete, and needs no further resolution.
This is a performance optimization.
Use this around collections being returned by a resolver when you are *certain* that the real query for the item(s)
in question are *completely* resolved. Pathom will stop working on those items.
Examples:
* A resolver returning `{:x [...]}` should instead return `{:x (final [...])}`.
* A resolver using a helper function that returns a map with a nested collection can use the arity 2 of this function:
`(final :x result)`, which is just a convenient form for `(update result :x final)`
"
([data]
(some-> data (vary-meta assoc ::p/final true)))
([at-k data]
(some-> data
(update at-k vary-meta assoc ::p/final true))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment