From 901178b523503b91e50eb79079e3fcf8f0d4e24c Mon Sep 17 00:00:00 2001 From: myHan Date: Mon, 8 Aug 2022 23:10:31 +0900 Subject: [PATCH 01/18] Add edn federation --- deps.edn | 5 +- dev-resources/edn-federation.edn | 77 ++++++ dev-resources/edn-federation.sdl | 34 +++ src/com/walmartlabs/lacinia/federation.clj | 227 ++++++++++++++++-- .../walmartlabs/lacinia/federation_tests.clj | 84 ++++++- 5 files changed, 401 insertions(+), 26 deletions(-) create mode 100644 dev-resources/edn-federation.edn create mode 100644 dev-resources/edn-federation.sdl diff --git a/deps.edn b/deps.edn index d8fc6c29..1fde65f4 100644 --- a/deps.edn +++ b/deps.edn @@ -1,7 +1,8 @@ {:deps {org.clojure/clojure {:mvn/version "1.10.3"} clj-antlr/clj-antlr {:mvn/version "0.2.12"} org.flatland/ordered {:mvn/version "1.15.10"} - org.clojure/data.json {:mvn/version "2.4.0"}} + org.clojure/data.json {:mvn/version "2.4.0"} + org.clojure/core.match {:mvn/version "1.0.0"}} :paths ["src" "resources"] :aliases {:dev @@ -51,4 +52,4 @@ :codox/config {:description "Clojure-native implementation of GraphQL" - :source-uri "https://github.com/walmartlabs/lacinia/blob/master/{filepath}#L{line}"}} \ No newline at end of file + :source-uri "https://github.com/walmartlabs/lacinia/blob/master/{filepath}#L{line}"}} diff --git a/dev-resources/edn-federation.edn b/dev-resources/edn-federation.edn new file mode 100644 index 00000000..f2283d82 --- /dev/null +++ b/dev-resources/edn-federation.edn @@ -0,0 +1,77 @@ +{:objects + {:_Service + {:fields + {:sdl + {:type (non-null String)}}} + :User + {:fields + {:id + {:type (non-null Int)} + :name + {:type (non-null String)}} + :directives [{:directive-type :key :directive-args {:fields "id"}}]} + :Query + {:fields + {:user_by_id + {:type :User :args + {:id + {:type (non-null Int)}}}}} + :Account + {:fields + {:acct_number + {:type (non-null String)} :name + {:type (non-null String)}} + :directives [{:directive-type :key :directive-args {:fields "acct_number"}}]} + :Product + {:fields + {:upc + {:type (non-null String) :directives [{:directive-type :external}]} :reviewed_by + {:type :User}} + :directives [{:directive-type :key :directive-args {:fields "upc"}} + {:directive-type :extends}]}} + :scalars + {:_Any + {:parse :_Any/parser, + :serialize :_Any/serializer}, + :_FieldSet + {:parse :_FieldSet/parser, + :serialize :_FieldSet/serializer} + :link__Import + {:parse :link__Import/parser, + :serialize :link__Import/serializer}} + + :enums + {:link__Purpose + {:values [{:enum-value :SECURITY} {:enum-value :EXECUTION}]}} + + :directive-defs + {:external + {:locations #{:field-definition}} + :requires + {:locations #{:field-definition} + :args {:fields {:type (non-null _FieldSet)}}} + :provides + {:locations #{:field-definition} + :args {:fields {:type (non-null _FieldSet)}}} + :key + {:locations #{:object :interface} + :args {:fields {:type (non-null _FieldSet)} + :resolvable {:type Boolean :default-value true}}} + :link + {:locations #{:schema}, + :args {:url {:type String}, :as {:type String}, :for {:type :link__Purpose}, :import {:type (list :link__Import)}}} + :shareable {:locations #{:field-definition :object}}, + :inaccessible + {:locations + #{:enum + :input-field-definition + :interface + :input-object + :enum-value + :scalar + :argument-definition + :union + :field-definition + :object}}, + :override {:locations #{:field-definition}, :args {:from {:type (non-null String)}}}, + :extends {:locations #{:interface :object}}}} diff --git a/dev-resources/edn-federation.sdl b/dev-resources/edn-federation.sdl new file mode 100644 index 00000000..8faed9aa --- /dev/null +++ b/dev-resources/edn-federation.sdl @@ -0,0 +1,34 @@ +type _Service{ +sdl: String! +} +type User @key(fields: "id") { +id: Int! +name: String! +} +type Query{ +user_by_id(id: Int!): User +} +type Account @key(fields: "acct_number") { +acct_number: String! +name: String! +} +type Product @key(fields: "upc") @extends { +upc: String! +reviewed_by: User +} +scalar _Any +scalar _FieldSet +scalar link__Import +enum link__Purpose{ +SECURITY +EXECUTION +} +directive @extends on INTERFACE | OBJECT +directive @key(fields: _FieldSet!, resolvable: Boolean = true) on INTERFACE | OBJECT +directive @external on FIELD_DEFINITION +directive @shareable on FIELD_DEFINITION | OBJECT +directive @requires(fields: _FieldSet!) on FIELD_DEFINITION +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) on SCHEMA +directive @provides(fields: _FieldSet!) on FIELD_DEFINITION +directive @override(from: String!) on FIELD_DEFINITION +directive @inaccessible on ENUM | INPUT_FIELD_DEFINITION | INTERFACE | INPUT_OBJECT | ENUM_VALUE | SCALAR | ARGUMENT_DEFINITION | UNION | FIELD_DEFINITION | OBJECT diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index c16dcb4c..ac5a5de0 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -14,11 +14,13 @@ (ns com.walmartlabs.lacinia.federation (:require - [com.walmartlabs.lacinia.resolve :as resolve :refer [with-error]] - [com.walmartlabs.lacinia.internal-utils :as utils :refer [get-nested]] - [com.walmartlabs.lacinia.resolve-utils :as ru] - [com.walmartlabs.lacinia.schema :as schema] - [clojure.spec.alpha :as s])) + [com.walmartlabs.lacinia.resolve :as resolve :refer [with-error]] + [com.walmartlabs.lacinia.internal-utils :as utils :refer [get-nested]] + [com.walmartlabs.lacinia.resolve-utils :as ru] + [com.walmartlabs.lacinia.schema :as schema] + [clojure.spec.alpha :as s] + [clojure.string :refer [join]] + [clojure.core.match :refer [match]])) (def foundation-types "Map of annotations and types to automatically include into an SDL @@ -119,25 +121,206 @@ (ru/aggregate-results results #(maybe-wrap (reduce into [] %)))))))) +(defn apply-list + [f x] + (if (-> x first seq?) + (apply f x) + (f x))) + +(defn edn-description->sdl-description + [description] + (if (nil? description) + "" + (str "\"\"\"\n" description "\n\"\"\"\n"))) + +(defn edn-type->sdl-type + [type] + (if (seq? type) + (let [[hd & tl] type] + (match hd + nil "" + 'non-null (str (apply-list edn-type->sdl-type tl) "!") + 'list (str "[" (apply-list edn-type->sdl-type tl) "]") + 'String "String" + 'Int "Int" + 'Float "Float" + 'Boolean "Boolean" + 'ID "ID" + (object :guard keyword?) (name object) + (scalar :guard symbol?) (name scalar))) + (recur (list type)))) + +(defn value->string + [value] + (match value + (string :guard string?) (str "\"" string "\"") + (keyword :guard keyword?) (name keyword) + else (str else))) + +(defn edn-default-value->sdl-default-value + [default-value] + (if (nil? default-value) + "" + (str " = " (value->string default-value)))) + +(defn- edn-arg-descrption->sdl-arg-description + [description] + (if (nil? description) + "" + (str "\"" description "\" "))) + +(defn edn-args->sdl-args + [args] + (if (nil? args) + "" + (str "(" (join ", " (map (fn [[arg-name {:keys [type default-value description]}]] (str (edn-arg-descrption->sdl-arg-description description) (name arg-name) ": " (edn-type->sdl-type type) (edn-default-value->sdl-default-value default-value))) args)) ")"))) + +(defn edn-directive-args->sdl-directive-args + [directive-args] + (if (nil? directive-args) + "" + (str "(" (->> directive-args + (map (fn [[arg-name arg-value]] (str (name arg-name) ": " (value->string arg-value)))) + (join ", ")) ")"))) + +(defn edn-directives->sdl-directives + [directives] + (if (nil? directives) + "" + (str " " + (->> directives + (map (fn [{:keys [directive-type directive-args]}] + (str "@" (name directive-type) (edn-directive-args->sdl-directive-args directive-args)))) + (join " ")) " "))) + +(defn edn-fields->sdl-fields + [fields] + (str + "{\n" + (->> fields + (map (fn [[field-name {:keys [type args description]}]] + (str (edn-description->sdl-description description) (name field-name) (edn-args->sdl-args args) ": " (edn-type->sdl-type type)))) + (join "\n")) + "\n}")) + +(defn edn-objects->sdl-objects + [objects] + (->> objects + (map (fn [[key {:keys [fields directives description]}]] + (str (edn-description->sdl-description description) + "type " + (name key) + (edn-directives->sdl-directives directives) + (edn-fields->sdl-fields fields)))) + (join "\n"))) +(defn edn-queries->sdl-queries + [queries] + (str (-> queries :description edn-description->sdl-description) "type Query " (edn-fields->sdl-fields queries))) + +(defn edn-interfaces->sdl-interfaces + [interfaces] + (->> interfaces + (map (fn [[key val]] + (str "interface " + (name key) + (-> val :fields edn-fields->sdl-fields)))) + (join "\n"))) +(defn edn-input-objects->sdl-input-objects + [input-objects] + (->> input-objects + (map (fn [[key val]] + (str "input " + (name key) + (-> val :fields edn-fields->sdl-fields)))) + (join "\n"))) +(defn edn-unions->sdl-unions + [unions] + (->> unions + (map (fn [[union-name {members :members}]] + (str "union " (name union-name) " = " (->> members + (map name) + (join " | "))))) + (join "\n"))) +(defn edn-mutations->sdl-mutations + [mutations] + (str "type Mutation " (edn-fields->sdl-fields mutations))) +(defn edn-enums->sdl-enums + [enums] + (->> enums + (map (fn [[enum-name {values :values}]] + (str "enum " (name enum-name) "{\n" (->> values (map :enum-value) (map name) (join "\n")) "\n}"))) + (join "\n"))) +(defn edn-scalars->sdl-scalars + [scalars] + (->> (keys scalars) + (map name) + (map #(str "scalar " %)) + (join "\n"))) + +(def directive-targets + {:enum "ENUM" + :input-field-definition "INPUT_FIELD_DEFINITION" + :interface "INTERFACE" + :input-object "INPUT_OBJECT" + :enum-value "ENUM_VALUE" + :scalar "SCALAR" + :argument-definition "ARGUMENT_DEFINITION" + :union "UNION" + :field-definition "FIELD_DEFINITION" + :object "OBJECT" + :schema "SCHEMA"}) + +(defn edn-directive-defs->sdl-directives + [directive-defs] + (->> directive-defs + (map (fn [[directive-name {:keys [locations args]}]] + (str "directive @" + (name directive-name) + (edn-args->sdl-args args) + " on " + (->> locations + (map directive-targets) + (join " | "))))) + (join "\n"))) + +(defn generate-sdl + [schema] + (->> schema + (map (fn [[key val]] + (case key + :objects (edn-objects->sdl-objects val) + :queries (edn-queries->sdl-queries val) + :interfaces (edn-interfaces->sdl-interfaces val) + :scalars (edn-scalars->sdl-scalars val) + :unions (edn-unions->sdl-unions val) + :input-objects (edn-input-objects->sdl-input-objects val) + :mutations (edn-mutations->sdl-mutations val) + :enums (edn-enums->sdl-enums val) + :directive-defs (edn-directive-defs->sdl-directives val) + :roots ""))) + (join "\n"))) + (defn inject-federation "Called after SDL parsing to extend the input schema (not the compiled schema) with federation support." - [schema sdl entity-resolvers] - (let [entity-names (find-entity-names schema) - entities-resolver (entities-resolver-factory entity-names entity-resolvers) - query-root (get-nested schema [:roots :query] :Query)] - (prevent-collision schema [:unions :_Entity]) - (prevent-collision schema [:objects query-root :fields :_service]) - (prevent-collision schema [:objects query-root :fields :_entities]) - (cond-> (assoc-in schema [:objects query-root :fields :_service] - {:type '(non-null :_Service) - :resolve (fn [_ _ _] {:sdl sdl})}) - entity-names (-> (assoc-in [:unions :_Entity :members] entity-names) - (assoc-in [:objects query-root :fields :_entities] - {:type '(non-null (list :_Entity)) - :args - {:representations - {:type '(non-null (list (non-null :_Any)))}} - :resolve entities-resolver}))))) + ([schema entity-resolvers] + (inject-federation schema (generate-sdl schema) entity-resolvers)) + ([schema sdl entity-resolvers] + (let [entity-names (find-entity-names schema) + entities-resolver (entities-resolver-factory entity-names entity-resolvers) + query-root (get-nested schema [:roots :query] :Query)] + (prevent-collision schema [:unions :_Entity]) + (prevent-collision schema [:objects query-root :fields :_service]) + (prevent-collision schema [:objects query-root :fields :_entities]) + (cond-> (assoc-in schema [:objects query-root :fields :_service] + {:type '(non-null :_Service) + :resolve (fn [_ _ _] {:sdl sdl})}) + entity-names (-> (assoc-in [:unions :_Entity :members] entity-names) + (assoc-in [:objects query-root :fields :_entities] + {:type '(non-null (list :_Entity)) + :args + {:representations + {:type '(non-null (list (non-null :_Any)))}} + :resolve entities-resolver})))))) (s/def ::entity-resolvers (s/map-of simple-keyword? ::schema/resolve)) diff --git a/test/com/walmartlabs/lacinia/federation_tests.clj b/test/com/walmartlabs/lacinia/federation_tests.clj index fdacbc3f..303411e4 100644 --- a/test/com/walmartlabs/lacinia/federation_tests.clj +++ b/test/com/walmartlabs/lacinia/federation_tests.clj @@ -14,13 +14,15 @@ (ns com.walmartlabs.lacinia.federation-tests (:require - [clojure.test :refer [deftest is]] + [clojure.test :refer [deftest is run-tests]] + [clojure.string :refer [trim]] [com.walmartlabs.lacinia.parser.schema :refer [parse-schema]] [com.walmartlabs.lacinia.resolve :refer [FieldResolver resolve-as]] [com.walmartlabs.lacinia.util :as util] [com.walmartlabs.test-utils :refer [execute]] [com.walmartlabs.test-reporting :refer [reporting]] - [com.walmartlabs.lacinia.schema :as schema])) + [com.walmartlabs.lacinia.schema :as schema] + [com.walmartlabs.lacinia.federation :refer [inject-federation generate-sdl]])) (defn ^:private resolve-user [_ {:keys [id]} _] @@ -265,3 +267,81 @@ query($reps : [_Any!]!) { (is (contains? field-names "_service")) (is (not (contains? field-names "_entities"))) (is (= #{"Stuff"} union-names))))) + +(deftest edn-schema->sdl-schema + (let [sample-schema '{:objects + {:Query + {:fields + {:todo + {:type :Todo :description "Get one todo item" :args + {:id + {:type (non-null ID)}}} :allTodos + {:type (non-null (list (non-null :Todo))) :description "List of all todo items"}}} :Mutation + {:fields + {:addTodo + {:type (non-null :Todo) :args + {:name + {:type (non-null String) :description "Name for the todo item"} :priority + {:type :Priority :description "Priority level of todo item" :default-value :LOW}}} :removeTodo + {:type (non-null :Todo) :args + {:id + {:type (non-null ID)}}}}} :Todo + {:fields + {:id + {:type (non-null ID)} :name + {:type (non-null String)} :description + {:type String :description "Useful description for todo item"} :priority + {:type (non-null :Priority)}}}} + :enums + {:Priority + {:values [{:enum-value :LOW} + {:enum-value :MEDIUM} + {:enum-value :HIGH}]}} + :unions + {:_Entity + {:members [:Todo]}} :scalars + {:FieldSet + {}} + :directive-defs + {:key + {:locations #{:interface :object} :args + {:fields + {:type (non-null :FieldSet)} :resolvable + {:type Boolean :default-value true}}} :external + {:locations #{:field-definition}}}}] + (is (= (-> sample-schema generate-sdl parse-schema) sample-schema)))) + +(deftest only-edn-schama-essential + (let [edn (-> "dev-resources/edn-federation.edn" slurp read-string) + sdl (-> "dev-resources/edn-federation.sdl" slurp trim) + schema (-> edn + (inject-federation {:User always-nil + :Account always-nil + :Product always-nil}) + (util/inject-resolvers {:Query/user_by_id resolve-user}) + (util/attach-scalar-transformers {:_Any/parser identity + :_Any/serializer identity + :_FieldSet/parser identity + :_FieldSet/serializer identity + :link__Import/parser identity + :link__Import/serializer identity}) + schema/compile)] + (is (= {:data {:_service {:sdl sdl}}} + (execute schema + "{ _service { sdl }}"))) + + (is (= {:data {:entities {:members [{:name "Account"} + {:name "Product"} + {:name "User"}] + :name "_Entity"}}} + (execute schema + "{ entities: __type(name: \"_Entity\") { name members: possibleTypes { name }}}"))) + + (is (= {:data {:user_by_id {:id 9998 + :name "User #9998"}}} + (execute schema + "{ user_by_id(id: 9998) { id name }}"))))) + +(comment + (run-tests) + ) From c20b1e41a8ba0f3bdb4b67e7e66571fa1812f7a9 Mon Sep 17 00:00:00 2001 From: myHan Date: Mon, 8 Aug 2022 23:30:26 +0900 Subject: [PATCH 02/18] minor fix --- src/com/walmartlabs/lacinia/federation.clj | 42 ++++++++++--------- .../walmartlabs/lacinia/federation_tests.clj | 6 +-- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index ac5a5de0..ab0ed709 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -121,19 +121,19 @@ (ru/aggregate-results results #(maybe-wrap (reduce into [] %)))))))) -(defn apply-list +(defn ^:private apply-list [f x] (if (-> x first seq?) (apply f x) (f x))) -(defn edn-description->sdl-description +(defn ^:private edn-description->sdl-description [description] (if (nil? description) "" (str "\"\"\"\n" description "\n\"\"\"\n"))) -(defn edn-type->sdl-type +(defn ^:private edn-type->sdl-type [type] (if (seq? type) (let [[hd & tl] type] @@ -150,32 +150,32 @@ (scalar :guard symbol?) (name scalar))) (recur (list type)))) -(defn value->string +(defn ^:private value->string [value] (match value (string :guard string?) (str "\"" string "\"") (keyword :guard keyword?) (name keyword) else (str else))) -(defn edn-default-value->sdl-default-value +(defn ^:private edn-default-value->sdl-default-value [default-value] (if (nil? default-value) "" (str " = " (value->string default-value)))) -(defn- edn-arg-descrption->sdl-arg-description +(defn ^:private edn-arg-descrption->sdl-arg-description [description] (if (nil? description) "" (str "\"" description "\" "))) -(defn edn-args->sdl-args +(defn ^:private edn-args->sdl-args [args] (if (nil? args) "" (str "(" (join ", " (map (fn [[arg-name {:keys [type default-value description]}]] (str (edn-arg-descrption->sdl-arg-description description) (name arg-name) ": " (edn-type->sdl-type type) (edn-default-value->sdl-default-value default-value))) args)) ")"))) -(defn edn-directive-args->sdl-directive-args +(defn ^:private edn-directive-args->sdl-directive-args [directive-args] (if (nil? directive-args) "" @@ -183,7 +183,7 @@ (map (fn [[arg-name arg-value]] (str (name arg-name) ": " (value->string arg-value)))) (join ", ")) ")"))) -(defn edn-directives->sdl-directives +(defn ^:private edn-directives->sdl-directives [directives] (if (nil? directives) "" @@ -193,7 +193,7 @@ (str "@" (name directive-type) (edn-directive-args->sdl-directive-args directive-args)))) (join " ")) " "))) -(defn edn-fields->sdl-fields +(defn ^:private edn-fields->sdl-fields [fields] (str "{\n" @@ -203,7 +203,7 @@ (join "\n")) "\n}")) -(defn edn-objects->sdl-objects +(defn ^:private edn-objects->sdl-objects [objects] (->> objects (map (fn [[key {:keys [fields directives description]}]] @@ -213,11 +213,11 @@ (edn-directives->sdl-directives directives) (edn-fields->sdl-fields fields)))) (join "\n"))) -(defn edn-queries->sdl-queries +(defn ^:private edn-queries->sdl-queries [queries] (str (-> queries :description edn-description->sdl-description) "type Query " (edn-fields->sdl-fields queries))) -(defn edn-interfaces->sdl-interfaces +(defn ^:private edn-interfaces->sdl-interfaces [interfaces] (->> interfaces (map (fn [[key val]] @@ -225,7 +225,7 @@ (name key) (-> val :fields edn-fields->sdl-fields)))) (join "\n"))) -(defn edn-input-objects->sdl-input-objects +(defn ^:private edn-input-objects->sdl-input-objects [input-objects] (->> input-objects (map (fn [[key val]] @@ -233,7 +233,7 @@ (name key) (-> val :fields edn-fields->sdl-fields)))) (join "\n"))) -(defn edn-unions->sdl-unions +(defn ^:private edn-unions->sdl-unions [unions] (->> unions (map (fn [[union-name {members :members}]] @@ -241,16 +241,16 @@ (map name) (join " | "))))) (join "\n"))) -(defn edn-mutations->sdl-mutations +(defn ^:private edn-mutations->sdl-mutations [mutations] (str "type Mutation " (edn-fields->sdl-fields mutations))) -(defn edn-enums->sdl-enums +(defn ^:private edn-enums->sdl-enums [enums] (->> enums (map (fn [[enum-name {values :values}]] (str "enum " (name enum-name) "{\n" (->> values (map :enum-value) (map name) (join "\n")) "\n}"))) (join "\n"))) -(defn edn-scalars->sdl-scalars +(defn ^:private edn-scalars->sdl-scalars [scalars] (->> (keys scalars) (map name) @@ -270,7 +270,7 @@ :object "OBJECT" :schema "SCHEMA"}) -(defn edn-directive-defs->sdl-directives +(defn ^:private edn-directive-defs->sdl-directives [directive-defs] (->> directive-defs (map (fn [[directive-name {:keys [locations args]}]] @@ -284,6 +284,7 @@ (join "\n"))) (defn generate-sdl + "Translate the edn lacinia schema to the SDL schema." [schema] (->> schema (map (fn [[key val]] @@ -302,7 +303,8 @@ (defn inject-federation "Called after SDL parsing to extend the input schema - (not the compiled schema) with federation support." + (not the compiled schema) with federation support. + If the SDL string is not given, it is automatically created through the schema." ([schema entity-resolvers] (inject-federation schema (generate-sdl schema) entity-resolvers)) ([schema sdl entity-resolvers] diff --git a/test/com/walmartlabs/lacinia/federation_tests.clj b/test/com/walmartlabs/lacinia/federation_tests.clj index 303411e4..85db82d1 100644 --- a/test/com/walmartlabs/lacinia/federation_tests.clj +++ b/test/com/walmartlabs/lacinia/federation_tests.clj @@ -14,7 +14,7 @@ (ns com.walmartlabs.lacinia.federation-tests (:require - [clojure.test :refer [deftest is run-tests]] + [clojure.test :refer [deftest is]] [clojure.string :refer [trim]] [com.walmartlabs.lacinia.parser.schema :refer [parse-schema]] [com.walmartlabs.lacinia.resolve :refer [FieldResolver resolve-as]] @@ -341,7 +341,3 @@ query($reps : [_Any!]!) { :name "User #9998"}}} (execute schema "{ user_by_id(id: 9998) { id name }}"))))) - -(comment - (run-tests) - ) From d1dadc61ed6c6488dff024775395bd21ec35165f Mon Sep 17 00:00:00 2001 From: myHan Date: Mon, 8 Aug 2022 23:30:40 +0900 Subject: [PATCH 03/18] update document --- docs/federation/index.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/federation/index.rst b/docs/federation/index.rst index 7049b350..5f39d466 100644 --- a/docs/federation/index.rst +++ b/docs/federation/index.rst @@ -14,11 +14,6 @@ service-spanning queries apart and build an overall query plan. Lacinia has been extended, starting in 0.38.0, to support acting as an implementing service; there is no plan at this time to act as a gateway. -.. warning:: - - At this time, only a schema defined with the :doc:`Schema Definition Language `, can be extended to act as - a service implementation. - Essentially, federation allows a set of services to each provide their own types, queries, and mutations, and organizes things so that each service can provide additional fields to the types provided by the other services. From 331483b058f2ddaadacd174f7333a5ad1693ae07 Mon Sep 17 00:00:00 2001 From: myHan Date: Thu, 11 Aug 2022 12:15:05 +0900 Subject: [PATCH 04/18] Add missing enum translate logic --- src/com/walmartlabs/lacinia/federation.clj | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index ab0ed709..733d43b3 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -244,11 +244,18 @@ (defn ^:private edn-mutations->sdl-mutations [mutations] (str "type Mutation " (edn-fields->sdl-fields mutations))) + +(defn ^:private edn-enum-value->sdl-enum-value + [enum-value] + (match enum-value + {:enum-value value} value + (value :guard keyword?) value)) + (defn ^:private edn-enums->sdl-enums [enums] (->> enums (map (fn [[enum-name {values :values}]] - (str "enum " (name enum-name) "{\n" (->> values (map :enum-value) (map name) (join "\n")) "\n}"))) + (str "enum " (name enum-name) "{\n" (->> values (map edn-enum-value->sdl-enum-value) (map name) (join "\n")) "\n}"))) (join "\n"))) (defn ^:private edn-scalars->sdl-scalars [scalars] From e940cc69818934535847d36cb3d14083b9365603 Mon Sep 17 00:00:00 2001 From: myHan Date: Tue, 16 Aug 2022 11:07:27 +0900 Subject: [PATCH 05/18] add implements translate --- src/com/walmartlabs/lacinia/federation.clj | 12 +++- .../walmartlabs/lacinia/federation_tests.clj | 56 +++++++++++++------ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 733d43b3..dd03b33d 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -203,16 +203,26 @@ (join "\n")) "\n}")) +(defn ^:private edn-implements->sdl-implements + [implements] + (if (seq implements) + (str " implements " (->> implements + (map name) + (join " & "))) + "")) + (defn ^:private edn-objects->sdl-objects [objects] (->> objects - (map (fn [[key {:keys [fields directives description]}]] + (map (fn [[key {:keys [fields directives description implements]}]] (str (edn-description->sdl-description description) "type " (name key) + (edn-implements->sdl-implements implements) (edn-directives->sdl-directives directives) (edn-fields->sdl-fields fields)))) (join "\n"))) + (defn ^:private edn-queries->sdl-queries [queries] (str (-> queries :description edn-description->sdl-description) "type Query " (edn-fields->sdl-fields queries))) diff --git a/test/com/walmartlabs/lacinia/federation_tests.clj b/test/com/walmartlabs/lacinia/federation_tests.clj index 85db82d1..baa6d21d 100644 --- a/test/com/walmartlabs/lacinia/federation_tests.clj +++ b/test/com/walmartlabs/lacinia/federation_tests.clj @@ -269,28 +269,46 @@ query($reps : [_Any!]!) { (is (= #{"Stuff"} union-names))))) (deftest edn-schema->sdl-schema - (let [sample-schema '{:objects + (let [sample-schema '{:interfaces + {:Node + {:fields + {:id + {:type (non-null ID)}}}} + :objects {:Query {:fields {:todo - {:type :Todo :description "Get one todo item" :args + {:type :Todo + :description "Get one todo item" + :args {:id - {:type (non-null ID)}}} :allTodos - {:type (non-null (list (non-null :Todo))) :description "List of all todo items"}}} :Mutation + {:type (non-null ID)}}} + :allTodos + {:type (non-null (list (non-null :Todo))) :description "List of all todo items"}}} + :Mutation {:fields {:addTodo - {:type (non-null :Todo) :args + {:type (non-null :Todo) + :args {:name - {:type (non-null String) :description "Name for the todo item"} :priority - {:type :Priority :description "Priority level of todo item" :default-value :LOW}}} :removeTodo - {:type (non-null :Todo) :args + {:type (non-null String) :description "Name for the todo item"} + :priority + {:type :Priority :description "Priority level of todo item" :default-value :LOW}}} + :removeTodo + {:type (non-null :Todo) + :args {:id - {:type (non-null ID)}}}}} :Todo - {:fields + {:type (non-null ID)}}}}} + :Todo + {:implements [:Node] + :fields {:id - {:type (non-null ID)} :name - {:type (non-null String)} :description - {:type String :description "Useful description for todo item"} :priority + {:type (non-null ID)} + :name + {:type (non-null String)} + :description + {:type String :description "Useful description for todo item"} + :priority {:type (non-null :Priority)}}}} :enums {:Priority @@ -299,15 +317,19 @@ query($reps : [_Any!]!) { {:enum-value :HIGH}]}} :unions {:_Entity - {:members [:Todo]}} :scalars + {:members [:Todo]}} + :scalars {:FieldSet {}} :directive-defs {:key - {:locations #{:interface :object} :args + {:locations #{:interface :object} + :args {:fields - {:type (non-null :FieldSet)} :resolvable - {:type Boolean :default-value true}}} :external + {:type (non-null :FieldSet)} + :resolvable + {:type Boolean :default-value true}}} + :external {:locations #{:field-definition}}}}] (is (= (-> sample-schema generate-sdl parse-schema) sample-schema)))) From e43ae523c548be93aed64296c2d7d017477cd567 Mon Sep 17 00:00:00 2001 From: myHan Date: Tue, 16 Aug 2022 11:16:41 +0900 Subject: [PATCH 06/18] remove match --- deps.edn | 3 +- src/com/walmartlabs/lacinia/federation.clj | 39 +++++++++++----------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/deps.edn b/deps.edn index 1fde65f4..1e331c69 100644 --- a/deps.edn +++ b/deps.edn @@ -1,8 +1,7 @@ {:deps {org.clojure/clojure {:mvn/version "1.10.3"} clj-antlr/clj-antlr {:mvn/version "0.2.12"} org.flatland/ordered {:mvn/version "1.15.10"} - org.clojure/data.json {:mvn/version "2.4.0"} - org.clojure/core.match {:mvn/version "1.0.0"}} + org.clojure/data.json {:mvn/version "2.4.0"}} :paths ["src" "resources"] :aliases {:dev diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index dd03b33d..8f272005 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -19,8 +19,7 @@ [com.walmartlabs.lacinia.resolve-utils :as ru] [com.walmartlabs.lacinia.schema :as schema] [clojure.spec.alpha :as s] - [clojure.string :refer [join]] - [clojure.core.match :refer [match]])) + [clojure.string :refer [join]])) (def foundation-types "Map of annotations and types to automatically include into an SDL @@ -137,25 +136,25 @@ [type] (if (seq? type) (let [[hd & tl] type] - (match hd - nil "" - 'non-null (str (apply-list edn-type->sdl-type tl) "!") - 'list (str "[" (apply-list edn-type->sdl-type tl) "]") - 'String "String" - 'Int "Int" - 'Float "Float" - 'Boolean "Boolean" - 'ID "ID" - (object :guard keyword?) (name object) - (scalar :guard symbol?) (name scalar))) + (cond + (nil? hd) "" + (= 'non-null hd) (str (apply-list edn-type->sdl-type tl) "!") + (= 'list hd) (str "[" (apply-list edn-type->sdl-type tl) "]") + (= 'String hd) "String" + (= 'Int hd) "Int" + (= 'Float hd) "Float" + (= 'Boolean hd) "Boolean" + (= 'ID hd) "ID" + (keyword? hd) (name hd) + (symbol? hd) (name hd))) (recur (list type)))) (defn ^:private value->string [value] - (match value - (string :guard string?) (str "\"" string "\"") - (keyword :guard keyword?) (name keyword) - else (str else))) + (cond + (string? value) (str "\"" value "\"") + (keyword? value) (name value) + :else (str value))) (defn ^:private edn-default-value->sdl-default-value [default-value] @@ -257,9 +256,9 @@ (defn ^:private edn-enum-value->sdl-enum-value [enum-value] - (match enum-value - {:enum-value value} value - (value :guard keyword?) value)) + (cond + (keyword? enum-value) enum-value + :else (:enum-value enum-value))) (defn ^:private edn-enums->sdl-enums [enums] From edf544267bf17d83060056ce35f1ca45f437f948 Mon Sep 17 00:00:00 2001 From: myHan Date: Tue, 16 Aug 2022 11:21:43 +0900 Subject: [PATCH 07/18] update test for string escape --- test/com/walmartlabs/lacinia/federation_tests.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/com/walmartlabs/lacinia/federation_tests.clj b/test/com/walmartlabs/lacinia/federation_tests.clj index baa6d21d..1d4a443b 100644 --- a/test/com/walmartlabs/lacinia/federation_tests.clj +++ b/test/com/walmartlabs/lacinia/federation_tests.clj @@ -279,7 +279,7 @@ query($reps : [_Any!]!) { {:fields {:todo {:type :Todo - :description "Get one todo item" + :description "\"Get one todo item\"" :args {:id {:type (non-null ID)}}} From 0810db59c5d86dda17f7dd272f195d15cd599cb4 Mon Sep 17 00:00:00 2001 From: myHan Date: Tue, 16 Aug 2022 11:25:55 +0900 Subject: [PATCH 08/18] add newline between functions --- src/com/walmartlabs/lacinia/federation.clj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 8f272005..25205884 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -234,6 +234,7 @@ (name key) (-> val :fields edn-fields->sdl-fields)))) (join "\n"))) + (defn ^:private edn-input-objects->sdl-input-objects [input-objects] (->> input-objects @@ -242,6 +243,7 @@ (name key) (-> val :fields edn-fields->sdl-fields)))) (join "\n"))) + (defn ^:private edn-unions->sdl-unions [unions] (->> unions @@ -250,6 +252,7 @@ (map name) (join " | "))))) (join "\n"))) + (defn ^:private edn-mutations->sdl-mutations [mutations] (str "type Mutation " (edn-fields->sdl-fields mutations))) @@ -266,6 +269,7 @@ (map (fn [[enum-name {values :values}]] (str "enum " (name enum-name) "{\n" (->> values (map edn-enum-value->sdl-enum-value) (map name) (join "\n")) "\n}"))) (join "\n"))) + (defn ^:private edn-scalars->sdl-scalars [scalars] (->> (keys scalars) From 143106264fff1961304501e5fdef15417d5c7ef8 Mon Sep 17 00:00:00 2001 From: myHan Date: Tue, 16 Aug 2022 11:40:17 +0900 Subject: [PATCH 09/18] sort root categories and scalars --- src/com/walmartlabs/lacinia/federation.clj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 25205884..665f37b8 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -274,6 +274,7 @@ [scalars] (->> (keys scalars) (map name) + sort (map #(str "scalar " %)) (join "\n"))) @@ -307,6 +308,15 @@ "Translate the edn lacinia schema to the SDL schema." [schema] (->> schema + (sort-by {:directive-defs 0 + :scalars 1 + :enums 2 + :unions 3 + :interfaces 4 + :input-objects 5 + :queries 6 + :mutations 7 + :objects 8}) (map (fn [[key val]] (case key :objects (edn-objects->sdl-objects val) From a9c70fe0b0cfa28dfc7952d27a33263d955ba401 Mon Sep 17 00:00:00 2001 From: myHan Date: Wed, 17 Aug 2022 11:15:04 +0900 Subject: [PATCH 10/18] fold queries, mutations, subscriptions in objects --- src/com/walmartlabs/lacinia/federation.clj | 29 +++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 665f37b8..6db5d473 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -304,31 +304,48 @@ (join " | "))))) (join "\n"))) +(defn ^:private fold-queries + [{:keys [queries] :as schema}] + (cond + (map? queries) (update-in schema [:objects :Query :fields] merge queries) + :else schema)) + +(defn ^:private fold-mutations + [{:keys [mutations] :as schema}] + (cond + (map? mutations) (update-in schema [:objects :Mutation :fields] merge mutations) + :else schema)) + +(defn ^:private fold-subscriptions + [{:keys [subscriptions] :as schema}] + (cond + (map? subscriptions) (update-in schema [:objects :Subscription :fields] merge subscriptions) + :else schema)) + (defn generate-sdl "Translate the edn lacinia schema to the SDL schema." [schema] (->> schema + fold-queries + fold-mutations + fold-subscriptions (sort-by {:directive-defs 0 :scalars 1 :enums 2 :unions 3 :interfaces 4 :input-objects 5 - :queries 6 - :mutations 7 - :objects 8}) + :objects 6}) (map (fn [[key val]] (case key :objects (edn-objects->sdl-objects val) - :queries (edn-queries->sdl-queries val) :interfaces (edn-interfaces->sdl-interfaces val) :scalars (edn-scalars->sdl-scalars val) :unions (edn-unions->sdl-unions val) :input-objects (edn-input-objects->sdl-input-objects val) - :mutations (edn-mutations->sdl-mutations val) :enums (edn-enums->sdl-enums val) :directive-defs (edn-directive-defs->sdl-directives val) - :roots ""))) + ""))) (join "\n"))) (defn inject-federation From f5d11f8e08ac74c83ef26b936595d590e9d227e3 Mon Sep 17 00:00:00 2001 From: myHan Date: Wed, 17 Aug 2022 11:17:33 +0900 Subject: [PATCH 11/18] remove unused functions --- src/com/walmartlabs/lacinia/federation.clj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 6db5d473..1c027d66 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -222,10 +222,6 @@ (edn-fields->sdl-fields fields)))) (join "\n"))) -(defn ^:private edn-queries->sdl-queries - [queries] - (str (-> queries :description edn-description->sdl-description) "type Query " (edn-fields->sdl-fields queries))) - (defn ^:private edn-interfaces->sdl-interfaces [interfaces] (->> interfaces @@ -253,10 +249,6 @@ (join " | "))))) (join "\n"))) -(defn ^:private edn-mutations->sdl-mutations - [mutations] - (str "type Mutation " (edn-fields->sdl-fields mutations))) - (defn ^:private edn-enum-value->sdl-enum-value [enum-value] (cond From 2f6c9b69739c9808eb691e67317f41ad56ba070a Mon Sep 17 00:00:00 2001 From: myHan Date: Wed, 17 Aug 2022 11:30:53 +0900 Subject: [PATCH 12/18] fix schema sort bug --- dev-resources/edn-federation.sdl | 32 +++++++++++----------- src/com/walmartlabs/lacinia/federation.clj | 14 +++++----- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/dev-resources/edn-federation.sdl b/dev-resources/edn-federation.sdl index 8faed9aa..38f3b9ac 100644 --- a/dev-resources/edn-federation.sdl +++ b/dev-resources/edn-federation.sdl @@ -1,3 +1,19 @@ +directive @extends on INTERFACE | OBJECT +directive @key(fields: _FieldSet!, resolvable: Boolean = true) on INTERFACE | OBJECT +directive @external on FIELD_DEFINITION +directive @shareable on FIELD_DEFINITION | OBJECT +directive @requires(fields: _FieldSet!) on FIELD_DEFINITION +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) on SCHEMA +directive @provides(fields: _FieldSet!) on FIELD_DEFINITION +directive @override(from: String!) on FIELD_DEFINITION +directive @inaccessible on ENUM | INPUT_FIELD_DEFINITION | INTERFACE | INPUT_OBJECT | ENUM_VALUE | SCALAR | ARGUMENT_DEFINITION | UNION | FIELD_DEFINITION | OBJECT +scalar _Any +scalar _FieldSet +scalar link__Import +enum link__Purpose{ +SECURITY +EXECUTION +} type _Service{ sdl: String! } @@ -16,19 +32,3 @@ type Product @key(fields: "upc") @extends { upc: String! reviewed_by: User } -scalar _Any -scalar _FieldSet -scalar link__Import -enum link__Purpose{ -SECURITY -EXECUTION -} -directive @extends on INTERFACE | OBJECT -directive @key(fields: _FieldSet!, resolvable: Boolean = true) on INTERFACE | OBJECT -directive @external on FIELD_DEFINITION -directive @shareable on FIELD_DEFINITION | OBJECT -directive @requires(fields: _FieldSet!) on FIELD_DEFINITION -directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) on SCHEMA -directive @provides(fields: _FieldSet!) on FIELD_DEFINITION -directive @override(from: String!) on FIELD_DEFINITION -directive @inaccessible on ENUM | INPUT_FIELD_DEFINITION | INTERFACE | INPUT_OBJECT | ENUM_VALUE | SCALAR | ARGUMENT_DEFINITION | UNION | FIELD_DEFINITION | OBJECT diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 1c027d66..e982e405 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -321,13 +321,13 @@ fold-queries fold-mutations fold-subscriptions - (sort-by {:directive-defs 0 - :scalars 1 - :enums 2 - :unions 3 - :interfaces 4 - :input-objects 5 - :objects 6}) + (sort-by #(-> % first {:directive-defs 0 + :scalars 1 + :enums 2 + :unions 3 + :interfaces 4 + :input-objects 5 + :objects 6})) (map (fn [[key val]] (case key :objects (edn-objects->sdl-objects val) From 7e08e795ea169607c8ddc5b963b3e7d008bb2c6b Mon Sep 17 00:00:00 2001 From: myHan Date: Wed, 17 Aug 2022 11:47:35 +0900 Subject: [PATCH 13/18] indent sdl --- dev-resources/edn-federation.sdl | 20 ++++++++++---------- src/com/walmartlabs/lacinia/federation.clj | 11 +++++++++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/dev-resources/edn-federation.sdl b/dev-resources/edn-federation.sdl index 38f3b9ac..79d715ff 100644 --- a/dev-resources/edn-federation.sdl +++ b/dev-resources/edn-federation.sdl @@ -11,24 +11,24 @@ scalar _Any scalar _FieldSet scalar link__Import enum link__Purpose{ -SECURITY -EXECUTION + SECURITY + EXECUTION } type _Service{ -sdl: String! + sdl: String! } type User @key(fields: "id") { -id: Int! -name: String! + id: Int! + name: String! } type Query{ -user_by_id(id: Int!): User + user_by_id(id: Int!): User } type Account @key(fields: "acct_number") { -acct_number: String! -name: String! + acct_number: String! + name: String! } type Product @key(fields: "upc") @extends { -upc: String! -reviewed_by: User + upc: String! + reviewed_by: User } diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index e982e405..0936679c 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -126,6 +126,12 @@ (apply f x) (f x))) +(defn ^:private indent + [s] + (cond + (clojure.string/blank? s) "" + :else (str " " (clojure.string/replace s #"\n" "\n ")))) + (defn ^:private edn-description->sdl-description [description] (if (nil? description) @@ -199,7 +205,8 @@ (->> fields (map (fn [[field-name {:keys [type args description]}]] (str (edn-description->sdl-description description) (name field-name) (edn-args->sdl-args args) ": " (edn-type->sdl-type type)))) - (join "\n")) + (join "\n") + indent) "\n}")) (defn ^:private edn-implements->sdl-implements @@ -259,7 +266,7 @@ [enums] (->> enums (map (fn [[enum-name {values :values}]] - (str "enum " (name enum-name) "{\n" (->> values (map edn-enum-value->sdl-enum-value) (map name) (join "\n")) "\n}"))) + (str "enum " (name enum-name) "{\n" (->> values (map edn-enum-value->sdl-enum-value) (map name) (join "\n") indent) "\n}"))) (join "\n"))) (defn ^:private edn-scalars->sdl-scalars From 04ecbcfbb6e00fbf94d06d7daa69d295c1816cad Mon Sep 17 00:00:00 2001 From: myHan Date: Wed, 17 Aug 2022 11:51:53 +0900 Subject: [PATCH 14/18] Add blank line between schema types --- dev-resources/edn-federation.sdl | 3 +++ src/com/walmartlabs/lacinia/federation.clj | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dev-resources/edn-federation.sdl b/dev-resources/edn-federation.sdl index 79d715ff..50b1940e 100644 --- a/dev-resources/edn-federation.sdl +++ b/dev-resources/edn-federation.sdl @@ -7,13 +7,16 @@ directive @link(url: String, as: String, for: link__Purpose, import: [link__Impo directive @provides(fields: _FieldSet!) on FIELD_DEFINITION directive @override(from: String!) on FIELD_DEFINITION directive @inaccessible on ENUM | INPUT_FIELD_DEFINITION | INTERFACE | INPUT_OBJECT | ENUM_VALUE | SCALAR | ARGUMENT_DEFINITION | UNION | FIELD_DEFINITION | OBJECT + scalar _Any scalar _FieldSet scalar link__Import + enum link__Purpose{ SECURITY EXECUTION } + type _Service{ sdl: String! } diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 0936679c..b86ce8d0 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -345,7 +345,7 @@ :enums (edn-enums->sdl-enums val) :directive-defs (edn-directive-defs->sdl-directives val) ""))) - (join "\n"))) + (join "\n\n"))) (defn inject-federation "Called after SDL parsing to extend the input schema From 15c844e3da4b45185534679bc896a75d54ef5267 Mon Sep 17 00:00:00 2001 From: myHan Date: Sun, 21 Aug 2022 15:29:59 +0900 Subject: [PATCH 15/18] Add roots->schema translation --- dev-resources/edn-federation.edn | 6 ++++- dev-resources/edn-federation.sdl | 6 +++++ src/com/walmartlabs/lacinia/federation.clj | 24 +++++++++++++------ .../walmartlabs/lacinia/federation_tests.clj | 4 +++- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/dev-resources/edn-federation.edn b/dev-resources/edn-federation.edn index f2283d82..8001688f 100644 --- a/dev-resources/edn-federation.edn +++ b/dev-resources/edn-federation.edn @@ -1,4 +1,8 @@ -{:objects +{:roots + {:query :Query + :mutation :Mutation + :subscription :Subscription} + :objects {:_Service {:fields {:sdl diff --git a/dev-resources/edn-federation.sdl b/dev-resources/edn-federation.sdl index 50b1940e..7274de15 100644 --- a/dev-resources/edn-federation.sdl +++ b/dev-resources/edn-federation.sdl @@ -1,3 +1,9 @@ +schema { + query: Query + mutation: Mutation + subscription: Subscription +} + directive @extends on INTERFACE | OBJECT directive @key(fields: _FieldSet!, resolvable: Boolean = true) on INTERFACE | OBJECT directive @external on FIELD_DEFINITION diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index b86ce8d0..57814616 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -303,6 +303,15 @@ (join " | "))))) (join "\n"))) +(defn ^:private edn-roots->sdl-schema + [{:keys [query mutation subscription]}] + (cond-> "schema {" + (some? query) (str "\n query: " (name query)) + (some? mutation) (str "\n mutation: " (name mutation)) + (some? subscription) (str "\n subscription: " (name subscription)) + true (str "\n}")) + ) + (defn ^:private fold-queries [{:keys [queries] :as schema}] (cond @@ -328,13 +337,13 @@ fold-queries fold-mutations fold-subscriptions - (sort-by #(-> % first {:directive-defs 0 - :scalars 1 - :enums 2 - :unions 3 - :interfaces 4 - :input-objects 5 - :objects 6})) + (sort-by #(-> % first {:directive-defs 1 + :scalars 2 + :enums 3 + :unions 4 + :interfaces 5 + :input-objects 6 + :objects 7})) (map (fn [[key val]] (case key :objects (edn-objects->sdl-objects val) @@ -344,6 +353,7 @@ :input-objects (edn-input-objects->sdl-input-objects val) :enums (edn-enums->sdl-enums val) :directive-defs (edn-directive-defs->sdl-directives val) + :roots (edn-roots->sdl-schema val) ""))) (join "\n\n"))) diff --git a/test/com/walmartlabs/lacinia/federation_tests.clj b/test/com/walmartlabs/lacinia/federation_tests.clj index 1d4a443b..3895fac0 100644 --- a/test/com/walmartlabs/lacinia/federation_tests.clj +++ b/test/com/walmartlabs/lacinia/federation_tests.clj @@ -269,7 +269,9 @@ query($reps : [_Any!]!) { (is (= #{"Stuff"} union-names))))) (deftest edn-schema->sdl-schema - (let [sample-schema '{:interfaces + (let [sample-schema '{:roots {:query :query + :mutation :mutation} + :interfaces {:Node {:fields {:id From 37885e52e6121b691fba28ad39c4b30f3e2d5199 Mon Sep 17 00:00:00 2001 From: myHan Date: Sun, 21 Aug 2022 16:52:07 +0900 Subject: [PATCH 16/18] Modify the fold function to match roots --- src/com/walmartlabs/lacinia/federation.clj | 24 +++++++++++-------- .../walmartlabs/lacinia/federation_tests.clj | 20 ++++++++++++---- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 57814616..b889991b 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -314,21 +314,24 @@ (defn ^:private fold-queries [{:keys [queries] :as schema}] - (cond - (map? queries) (update-in schema [:objects :Query :fields] merge queries) - :else schema)) + (let [query (get-in schema [:roots :query] :Query)] + (cond + (map? queries) (update-in schema [:objects query :fields] merge queries) + :else schema))) (defn ^:private fold-mutations [{:keys [mutations] :as schema}] - (cond - (map? mutations) (update-in schema [:objects :Mutation :fields] merge mutations) - :else schema)) + (let [mutation (get-in schema [:roots :mutation] :Mutation)] + (cond + (map? mutations) (update-in schema [:objects mutation :fields] merge mutations) + :else schema))) (defn ^:private fold-subscriptions [{:keys [subscriptions] :as schema}] - (cond - (map? subscriptions) (update-in schema [:objects :Subscription :fields] merge subscriptions) - :else schema)) + (let [subscription (get-in schema [:roots :subscription] :Subscription)] + (cond + (map? subscriptions) (update-in schema [:objects subscription :fields] merge subscriptions) + :else schema))) (defn generate-sdl "Translate the edn lacinia schema to the SDL schema." @@ -355,7 +358,8 @@ :directive-defs (edn-directive-defs->sdl-directives val) :roots (edn-roots->sdl-schema val) ""))) - (join "\n\n"))) + (join "\n\n") + clojure.string/trim)) (defn inject-federation "Called after SDL parsing to extend the input schema diff --git a/test/com/walmartlabs/lacinia/federation_tests.clj b/test/com/walmartlabs/lacinia/federation_tests.clj index 3895fac0..70d17c5d 100644 --- a/test/com/walmartlabs/lacinia/federation_tests.clj +++ b/test/com/walmartlabs/lacinia/federation_tests.clj @@ -269,15 +269,15 @@ query($reps : [_Any!]!) { (is (= #{"Stuff"} union-names))))) (deftest edn-schema->sdl-schema - (let [sample-schema '{:roots {:query :query - :mutation :mutation} + (let [sample-edn-1 '{:roots {:query :MyQuery + :mutation :Mutation} :interfaces {:Node {:fields {:id {:type (non-null ID)}}}} :objects - {:Query + {:MyQuery {:fields {:todo {:type :Todo @@ -332,8 +332,18 @@ query($reps : [_Any!]!) { :resolvable {:type Boolean :default-value true}}} :external - {:locations #{:field-definition}}}}] - (is (= (-> sample-schema generate-sdl parse-schema) sample-schema)))) + {:locations #{:field-definition}}}} + sample-edn-2 '{:queries + {:node + {:description "node query" + :type Node + :args {:id {:type (non-null ID)}}}} + :roots + {:query :CustomQuery}} + sample-sdl-2 "schema {\n query: CustomQuery\n}\n\ntype CustomQuery{\n \"\"\"\n node query\n \"\"\"\n node(id: ID!): Node\n}"] + + (is (= (-> sample-edn-1 generate-sdl parse-schema) sample-edn-1)) + (is (= (generate-sdl sample-edn-2) sample-sdl-2)))) (deftest only-edn-schama-essential (let [edn (-> "dev-resources/edn-federation.edn" slurp read-string) From eff986dbdb5436cb1b5dbcd5863f4f16fa8a08d2 Mon Sep 17 00:00:00 2001 From: myHan Date: Sun, 28 Aug 2022 11:20:28 +0900 Subject: [PATCH 17/18] Delete a useless cond clause --- src/com/walmartlabs/lacinia/federation.clj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index b889991b..6abaa550 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -146,11 +146,6 @@ (nil? hd) "" (= 'non-null hd) (str (apply-list edn-type->sdl-type tl) "!") (= 'list hd) (str "[" (apply-list edn-type->sdl-type tl) "]") - (= 'String hd) "String" - (= 'Int hd) "Int" - (= 'Float hd) "Float" - (= 'Boolean hd) "Boolean" - (= 'ID hd) "ID" (keyword? hd) (name hd) (symbol? hd) (name hd))) (recur (list type)))) From cfbe11508e686b4c8af27909a1301134a60f8f50 Mon Sep 17 00:00:00 2001 From: myHan Date: Mon, 5 Sep 2022 10:17:38 +0900 Subject: [PATCH 18/18] Add string escape and unescape --- src/com/walmartlabs/lacinia/federation.clj | 8 ++++---- src/com/walmartlabs/lacinia/parser/common.clj | 3 ++- test/com/walmartlabs/lacinia/federation_tests.clj | 5 +++-- test/com/walmartlabs/lacinia/parser/schema_test.clj | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/com/walmartlabs/lacinia/federation.clj b/src/com/walmartlabs/lacinia/federation.clj index 6abaa550..0c97b32d 100644 --- a/src/com/walmartlabs/lacinia/federation.clj +++ b/src/com/walmartlabs/lacinia/federation.clj @@ -19,7 +19,7 @@ [com.walmartlabs.lacinia.resolve-utils :as ru] [com.walmartlabs.lacinia.schema :as schema] [clojure.spec.alpha :as s] - [clojure.string :refer [join]])) + [clojure.string :refer [join escape]])) (def foundation-types "Map of annotations and types to automatically include into an SDL @@ -136,7 +136,7 @@ [description] (if (nil? description) "" - (str "\"\"\"\n" description "\n\"\"\"\n"))) + (str "\"\"\"\n" (escape description {\" "\\\""}) "\n\"\"\"\n"))) (defn ^:private edn-type->sdl-type [type] @@ -153,7 +153,7 @@ (defn ^:private value->string [value] (cond - (string? value) (str "\"" value "\"") + (string? value) (str "\"" (escape value {\" "\\\""}) "\"") (keyword? value) (name value) :else (str value))) @@ -167,7 +167,7 @@ [description] (if (nil? description) "" - (str "\"" description "\" "))) + (str "\"" (escape description {\" "\\\""}) "\" "))) (defn ^:private edn-args->sdl-args [args] diff --git a/src/com/walmartlabs/lacinia/parser/common.clj b/src/com/walmartlabs/lacinia/parser/common.clj index 06563e91..cc480310 100644 --- a/src/com/walmartlabs/lacinia/parser/common.clj +++ b/src/com/walmartlabs/lacinia/parser/common.clj @@ -156,7 +156,8 @@ (let [token-name* (token-name t p)] (when-not (ignored-terminal? token-name*) (list (keyword (str/lower-case token-name*)) - (.getText t)))))) + (cond-> (.getText t) + (#{"StringValue" "BlockStringValue"} token-name*) (clojure.string/replace #"\\\"" "\""))))))) (defn antlr-parse [grammar input-document] diff --git a/test/com/walmartlabs/lacinia/federation_tests.clj b/test/com/walmartlabs/lacinia/federation_tests.clj index 70d17c5d..71565ec5 100644 --- a/test/com/walmartlabs/lacinia/federation_tests.clj +++ b/test/com/walmartlabs/lacinia/federation_tests.clj @@ -281,10 +281,11 @@ query($reps : [_Any!]!) { {:fields {:todo {:type :Todo - :description "\"Get one todo item\"" + :description "\"\"\"Get one todo item\"" :args {:id - {:type (non-null ID)}}} + {:type (non-null ID) + :default-value "\"default-node-id"}}} :allTodos {:type (non-null (list (non-null :Todo))) :description "List of all todo items"}}} :Mutation diff --git a/test/com/walmartlabs/lacinia/parser/schema_test.clj b/test/com/walmartlabs/lacinia/parser/schema_test.clj index 2a3ddd12..845033c9 100644 --- a/test/com/walmartlabs/lacinia/parser/schema_test.clj +++ b/test/com/walmartlabs/lacinia/parser/schema_test.clj @@ -356,13 +356,13 @@ :weight {:type (non-null Int)} :imperial {:type Boolean :default-value false} - :category {:type String :default-value "feline"}}}}} + :category {:type String :default-value "\"feline\""}}}}} (parse-string "input Animal { name: String! keyword: String = null weight: Int! imperial: Boolean = false - category: String = \"feline\" + category: String = \"\\\"feline\\\"\" }")))) (deftest extend-input-object