-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}}}}}}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)}]})))))))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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])))) | ||
|
||
)) |