Skip to content

Commit

Permalink
Added core and type namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
ajk committed Mar 9, 2018
1 parent 3788eee commit 4838970
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 0 deletions.
23 changes: 23 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(defproject specialist-client "0.1.0-SNAPSHOT"
:description "ClojureScript GraphQL client"
:url "https://github.com/ajk/specialist-client"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/clojurescript "1.9.946" :scope "provided"]]

:plugins [[lein-cljsbuild "1.1.7"]
[lein-doo "0.1.8"]]

:aliases {"test-cljs" ["with-profile" "test" "doo" "nashorn" "test" "once"]
"test-all" ["do" ["test"] ["test-cljs"]]}
:profiles
{:test {:cljsbuild
{:builds
{:test
{:source-paths ["src" "test"]
:compiler {:output-to "target/main.js"
:output-dir "target"
:language-in :ecmascript5
:main specialist-client.test-runner
:optimizations :simple}}}}}})
51 changes: 51 additions & 0 deletions src/specialist_client/core.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
(ns specialist-client.core
#?(:cljs
(:require [specialist-client.type :as t :include-macros true])))

(def default-opt
{:method "post"
:credentials "include"
:headers {"Accept" "application/json"
"Content-Type" "application/json"}})

#?(:cljs
(defn default-fail [res]
(doseq [e (:errors res)]
(when (:message e)
(js/console.error "error:" (:message e))))
(-> res clj->js js/console.error)))

#?(:cljs
(defn client
([url] (client url {}))
([url input-opt]
(let [req-opt (merge default-opt input-opt)]
(fn [body opt]
(let [{:keys [on-success on-failure]} (if (fn? opt)
{:on-success opt
:on-failure default-fail}
opt)]
(-> (js/fetch
url (-> req-opt
(assoc :body (.stringify js/JSON (clj->js body)))
clj->js))
(.then
(fn [res]
(if (= 200 (.-status res))
(.json res)
(on-failure
{:errors [{:message "request failed"
:status (.-status res)
:status-text (.-statusText res)
:body (.-body res)}]}))))
(.then
(fn [res]
(let [data (js->clj res :keywordize-keys true)]
(if (:data data)
(on-success data)
(on-failure data)))))
(.catch
(fn [err]
(on-failure
{:errors [{:message "request failed"
:exception (js->clj err)}]}))))))))))
157 changes: 157 additions & 0 deletions src/specialist_client/type.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
(ns specialist-client.type
#?(:cljs
(:require-macros [specialist-client.type :refer [defscalar defenum]]))
(:refer-clojure :exclude [int long float boolean])
(:require [clojure.spec.alpha :as s]
[clojure.string :as string]))

(def scalar-kind "SCALAR")
(def object-kind "OBJECT")
(def interface-kind "INTERFACE")
(def union-kind "UNION")
(def enum-kind "ENUM")
(def input-object-kind "INPUT_OBJECT")
(def list-kind "LIST")
(def non-null-kind "NON_NULL")


#?(:clj
(defmacro defscalar
"Defines new scalar types."
[type-name type-meta conform-fn]
(let [meta-map (if (map? type-meta) type-meta {:name (name type-name) :description type-meta})]
`(def ~(vary-meta type-name
assoc
:specialist-client.type/name (:name meta-map)
:specialist-client.type/kind scalar-kind
:specialist-client.type/type-description (:description meta-map)
:specialist-client.type/field-description "Self descriptive.")
(vary-meta (s/conformer ~conform-fn)
assoc
:specialist-client.type/name ~(:name meta-map)
:specialist-client.type/kind ~scalar-kind
:specialist-client.type/type-description ~(:description meta-map)
:specialist-client.type/field-description "Self descriptive.")))))

#?(:clj
(defmacro defenum
"Defines new enum types."
[enum-name enum-meta enum-set]
(when-not (set? enum-set)
(throw (#?(:clj IllegalArgumentException.
:cljs js/Error.)
"last argument must be a set")))
(let [meta-map (if (map? enum-meta) enum-meta {:name (name enum-name) :description enum-meta})]
`(def ~(vary-meta enum-name
assoc
:specialist-client.type/name (:name meta-map)
:specialist-client.type/kind enum-kind
:specialist-client.type/type-description (:description meta-map)
:specialist-client.type/field-description "Self descriptive.")
(vary-meta ~enum-set
assoc
:specialist-client.type/name ~(:name meta-map)
:specialist-client.type/kind ~enum-kind
:specialist-client.type/type-description ~(:description meta-map)
:specialist-client.type/field-description "Self descriptive.")))))

#?(:cljs
(defn field
([t doc] (field t doc {}))
([t doc opt]
(vary-meta t
assoc
:specialist-client.type/field-description doc
:specialist-client.type/is-deprecated (clojure.core/boolean (:deprecated opt))
:specialist-client.type/deprecation-reason (:deprecated opt)))))

;;;

#?(:cljs
(defscalar
string
{:name "String"
:description
(str "The 'String' scalar type represents textual data, represented as UTF-8 "
"character sequences. The String type is most often used by GraphQL to "
"represent free-form human-readable text.")}
(fn [v]
(cond
(nil? v) ::s/invalid
(coll? v) ::s/invalid
:else (str v)))))

#?(:cljs
(defscalar
int
{:name "Int"
:description
(str "The 'Int' scalar type represents non-fractional signed whole numeric values. "
"Int can represent values between -(2^31) and 2^31 - 1.")}
(fn [v]
(let [int-min -2147483648
int-max 2147483647
i (js/parseInt v 10)]
(if (and (not (js/isNaN i)) (>= i int-min) (<= i int-max))
i
::s/invalid)))))



#?(:cljs
(defscalar
long
{:name "Long"
:description
(str "The 'Long' scalar type represents non-fractional signed whole numeric "
"values. Long can represent values between -(2^64) and 2^64 - 1.")}
(fn [v]
(let [long-min -9223372036854775808
long-max 9223372036854775807
i (js/parseInt v 10)]
(if (and (not (js/isNaN i)) (>= i long-min) (<= i long-max))
i
::s/invalid)))))

#?(:cljs
(defscalar
float
{:name "Float"
:description
(str "The 'Float' scalar type represents signed double-precision fractional values "
"as specified by IEEE 754")}
(fn [v]
(let [f (js/parseFloat v)]
(if-not (js/isNaN f)
f
::s/invalid)))))

#?(:cljs
(defscalar
boolean
{:name "Boolean"
:description "The 'Boolean' scalar type represents 'true' or 'false'."}
(fn [v]
(cond
(and (boolean? v) (= true v)) true
(and (boolean? v) (= false v)) false
(string/blank? v) ::s/invalid
(and (clojure.core/string? v) (re-find #"(?i)^true$" v)) true
(and (clojure.core/string? v) (re-find #"(?i)^false$" v)) false
:else ::s/invalid))))

#?(:cljs
(defscalar
id
{:name "ID"
:description
(str "The 'ID' scalar type represents a unique identifier, often used to refetch "
"an object or as key for a cache. The ID type appears in a JSON response as a "
"String; however, it is not intended to be human-readable. When expected as an "
"input type, any string (such as \"4\") or integer (such as 4) input value "
"will be accepted as an ID.")}
(fn [v]
(cond
(string/blank? v) ::s/invalid
(coll? v) ::s/invalid
:else (str v)))))
9 changes: 9 additions & 0 deletions test/specialist_client/core_test.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(ns specialist-client.core-test
(:require #?(:clj [clojure.test :refer :all]
:cljs [cljs.test :refer-macros [deftest is testing]])
[specialist-client.core :as core]))

#?(:cljs
(deftest core-test
(testing "client"
(is (fn? (core/client "/graphql"))))))
7 changes: 7 additions & 0 deletions test/specialist_client/test_runner.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(ns specialist-client.test-runner
(:require [doo.runner :refer-macros [doo-tests]]
[specialist-client.core-test]
[specialist-client.type-test]))

(doo-tests 'specialist-client.core-test
'specialist-client.type-test)
59 changes: 59 additions & 0 deletions test/specialist_client/type_test.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
(ns specialist-client.type-test
(:require #?(:clj [clojure.test :refer :all]
:cljs [cljs.test :refer-macros [deftest is testing]])
[clojure.spec.alpha :as s]
[specialist-client.type :as t :include-macros true]))

#?(:cljs
(s/def ::string t/string))

#?(:cljs
(s/def ::int t/int))

#?(:cljs
(s/def ::long t/long))

#?(:cljs
(s/def ::float t/float))

#?(:cljs
(s/def ::boolean t/boolean))


#?(:cljs
(s/def ::id t/id))

#?(:cljs
(deftest type-test

(testing "valid string"
(is (s/valid? ::string "ok")))
(testing "invalid string"
(is (not (s/valid? ::string nil))))

(testing "valid int"
(is (s/valid? ::int 1)))
(testing "invalid int"
(is (not (s/valid? ::int "4294967296"))))

(testing "valid long"
(is (s/valid? ::long "4294967296")))
(testing "invalid long"
(is (not (s/valid? ::long "not long"))))

(testing "valid float"
(is (s/valid? ::float 1.0)))
(testing "invalid float"
(is (not (s/valid? ::float nil))))

(testing "valid boolean"
(is (s/valid? ::boolean "TRUE")))
(testing "invalid boolean"
(is (not (s/valid? ::boolean "?"))))

(testing "valid id"
(is (s/valid? ::id 123)))
(testing "invalid id"
(is (not (s/valid? ::id [123]))))

))

0 comments on commit 4838970

Please sign in to comment.