Giter Site home page Giter Site logo

eql-as's Introduction

eql-as

Utility functions to create EQL queries with "renaming" capabilities

How?

This library do not implement renaming. It helps you to create queries that used with EQL parsers like pathom will result in a renaming

Quick example:

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.

Usage

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}]}) 

Qualify a map

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"}}

Request a query, with unqualified keys.

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"}}

Datomic

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"}}

Tips and tricks

coercion

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"}

validation

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

placeholders

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"}}}

Advanced coercion

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]}

Real World exmaple

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    "[email protected]"
                          :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 "[email protected]",
;;                         :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

eql-as's People

Contributors

jeroenvandijk avatar souenzzo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

jeroenvandijk

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.