diff --git a/resources/com/walmartlabs/lacinia/introspection.edn b/resources/com/walmartlabs/lacinia/introspection.edn index c6bbe01f..ca716ee4 100644 --- a/resources/com/walmartlabs/lacinia/introspection.edn +++ b/resources/com/walmartlabs/lacinia/introspection.edn @@ -28,7 +28,9 @@ :inputFields {:type (list :__InputValue) :resolve :input-fields} :ofType {:type :__Type - :resolve :of-type}}} + :resolve :of-type} + :specifiedByUrl {:type String + :resolve :specified-by-url}}} :__Field {:fields diff --git a/src/com/walmartlabs/lacinia/introspection.clj b/src/com/walmartlabs/lacinia/introspection.clj index 897801a3..c4e78203 100644 --- a/src/com/walmartlabs/lacinia/introspection.clj +++ b/src/com/walmartlabs/lacinia/introspection.clj @@ -280,6 +280,11 @@ (::type-map input-value) (::default-value input-value))) +(defn ^:private resolve-specified-by-url + [_ _ {:keys [::category ::type-def] :as _value}] + (when (= :scalar category) + (:specified-by type-def))) + (defn introspection-schema "Builds an returns the introspection schema, which can be merged into the user schema." [] @@ -296,4 +301,5 @@ :interfaces resolve-interfaces :of-type resolve-of-type :possible-types resolve-possible-types - :default-value default-value}))) + :default-value default-value + :specified-by-url resolve-specified-by-url}))) diff --git a/src/com/walmartlabs/lacinia/parser/schema.clj b/src/com/walmartlabs/lacinia/parser/schema.clj index a6bd648a..70c2fbdc 100644 --- a/src/com/walmartlabs/lacinia/parser/schema.clj +++ b/src/com/walmartlabs/lacinia/parser/schema.clj @@ -629,8 +629,9 @@ (s/def ::fn-map (s/map-of simple-keyword? ::field-fn)) (s/def ::parse ::schema/parse-or-serialize-fn) (s/def ::serialize ::schema/parse-or-serialize-fn) +(s/def ::specified-by string?) (s/def ::scalar-def (s/keys :req-un [::parse ::serialize] - :opt-un [::description])) + :opt-un [::description ::specified-by])) (s/def ::description string?) (s/def ::documentation (s/map-of keyword? string?)) diff --git a/src/com/walmartlabs/lacinia/schema.clj b/src/com/walmartlabs/lacinia/schema.clj index 255c3272..d0463906 100644 --- a/src/com/walmartlabs/lacinia/schema.clj +++ b/src/com/walmartlabs/lacinia/schema.clj @@ -38,9 +38,9 @@ [clojure.pprint :as pprint] [com.walmartlabs.lacinia.selection :as selection]) (:import - (clojure.lang IObj PersistentQueue) - (java.io Writer) - (java.util.concurrent Executor ThreadPoolExecutor TimeUnit LinkedBlockingQueue ThreadFactory))) + (clojure.lang IObj PersistentQueue) + (java.io Writer) + (java.util.concurrent Executor ThreadPoolExecutor TimeUnit LinkedBlockingQueue ThreadFactory))) ;; When using Clojure 1.8, the dependency on clojure-future-spec must be included, ;; and this code will trigger @@ -289,6 +289,13 @@ ([message data] (merge {:message message} data))) +(defn valid-url? [s] + (try + (let [uri (java.net.URI. s)] + (.toURL uri) + true) + (catch Exception _ false))) + ;;------------------------------------------------------------------------------- ;; ## Validations @@ -397,8 +404,10 @@ :var var?)) (s/def ::parse ::parse-or-serialize-fn) (s/def ::serialize ::parse-or-serialize-fn) +(s/def ::specified-by valid-url?) (s/def ::scalar (s/keys :opt-un [::description - ::directives] + ::directives + ::specified-by] :req-un [::parse ::serialize])) (s/def ::scalars (s/map-of ::schema-key ::scalar)) @@ -614,7 +623,7 @@ (directives [_] compiled-directives)) -(defrecord ^:private Scalar [category type-name description parse serialize directives compiled-directives] +(defrecord ^:private Scalar [category type-name description parse serialize directives compiled-directives specified-by] selection/TypeDef @@ -1924,9 +1933,9 @@ ;; Note: using merge, not two calls to xfer-types, since want to allow ;; for overrides of the built-in scalars without a name conflict exception. (let [merged-scalars (->> schema - :scalars - (merge default-scalar-transformers) - (map-vals #(assoc % :category :scalar))) + :scalars + (merge default-scalar-transformers) + (map-vals #(assoc % :category :scalar))) executor (or (:executor options) resolve/*callback-executor* (default-executor)) @@ -1939,30 +1948,30 @@ :subscription subscription} ::executor executor ::options options} - (xfer-types merged-scalars :scalar) - (xfer-types (:enums schema) :enum) - (xfer-types (:unions schema) :union) - (xfer-types (:objects schema) :object) - (xfer-types (:interfaces schema) :interface) - (xfer-types (:input-objects schema) :input-object) - (add-root query :queries (:queries schema)) - (add-root mutation :mutations (:mutations schema)) - (add-root subscription :subscriptions (:subscriptions schema)) - (apply-default-subscription-resolver subscription) - (as-> s - (map-vals #(compile-type % s) s)) - (compile-directive-defs (:directive-defs schema)) - (prepare-and-validate-interfaces) - (prepare-and-validate-objects :object) - (prepare-and-validate-objects :input-object) - (validate-directives-by-category :union) - (validate-directives-by-category :scalar) - validate-enum-directives - inject-null-collapsers + (xfer-types merged-scalars :scalar) + (xfer-types (:enums schema) :enum) + (xfer-types (:unions schema) :union) + (xfer-types (:objects schema) :object) + (xfer-types (:interfaces schema) :interface) + (xfer-types (:input-objects schema) :input-object) + (add-root query :queries (:queries schema)) + (add-root mutation :mutations (:mutations schema)) + (add-root subscription :subscriptions (:subscriptions schema)) + (apply-default-subscription-resolver subscription) + (as-> s + (map-vals #(compile-type % s) s)) + (compile-directive-defs (:directive-defs schema)) + (prepare-and-validate-interfaces) + (prepare-and-validate-objects :object) + (prepare-and-validate-objects :input-object) + (validate-directives-by-category :union) + (validate-directives-by-category :scalar) + validate-enum-directives + inject-null-collapsers ;; Last so that schema is as close to final and verified state as possible - (prepare-field-resolvers options) - (prepare-field-streamers options) - map->CompiledSchema))) + (prepare-field-resolvers options) + (prepare-field-streamers options) + map->CompiledSchema))) (defn default-field-resolver "The default for the :default-field-resolver option, this uses the field name as the key into diff --git a/test/com/walmartlabs/introspection_test.clj b/test/com/walmartlabs/introspection_test.clj index 46b30564..11c47172 100644 --- a/test/com/walmartlabs/introspection_test.clj +++ b/test/com/walmartlabs/introspection_test.clj @@ -1505,3 +1505,23 @@ :line 1}] :message "Cannot query field `__type' on type `Query'."}]} (execute schema q))))) + +(def ^:private specified-by-url-query + "{ + __type(name: \"DateTime\") { + name + kind + specifiedByUrl + } + }") + +(deftest scalar-specified-by-url + (let [schema (schema/compile {:scalars {:DateTime {:parse identity + :serialize identity + :specified-by "https://scalars.graphql.org/andimarek/date-time.html"}}})] + (is (= {:data + {:__type + {:kind :SCALAR + :name "DateTime" + :specifiedByUrl "https://scalars.graphql.org/andimarek/date-time.html"}}} + (utils/execute schema specified-by-url-query)))))