Utility functions to create EQL queries with "renaming" capabilities
This library do not implement renaming. It helps you to create queries that used with EQL parsers like pathom will result in a renaming
Let's say that you have a unqualified map:
{:name "Alex"
:address [{:street "Atlantic"}]}
You can create a EQL Query describing the qualify process, like this
[(:name {:pathom/as :user/name})
{(:address {:pathom/as :user/address}) [(:street {:pathom/as :address/street})]}]
Click here if you can't understand this query
Without params, the query will look like this
[:name
{:address [:street]}]
this query says:
- From the map, select the key
:name
- From the map, select the key
:address
- From the map inside
:address
, select the key:street
Now we can add params to this query
[(:name {})
{:address [:street]}]
this query says:
- From the map, select the key
:name
with params{}
....
pathom know how to use some special params, like :pathom/as
With params, the query will look like this
[(:name {:pathom/as :user/name})
{(:address {:pathom/as :user/address}) [(:street {:pathom/as :address/street})]}]
this query says:
- From the map, select the key
:name
with params{:pathom/as :user/name}
. Pathom will assoc:name
as:user/name
in the result - From the map, select the key
:address
with params{:pathom/as :user/address}
. Pathom will assoc:address
as:user/address
in the result - From the map inside
:address
, select the key:street
with params{:pathom/as :address/street}
. Pathom will assoc:street
as:address/street
in the result
In pathom case, it use :pathom/as
as alias
keyword.
Once you run this query in this data, it will be qualified
;; (require '[com.wsscode.pathom.core :as p])
(p/map-select
{:name "Alex"
:address [{:street "Atlantic"}]}
`[(:name {:pathom/as :user/name})
{(:address {:pathom/as :user/address}) [(:street {:pathom/as :address/street})]}])
;; => {:user/name "Alex"
;; :user/address [{:address/street "Atlantic"}]
We now have a "free" map-qualifier. eql-as
will help you to create this kind of query.
Add to your deps.edn
br.com.souenzzo/eql-as {:git/url "https://github.com/souenzzo/eql-as.git"
:sha "e59e457c77603384276d67ed446c2d1cbc8cab85"}
Let's start with a sample data, like
{"name": "Alex",
"address": {"street": "Atlantic"}}
Then we create a as-map
, that specify how you want to "qualify" your data.
Here we say {:the-final-name-that-i-want :the-name-on-original-data}
(def user-as-map
{:user/name :name
:user/address [:address {:address/street :street}]})
Now we can create a query that pathom will know who to qualify your data
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[com.wsscode.pathom.connect :as pc]
;; '[com.wsscode.pathom.core :as p])
(->> {::eql-as/as-map user-as-map
::eql-as/as-key :pathom/as}
eql-as/ident-query
(p/map-select {:name "Alex"
:address {:street "Atlantic"}}))
;; => {:user/name "Alex", :user/address {:address/street "Atlantic"}}
We can also do the oposite operation, run our parser asking for the data
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[com.wsscode.pathom.connect :as pc]
;; '[com.wsscode.pathom.core :as p])
(let [parser (p/parser {::p/plugins [(pc/connect-plugin {::pc/register [(pc/constantly-resolver :user/name "Alex")
(pc/constantly-resolver :user/address {})
(pc/constantly-resolver :address/street "Atlantic")]})]
::p/env {::p/reader [p/map-reader
pc/reader2]}})]
(->> {::eql-as/as-map user-as-map
::eql-as/as-key :pathom/as}
eql-as/as-query
(parser {})))
;; => {:name "Alex", :address {:street "Atlantic"}}
You can use it with datomic, using eql-datomic
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[br.com.souenzzo.eql-datomic :as eqld]
;; '[edn-query-language.core :as eql]
;; '[datomic.api :as d])
(let [pattern (->> {::eql-as/as-map user-as-map
::eql-as/as-key :as}
eql-as/as-query
eql/query->ast
eqld/ast->query)]
(d/pull db pattern user-id))
;; => => {:name "Alex", :address {:street "Atlantic"}}
You can use this libs with spec-coerce to get coercion
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[clojure.spec.alpha :as s]
;; '[spec-coerce.core :as sc])
(s/def ::born inst?)
(let [data {:born-date "1993"}
pattern (->> {::eql-as/as-map {::born :born-date}
::eql-as/as-key :pathom/as}
eql-as/ident-query)]
(sc/coerce-structure (p/map-select data pattern)))
;; => {::born #inst"1993"}
You can use this libs with spec to get validation (usually after coercion)
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[clojure.spec.alpha :as s]
;; '[com.wsscode.pathom.core :as p]
;; '[spec-coerce.core :as sc])
(s/def ::born inst?)
(let [data {:born-date "1993"}
pattern (->> {::eql-as/as-map {::born :born-date}
::eql-as/as-key :pathom/as}
eql-as/ident-query)]
(s/valid? (s/keys :req [::born]) (sc/coerce-structure (p/map-select data pattern))))
;; => true
Pathom has a placeholder concept and you can use it.
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[com.wsscode.pathom.connect :as pc]
;; '[com.wsscode.pathom.core :as p])
(let [parser (p/parser {::p/plugins [(pc/connect-plugin {::pc/register [(pc/constantly-resolver :user/name "Alex")
(pc/constantly-resolver :user/address {})
(pc/constantly-resolver :address/street "Atlantic")]})]
::p/env {::p/reader [p/map-reader
pc/reader2
p/env-placeholder-reader]
::p/placeholder-prefixes #{">"}}})]
(->> {::eql-as/as-map {:user/name :name
:user/address [:address {:>/street [:street {:address/street :name}]}]}
::eql-as/as-key :pathom/as}
eql-as/as-query
(parser {})))
;; => {:name "Alex", :address {:street {:name "Atlantic"}}}
Sometimes we need a "real" function to mae the "coercion". We can do it again with parsers and queries.
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[com.wsscode.pathom.connect :as pc]
;; '[com.wsscode.pathom.core :as p])
(let [register [(pc/single-attr-resolver :user/roles-str :user/roles (partial mapv (partial keyword "user.roles")))]
parser (p/parser {::p/plugins [(pc/connect-plugin {::pc/register register})]
::p/env {::p/reader [p/map-reader
pc/reader2]}})
data {:id "123"
:roles ["admin"]}
qualified (->> {::eql-as/as-map {:user/id :id
:user/roles-str :roles}
::eql-as/as-key :pathom/as}
eql-as/ident-query
(p/map-select data))]
(parser {::p/entity qualified}
[:user/id
:user/roles]))
;; => {:user/id "123", :user/roles [:user.roles/admin]}
Let's implement a REST API, like CreateUser from RealWorld spec
;; (require '[br.com.souenzzo.eql-as.alpha :as eql-as]
;; '[com.wsscode.pathom.core :as p])
(let [json-params {:user {:username "souenzzo"
:email "souenzzo@souenzzo.com.br"
:password "*****"}}
params (->> {::eql-as/as-map {:>/user [:user {:user/email :email
:user/password :password
:user/slug :username
:image :user/image}]}
::eql-as/as-key :pathom/as}
eql-as/ident-query
(p/map-select json-params))
returning (-> {::eql-as/as-map {:user [:>/user {:email :user/email
:token :user/token
:username :user/slug
:bio :user/bio
:image :user/image}]}
::eql-as/as-key :pathom/as}
eql-as/ident-query)
query `[{(create-user ~(:>/user params))
~returning}]]
query)
;; => [{(user/create-user {:user/email "souenzzo@souenzzo.com.br",
;; :user/password "*****",
;; :user/slug "souenzzo"})
;; [({:>/user [(:user/email {:pathom/as :email})
;; (:user/token {:pathom/as :token})
;; (:user/slug {:pathom/as :username})
;; (:user/bio {:pathom/as :bio})
;; (:user/image {:pathom/as :image})]}
;; {:pathom/as :user})]}]
This query you can pipe into your parser and the return can be directly back on :body