├── .github └── workflows │ └── config.yml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── README.md ├── VERSION.txt ├── build.clj ├── deps.edn ├── dev-resources ├── StructureDefinition-us-core-pulse-oximetry.json ├── bad-resolver-enum.edn ├── bad_schema.sdl ├── basic-docs.md ├── basic-vars.edn ├── basic.edn ├── blockquote.sdl ├── com │ └── walmartlabs │ │ ├── test_schema.clj │ │ └── test_utils.clj ├── custom-scalar-serialize-schema.edn ├── deep-response.edn ├── deep.sdl ├── deprecated-enums-schema.edn ├── deprecated-fields-schema.edn ├── doc-inheritance-schema.edn ├── documented-schema.sdl ├── duplicate-type.sdl ├── dyn-args-schema.edn ├── dynamic-input-objects.edn ├── empty-input-objects.edn ├── enum-default-value-with-transformer-schema.edn ├── enum-parse-serialize.edn ├── enums.sdl ├── errors.edn ├── extensions-schema.edn ├── field-resolver-errors.edn ├── field-resolver-protocol-schema.edn ├── fixes-types-schema.edn ├── fragments-schema.edn ├── generic-scalar-errors-schema.edn ├── input-object-default-value-schema.edn ├── input-object-schema.edn ├── interfaces.sdl ├── introspection.edn ├── large-lists-schema.edn ├── large_lists.clj ├── list-default-value-schema.edn ├── lists-schema.edn ├── logback-test.xml ├── mult-inheritance.sdl ├── mutable-context-schema.edn ├── nested-non-nullable-fields-schema.edn ├── no-entities-federation.sdl ├── non-chatty-library-failures.edn ├── non-string-default-value-schema.edn ├── nullability.edn ├── object-scalars-schema.edn ├── opt-req-enum.edn ├── org │ └── example │ │ ├── db.clj │ │ └── schema.clj ├── parser │ ├── aliases.edn │ ├── aliases.gql │ ├── args.edn │ ├── args.gql │ ├── arrays.edn │ ├── arrays.gql │ ├── enum-reserved.edn │ ├── enum-reserved.gql │ ├── enum.edn │ ├── enum.gql │ ├── explicit-query.edn │ ├── explicit-query.gql │ ├── field-directive.edn │ ├── field-directive.gql │ ├── frag-spread.edn │ ├── frag-spread.gql │ ├── fragment-directives.edn │ ├── fragment-directives.gql │ ├── literals.edn │ ├── literals.gql │ ├── named-fragment.edn │ ├── named-fragment.gql │ ├── named-operation.edn │ ├── named-operation.gql │ ├── nested-fields.edn │ ├── nested-fields.gql │ ├── object.edn │ ├── object.gql │ ├── operation-directives.edn │ ├── operation-directives.gql │ ├── reserved-args.edn │ ├── reserved-args.gql │ ├── reserved-words.edn │ ├── reserved-words.gql │ ├── simple.edn │ ├── simple.gql │ ├── variable-defaults.edn │ ├── variable-defaults.gql │ ├── vars.edn │ └── vars.gql ├── perf.clj ├── preamble-docs.md ├── query-reserved.edn ├── root-object-schema.edn ├── root-object-with-conflicts-schema.edn ├── sample_schema.sdl ├── select-type-schema.edn ├── selection │ ├── argument-def-directive.sdl │ ├── directive-args.sdl │ ├── enum-types.sdl │ ├── interface-types.sdl │ ├── object-type.sdl │ ├── scalar-types.sdl │ ├── simple.sdl │ ├── union-types.sdl │ └── wrap-field-directive.sdl ├── selections-schema.edn ├── simple-federation.sdl ├── star-wars-schema.edn ├── subscription-selection.edn ├── subscriptions-schema.edn ├── summarize-schema.edn ├── timing-schema.edn ├── tracing_demo.clj ├── typename-schema.edn ├── union-query-root-schema.edn ├── unions.sdl ├── unknown-argument-type-schema.edn └── user.clj ├── docs ├── .gitignore ├── Makefile ├── _examples │ ├── async-example.edn │ ├── compile-schema.clj │ ├── custom-scalars.edn │ ├── directive-defs.edn │ ├── enum-definition-description.edn │ ├── enum-definition.edn │ ├── errors-result.edn │ ├── extension-result.edn │ ├── extension.edn │ ├── fed │ │ ├── external.gql │ │ ├── internal.gql │ │ ├── products.edn │ │ ├── query.gql │ │ └── schema.gql │ ├── hero-query-response.edn │ ├── interface-definition.edn │ ├── invoke-streamer.edn │ ├── mutable-context.edn │ ├── object-definition.edn │ ├── object-tag.edn │ ├── overview-exec-query.edn │ ├── parsed_sample_schema.edn │ ├── query-def-var.edn │ ├── query-def.edn │ ├── resolve-method.edn │ ├── sample_schema.txt │ ├── selections-tree.edn │ ├── selects-field.edn │ ├── star-wars-schema.edn │ ├── subs-schema.edn │ ├── subs-streamer.edn │ ├── tagged-resolver.edn │ ├── tracing.edn │ ├── tutorial │ │ ├── build-1.clj │ │ ├── cgg-data-1.edn │ │ ├── cgg-data-2.edn │ │ ├── cgg-data-3.edn │ │ ├── cgg-schema-1.edn │ │ ├── cgg-schema-2.edn │ │ ├── cgg-schema-3.edn │ │ ├── cgg-schema-4.edn │ │ ├── cgg-schema-5.edn │ │ ├── cgg-schema-6.edn │ │ ├── db-1.clj │ │ ├── db-2.clj │ │ ├── db-3.clj │ │ ├── db-4.clj │ │ ├── deps-1.edn │ │ ├── deps-2.edn │ │ ├── deps-3.edn │ │ ├── deps-4.edn │ │ ├── deps-5.edn │ │ ├── deps-6.edn │ │ ├── docker-compose-1.yml │ │ ├── logback-test-1.xml │ │ ├── logback-test-2.xml │ │ ├── schema-0.clj │ │ ├── schema-1.clj │ │ ├── schema-2.clj │ │ ├── schema-3.clj │ │ ├── schema-4.clj │ │ ├── schema-5.clj │ │ ├── schema-6.clj │ │ ├── server-1.clj │ │ ├── server-2.clj │ │ ├── setup-db-1.sh │ │ ├── system-1.clj │ │ ├── system-2.clj │ │ ├── system-3.clj │ │ ├── system_test-1.clj │ │ ├── test_utils-1.clj │ │ ├── user-1.clj │ │ ├── user-2.clj │ │ ├── user-3.clj │ │ └── user-4.clj │ ├── union-definition.edn │ └── unions-query-response.edn ├── _exts │ └── api_link.py ├── _static │ ├── css │ │ └── custom.css │ └── tutorial │ │ ├── deps.png │ │ ├── graphiql-basic-query.png │ │ ├── graphiql-doc-browser.png │ │ └── graphiql-initial.png ├── clojure.rst ├── conf.py ├── contributing.rst ├── custom-scalars.rst ├── deprecation.rst ├── directives.rst ├── enums.rst ├── federation │ ├── external.rst │ ├── implementation.rst │ ├── index.rst │ ├── internal.rst │ └── reps.rst ├── fields.rst ├── index.rst ├── input-objects.rst ├── interfaces.rst ├── introspection.rst ├── mutations.rst ├── objects.rst ├── overview.rst ├── queries.rst ├── resolve │ ├── async.rst │ ├── attach.rst │ ├── context.rst │ ├── examples.rst │ ├── exceptions.rst │ ├── extensions.rst │ ├── field-resolver-protocol.rst │ ├── index.rst │ ├── overview.rst │ ├── resolve-as.rst │ ├── selections.rst │ └── type-tags.rst ├── resources.rst ├── roots.rst ├── samples.rst ├── schema │ └── parsing.rst ├── spec.rst ├── subscriptions │ ├── index.rst │ ├── overview.rst │ ├── resolver.rst │ └── streamer.rst ├── tracing.rst ├── tutorial │ ├── component.rst │ ├── create-project.rst │ ├── database-1.rst │ ├── database-2.rst │ ├── designer-data.rst │ ├── domain.rst │ ├── game-data.rst │ ├── index.rst │ ├── init-schema.rst │ ├── member-ratings.rst │ ├── mutable-database.rst │ ├── pedestal.rst │ ├── prereqs.rst │ ├── rating-mutation.rst │ ├── testing-1.rst │ └── wrap-up.rst └── unions.rst ├── perf ├── .gitignore ├── benchmarks.csv └── create-charts.R ├── resources └── com │ └── walmartlabs │ └── lacinia │ ├── Graphql.g4 │ ├── introspection.edn │ └── schema.g4 ├── src └── com │ └── walmartlabs │ ├── lacinia.clj │ └── lacinia │ ├── constants.clj │ ├── describe.clj │ ├── executor.clj │ ├── expound.clj │ ├── federation.clj │ ├── internal_utils.clj │ ├── introspection.clj │ ├── parser.clj │ ├── parser │ ├── common.clj │ ├── docs.clj │ ├── query.clj │ └── schema.clj │ ├── resolve.clj │ ├── resolve_utils.clj │ ├── schema.clj │ ├── select_utils.clj │ ├── selection.clj │ ├── tracing.clj │ ├── util.clj │ ├── validation │ ├── fragments.clj │ ├── no_unused_fragments.clj │ └── scalar_leafs.clj │ └── validator.clj └── test └── com └── walmartlabs ├── input_objects_test.clj ├── interface_test.clj ├── introspection_test.clj ├── lacinia ├── arguments_test.clj ├── async_test.clj ├── custom_scalars_test.clj ├── directives_test.clj ├── documentation_test.clj ├── enums_test.clj ├── executor_test.clj ├── expound_tests.clj ├── extensions_test.clj ├── federation_tests.clj ├── field_tests.clj ├── fixed_types_test.clj ├── fragments_tests.clj ├── input_types_test.clj ├── interfaces_test.clj ├── internal_utils_tests.clj ├── invariant_test.clj ├── lists_tests.clj ├── merge_selections_test.clj ├── mutable_context_tests.clj ├── nested_list_tests.clj ├── object_scalars_test.clj ├── parser │ ├── docs_test.clj │ ├── query_test.clj │ ├── schema_test.clj │ └── selection_test.clj ├── query_ops_test.clj ├── resolve_bindings_test.clj ├── resolve_test.clj ├── resolver_errors_test.clj ├── resolver_result_test.clj ├── roots_test.clj ├── scalar_tests.clj ├── schema_test.clj ├── select_type_test.clj ├── selections_tests.clj ├── subscription_tests.clj ├── summarize_query_tests.clj ├── timeout_test.clj ├── tracing_test.clj ├── typename_test.clj ├── unions_test.clj ├── util_test.clj ├── validator_test.clj ├── variables_test.clj └── wrapped_values_in_lists_tests.clj └── lacinia_test.clj /.github/workflows/config.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3.4.0 15 | - name: Setup Java 16 | uses: actions/setup-java@v3.10.0 17 | with: 18 | java-version: '11' 19 | distribution: 'corretto' 20 | - name: Setup Clojure 21 | uses: DeLaGuardo/setup-clojure@10.2 22 | with: 23 | cli: 1.11.1.1165 24 | 25 | - name: Cache clojure dependencies 26 | uses: actions/cache@v3 27 | with: 28 | path: | 29 | ~/.m2/repository 30 | ~/.gitlibs 31 | ~/.deps.clj 32 | # List all files containing dependencies: 33 | key: cljdeps-${{ hashFiles('deps.edn') }} 34 | 35 | - name: Execute tests 36 | run: clojure -X:dev:test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | *.iml 13 | .idea 14 | _build 15 | .DS_Store 16 | build.xml 17 | .Rapp.history 18 | .settings 19 | .project 20 | .classpath 21 | *.pom.asc 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 WalmartLabs 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /VERSION.txt: -------------------------------------------------------------------------------- 1 | 1.2.2 2 | 3 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2021-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | ;; clj -T:build 16 | 17 | (ns build 18 | (:require [clojure.string :as string] 19 | [clojure.tools.build.api :as build] 20 | [net.lewisship.build :as b])) 21 | 22 | (def lib 'com.walmartlabs/lacinia) 23 | (def version (-> "VERSION.txt" slurp string/trim)) 24 | 25 | (def jar-params {:project-name lib 26 | :version version}) 27 | 28 | (defn clean 29 | [_params] 30 | (build/delete {:path "target"})) 31 | 32 | (defn jar 33 | [_params] 34 | (b/create-jar jar-params)) 35 | 36 | (defn deploy 37 | [_params] 38 | (clean nil) 39 | (b/deploy-jar (jar nil))) 40 | 41 | (defn codox 42 | [_params] 43 | (b/generate-codox {:project-name lib 44 | :version version 45 | :aliases [:dev]})) 46 | 47 | (def publish-dir "../apidocs/lacinia") 48 | 49 | (defn publish 50 | "Generate Codox documentation and publish via a GitHub push." 51 | [_params] 52 | (println "Generating Codox documentation") 53 | (codox nil) 54 | (println "Copying documentation to" publish-dir "...") 55 | (build/copy-dir {:target-dir publish-dir 56 | :src-dirs ["target/doc"]}) 57 | (println "Committing changes ...") 58 | (build/process {:dir publish-dir 59 | :command-args ["git" "commit" "-a" "-m" (str "lacinia " version)]}) 60 | (println "Pushing changes ...") 61 | (build/process {:dir publish-dir 62 | :command-args ["git" "push"]})) 63 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.11.1"} 2 | clj-antlr/clj-antlr {:mvn/version "0.2.12"} 3 | org.flatland/ordered {:mvn/version "1.15.10"} 4 | org.clojure/data.json {:mvn/version "2.4.0"}} 5 | :paths ["src" "resources"] 6 | :aliases 7 | {:dev 8 | {:extra-deps {criterium/criterium {:mvn/version "0.4.6"} 9 | org.clojure/core.async {:mvn/version "1.6.673"} 10 | expound/expound {:mvn/version "0.9.0"} 11 | joda-time/joda-time {:mvn/version "2.12.2"} 12 | com.walmartlabs/test-reporting {:mvn/version "1.2"} 13 | io.aviso/logging {:mvn/version "1.0"} 14 | io.github.hlship/trace {:mvn/version "v1.0"} 15 | io.pedestal/pedestal.log {:mvn/version "0.5.10"} 16 | org.clojure/test.check {:mvn/version "1.1.1"} 17 | org.clojure/data.csv {:mvn/version "1.0.1"} 18 | org.clojure/tools.cli {:mvn/version "1.0.214"} 19 | com.clojure-goes-fast/clj-async-profiler {:mvn/version "1.0.3"}} 20 | :jvm-opts ["-Xmx1g" 21 | "-Xms1g" 22 | "-XX:NewSize=200m" 23 | "-XX:MaxNewSize=500m" 24 | "-XX:-OmitStackTraceInFastThrow" 25 | "-XX:+UnlockDiagnosticVMOptions" 26 | "-XX:+DebugNonSafepoints" 27 | "-XX:FlightRecorderOptions=stackdepth=256" 28 | "-Djdk.attach.allowAttachSelf" 29 | "-Dapple.awt.UIElement=true"] 30 | :extra-paths ["test" "dev-resources"]} 31 | 32 | ;; clj -Mdev:perf 33 | :perf 34 | {:main-opts ["--main" "perf"]} 35 | 36 | ;; :test needs :dev, i.e. clj -Xdev:test 37 | :test 38 | {:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" 39 | :git/sha "dfb30dd"}} 40 | :exec-fn cognitect.test-runner.api/test 41 | :exec-args 42 | {:patterns [".*-tests?$"]}} 43 | 44 | ;; clj -Mdev:test:coverage 45 | :coverage {:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}} 46 | :main-opts ["-m" "cloverage.coverage" "-p" "src" "-s" "test" "--codecov"]} 47 | 48 | ;; clj -T:build 49 | :build {:deps {io.github.hlship/build-tools 50 | {:git/tag "0.10.1" :git/sha "7ecff5"}} 51 | :ns-default build}} 52 | 53 | :net.lewisship.build/scm 54 | {:license :asl} 55 | 56 | :codox/config 57 | {:description "Clojure-native implementation of GraphQL" 58 | :source-uri "https://github.com/walmartlabs/lacinia/blob/master/{filepath}#L{line}"}} 59 | -------------------------------------------------------------------------------- /dev-resources/bad-resolver-enum.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:status 3 | {:values [:good :bad]}} 4 | 5 | :queries 6 | {:current_status 7 | {:type :status 8 | :resolve :query/current-status}}} 9 | -------------------------------------------------------------------------------- /dev-resources/bad_schema.sdl: -------------------------------------------------------------------------------- 1 | # Changing this will probably break tests. 2 | enum episode { 3 | NEWHOPE 4 | EMPIRE 5 | JEDI 6 | } 7 | 8 | scalar Date 9 | 10 | interface Character { 11 | name: String 12 | birthDate: Date 13 | } 14 | 15 | type CharacterOutput implements Character { 16 | name: String 17 | birthDate: Date 18 | episodes: [episode] 19 | } 20 | 21 | input Character { 22 | name: String! 23 | birthDate: Date 24 | episodes: [episode] 25 | } 26 | 27 | type Query { 28 | in_episode(episode: episode = NEWHOPE, episode: String) : [CharacterOutput] 29 | } 30 | 31 | type Query { 32 | find_by_names(names: [String!]!) : [CharacterOutput] 33 | find_by_names(character_names: [String!]!) : [CharacterOutput] 34 | } 35 | 36 | union Queries = Query 37 | 38 | type Queries { 39 | find_by_names: Character 40 | } 41 | 42 | type Mutation { 43 | add(character: Character = {name: "Unspecified", episode: [NEWHOPE, EMPIRE, JEDI]}) : Boolean 44 | } 45 | 46 | schema { 47 | query: Queries 48 | mutation: Mutation 49 | } 50 | -------------------------------------------------------------------------------- /dev-resources/basic-docs.md: -------------------------------------------------------------------------------- 1 | # Character 2 | 3 | A person who appears in one of the movies. 4 | 5 | Persons are more than humans, and may include droids and computers. 6 | 7 | ## Character/name 8 | 9 | The primary name of the character. 10 | 11 | For droids, this is the announced name, such as "AreToo". 12 | 13 | -------------------------------------------------------------------------------- /dev-resources/basic-vars.edn: -------------------------------------------------------------------------------- 1 | {:data 2 | {:default 3 | {:name "Darth Vader", 4 | :appears_in [:NEWHOPE :EMPIRE :JEDI], 5 | :friends [{:name "Wilhuff Tarkin"}], 6 | :home_planet "Tatooine"}, 7 | :hope_hero 8 | {:id "2001", 9 | :name "R2-D2", 10 | :friends 11 | [{:name "Luke Skywalker"} 12 | {:name "Han Solo"} 13 | {:name "Leia Organa"}]}}} 14 | -------------------------------------------------------------------------------- /dev-resources/basic.edn: -------------------------------------------------------------------------------- 1 | {:data {:default {:name "Darth Vader", 2 | :appears_in [:NEWHOPE :EMPIRE :JEDI], 3 | :friends [{:name "Wilhuff Tarkin"}], 4 | :home_planet "Tatooine"}, 5 | :hope_hero {:id "2001", 6 | :name "R2-D2", 7 | :friends [{:name "Luke Skywalker"} {:name "Han Solo"} {:name "Leia Organa"}]}}} 8 | -------------------------------------------------------------------------------- /dev-resources/blockquote.sdl: -------------------------------------------------------------------------------- 1 | type Query { 2 | with_default(arg: String = """ 3 | line 1 4 | 5 | line 3 6 | indented line 4 7 | line 5 8 | """) : String 9 | } 10 | -------------------------------------------------------------------------------- /dev-resources/custom-scalar-serialize-schema.edn: -------------------------------------------------------------------------------- 1 | {:scalars 2 | {:LimitedInt {:parse :parse 3 | :serialize :serialize}} 4 | 5 | :queries 6 | {:test {:type :LimitedInt 7 | :args {:in {:type (non-null Int)}} 8 | :resolve :test-query} 9 | 10 | :dupe {:type Int 11 | :args {:in {:type LimitedInt}} 12 | :resolve :test-query}}} 13 | -------------------------------------------------------------------------------- /dev-resources/deprecated-enums-schema.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:mood 3 | {:values [{:enum-value :GOOD 4 | :deprecated "Should use happy."} 5 | {:enum-value :HAPPY 6 | :description "Desired state."} 7 | {:enum-value :SAD 8 | :deprecated true}]}}} 9 | -------------------------------------------------------------------------------- /dev-resources/deprecated-fields-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:user 3 | {:fields 4 | {:title {:type String 5 | :deprecated true 6 | :description "Replaced by honorific."} 7 | :honorific {:type String} 8 | :nomdeplume {:type String 9 | :deprecated "Out of fashion." 10 | :description "Used by poets."}}}}} 11 | -------------------------------------------------------------------------------- /dev-resources/doc-inheritance-schema.edn: -------------------------------------------------------------------------------- 1 | {:interfaces 2 | {:sierra 3 | {:fields 4 | {:alpha {:type String} 5 | :bravo {:type String 6 | :description "sierra/bravo"}}} 7 | :tango 8 | {:description "tango interface" 9 | :fields 10 | {:charlie 11 | {:type String 12 | :description "tango/charlie" 13 | :args 14 | {:delta 15 | {:type String 16 | :description "tango/charlie/delta"} 17 | :echo 18 | {:type String 19 | :description "tango/charlie/echo"}}}}}} 20 | 21 | :objects 22 | {:ex1 23 | {:implements [:sierra] 24 | :fields 25 | {:alpha {:type String 26 | :description "ex1/alpha"} 27 | :bravo {:type String}}} 28 | :ex2 29 | {:implements [:sierra] 30 | :fields 31 | {:alpha {:type String 32 | :description "ex2/alpha"} 33 | :bravo {:type String 34 | :description "ex2/bravo"}}} 35 | :ex3 36 | {:implements [:tango] 37 | :fields 38 | {:charlie 39 | {:type String 40 | :args 41 | {:delta 42 | {:type String 43 | :description "ex3/delta"} 44 | :echo 45 | {:type String}}}}}}} 46 | -------------------------------------------------------------------------------- /dev-resources/documented-schema.sdl: -------------------------------------------------------------------------------- 1 | """ 2 | Things that have a name. 3 | """ 4 | interface Named { 5 | "The unique name for the Named thing." 6 | name: String 7 | } 8 | 9 | """ 10 | File node type. 11 | """ 12 | enum FileNodeType { 13 | "A standard file-system file." 14 | FILE 15 | 16 | "A directory that may contain other files and directories." 17 | DIR 18 | 19 | "A special file, such as a device." 20 | SPECIAL 21 | } 22 | 23 | type DirectoryListing implements Named { 24 | name: String 25 | node_type: FileNodeType 26 | } 27 | 28 | """ 29 | String that identifies permissions on a file or directory. 30 | """ 31 | scalar Permissions 32 | 33 | """ 34 | Directory type. 35 | """ 36 | type Directory implements Named { 37 | name : String 38 | permissions: Permissions 39 | contents( 40 | """ 41 | Wildcard used for matching. 42 | """ 43 | match : String) : [DirectoryListing] 44 | } 45 | 46 | type File implements Named { 47 | name : String 48 | } 49 | 50 | "Stuff that can appear on the file system" 51 | union FileSystemEntry = File | Directory 52 | 53 | type Query { 54 | file(path : String) : FileSystemEntry 55 | } 56 | -------------------------------------------------------------------------------- /dev-resources/duplicate-type.sdl: -------------------------------------------------------------------------------- 1 | type Tree { height: Int } 2 | 3 | type Leaf { id: ID } 4 | 5 | type Tree { left: Node, right: Node } 6 | 7 | union Node = Leaf | Tree 8 | -------------------------------------------------------------------------------- /dev-resources/dyn-args-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:cars {:type :CarCollection 3 | :args {:filter {:type :CarCollectionFilter}}}} 4 | 5 | :input-objects 6 | {:CarCollectionFilter 7 | {:fields {:or {:type (list :CarCollectionFilter)} 8 | :color {:type :StringFilter}}} 9 | 10 | :StringFilter 11 | {:fields {:equals {:type String}}}} 12 | 13 | :objects 14 | {:Car 15 | {:fields {:color {:type String}}} 16 | 17 | :CarCollection 18 | {:fields {:nodes {:type (list :Car)}}}}} -------------------------------------------------------------------------------- /dev-resources/dynamic-input-objects.edn: -------------------------------------------------------------------------------- 1 | {:input-objects 2 | {:Filter 3 | {:fields 4 | {:id {:type (non-null :ID)} 5 | :limit {:type (non-null :Int)}}}} 6 | :queries 7 | {:filter 8 | {:type String 9 | :resolve :queries/filter 10 | :args 11 | {:input {:type (non-null :Filter)}}}}} -------------------------------------------------------------------------------- /dev-resources/empty-input-objects.edn: -------------------------------------------------------------------------------- 1 | {:input-objects 2 | {:root_input 3 | {:fields {:name {:type String}}}} 4 | 5 | :queries 6 | {:print_input 7 | {:type String 8 | :args 9 | {:input {:type :root_input}} 10 | :resolve :query/print-input}}} 11 | -------------------------------------------------------------------------------- /dev-resources/enum-default-value-with-transformer-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Country 3 | {:fields 4 | {:code {:type :CountryCode}}}} 5 | :enums 6 | {:CountryCode 7 | {:values [:US]}} 8 | :queries 9 | {:countryByCode 10 | {:type :Country 11 | :resolve :placeholder 12 | :args 13 | {:code {:type :CountryCode 14 | :default-value :country-codes/us}}}}} 15 | -------------------------------------------------------------------------------- /dev-resources/enum-parse-serialize.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:status 3 | {:values [:good :bad]}} 4 | 5 | :objects 6 | {:Result 7 | {:fields 8 | {:input {:type String} 9 | :output {:type :status}}}} 10 | 11 | :queries 12 | {:echo 13 | {:type :Result 14 | :args {:in {:type :status}} 15 | :resolve :queries/echo} 16 | 17 | :fail 18 | {:type :Result 19 | :resolve :queries/fail}}} 20 | -------------------------------------------------------------------------------- /dev-resources/enums.sdl: -------------------------------------------------------------------------------- 1 | enum Location { MATRIX ZION MACHINE_CITY} 2 | -------------------------------------------------------------------------------- /dev-resources/extensions-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:extension {:type String 3 | :resolve :queries/extension} 4 | :warning {:type String 5 | :resolve :queries/warning}}} 6 | -------------------------------------------------------------------------------- /dev-resources/field-resolver-errors.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:Color {:values [RED GREEN BLUE]}} 3 | :objects 4 | {:MyObject 5 | {:fields 6 | {:error_field 7 | {:type String 8 | :resolve :single-error} 9 | 10 | :color {:type :Color 11 | :resolve :color} 12 | :exception 13 | {:type String 14 | :args 15 | {:range {:type Int}} 16 | :resolve :exception} 17 | 18 | :with_extensions 19 | {:type String 20 | :resolve :with-extensions} 21 | 22 | :multiple_errors_field 23 | {:type String 24 | :resolve :multiple-errors}}}} 25 | 26 | :queries 27 | {:root {:type :MyObject 28 | :resolve :resolve-root}}} 29 | -------------------------------------------------------------------------------- /dev-resources/field-resolver-protocol-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:hello {:type String 3 | :resolve :query/hello}}} 4 | -------------------------------------------------------------------------------- /dev-resources/fixes-types-schema.edn: -------------------------------------------------------------------------------- 1 | {:unions 2 | {:Characters 3 | {:members [:Human :Droid]}} 4 | :objects 5 | {:Human 6 | {:fields 7 | {:first_name {:type String 8 | :resolve [:prop :firstName]} 9 | :last_name {:type String 10 | :resolve [:prop :lastName]}}} 11 | 12 | :Droid 13 | {:fields 14 | {:designation {:type String 15 | :resolve [:prop :designation]} 16 | :function {:type String 17 | :resolve [:prop :function]}}}} 18 | 19 | :queries 20 | {:human 21 | {:type :Human 22 | :resolve :resolve-human} 23 | 24 | :droid 25 | {:type :Droid 26 | :resolve :resolve-droid} 27 | 28 | :astromech 29 | {:type :Droid 30 | :resolve :resolve-astromech} 31 | 32 | :characters 33 | {:type (list :Characters) 34 | :resolve :resolve-characters}}} 35 | -------------------------------------------------------------------------------- /dev-resources/fragments-schema.edn: -------------------------------------------------------------------------------- 1 | {:interfaces 2 | {:character 3 | {:fields 4 | {:name {:type String} 5 | :friends {:type (list :character)}}}} 6 | 7 | :objects 8 | {:droid 9 | {:implements [:character] 10 | :fields {:name {:type String} 11 | :power {:type String} 12 | :friends {:type (list :character) 13 | :resolve :resolve-friends }}} 14 | 15 | :human 16 | {:implements [:character] 17 | :fields {:name {:type String} 18 | :friends {:type (list :character) 19 | :resolve :resolve-friends} 20 | :home_world {:type String}}}} 21 | 22 | :queries 23 | {:characters 24 | {:type (list :character) 25 | :resolve :resolve-characters}}} 26 | -------------------------------------------------------------------------------- /dev-resources/generic-scalar-errors-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Data 3 | {:fields 4 | {:value {:type Int}}}} 5 | 6 | :queries 7 | {:make_data 8 | {:type :Data 9 | :args {:value {:type Int}} 10 | :resolve :queries/make-data} 11 | 12 | :bad_data 13 | {:type :Data 14 | :resolve :queries/bad-data}}} 15 | -------------------------------------------------------------------------------- /dev-resources/input-object-default-value-schema.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:mood {:values [:happy :sad]}} 3 | 4 | :input-objects 5 | {:Filter 6 | {:fields 7 | {:title {:type String 8 | :default-value "columbia"} 9 | :checked {:type Boolean 10 | :default-value true} 11 | :count {:type Int 12 | :default-value 10} 13 | :mood {:type :mood 14 | :default-value :happy} 15 | :has_no_default {:type String} 16 | :target {:type Float 17 | :default-value 3.2}}}} 18 | 19 | :queries 20 | {:search 21 | {:type String 22 | :resolve :placeholder 23 | :args 24 | {:filter {:type :Filter 25 | :default-value {:title "gorge" 26 | :checked false 27 | :count 20 28 | :target 3.14}}}}}} 29 | -------------------------------------------------------------------------------- /dev-resources/input-object-schema.edn: -------------------------------------------------------------------------------- 1 | {:input-objects 2 | {:Filter 3 | {:fields 4 | {:terms {:type (list (non-null String))} 5 | :max_count {:type Int}}}} 6 | 7 | :queries 8 | {:search 9 | {:type (list String) 10 | :args 11 | {:filter {:type :Filter}} 12 | :resolve :queries/search}}} 13 | -------------------------------------------------------------------------------- /dev-resources/interfaces.sdl: -------------------------------------------------------------------------------- 1 | interface Matrix 2 | { 3 | eject(the_one: Boolean!) : Human 4 | } 5 | 6 | type Human { 7 | name : String! 8 | } 9 | -------------------------------------------------------------------------------- /dev-resources/large-lists-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Item 3 | {:fields 4 | {:name {:type String} 5 | :age {:type Int} 6 | :id {:type Int} 7 | :city {:type String}}}} 8 | 9 | :queries 10 | {:list {:type (list :Item) 11 | :resolve :resolve-list}}} 12 | -------------------------------------------------------------------------------- /dev-resources/large_lists.clj: -------------------------------------------------------------------------------- 1 | (ns large-lists 2 | (:require 3 | [clojure.spec.alpha :as s] 4 | [clojure.spec.gen.alpha :as gen] 5 | [criterium.core :as c] 6 | [com.walmartlabs.test-utils :refer [compile-schema]] 7 | [com.walmartlabs.lacinia.parser :as parser] 8 | [com.walmartlabs.lacinia :as lacinia])) 9 | 10 | (s/def ::name string?) 11 | (s/def ::age (s/int-in 1 100)) 12 | (s/def ::id integer?) 13 | (s/def ::city string?) 14 | (s/def ::item (s/keys :req-un [::name ::age ::id ::city])) 15 | 16 | (def ^:private large-list 17 | (vec 18 | (repeatedly 5000 19 | #(gen/generate (s/gen ::item))))) 20 | 21 | 22 | (def schema (compile-schema "large-lists-schema.edn" 23 | {:resolve-list (constantly large-list)})) 24 | 25 | (defn bench-mapv 26 | [] 27 | (binding [c/*report-progress* true] 28 | (c/bench (mapv #(select-keys % [:name :age :id]) large-list)))) 29 | 30 | (defn bench-exec 31 | [] 32 | (binding [c/*report-progress* true] 33 | (let [q "{ list { name age id }}" 34 | parsed (parser/parse-query schema q)] 35 | (c/bench 36 | (lacinia/execute-parsed-query parsed nil nil))))) 37 | 38 | (defn bench-parse-and-execute 39 | [] 40 | (binding [c/*report-progress* true] 41 | (c/bench 42 | (lacinia/execute schema "{ list { name age id }}" nil nil)))) 43 | 44 | (comment 45 | (bench-mapv) 46 | ;; 2.308310 ms 47 | ;; -- switch to bench -- 48 | ;; 2.290889 ms 49 | 50 | (bench-exec) 51 | ;; 61.779221 ms -- base line 52 | ;; 59.036665 ms -- use hash-map instead of ordered-map 53 | ;; 66.580824 ms -- optimize (?!) combine-results (take 1) 54 | ;; 65.814538 ms -- optimize (?!) combine-results (take 2) 55 | ;; 67.019828 ms -- new base line (should match first base line, but eh, quick-bench) 56 | ;; 63.949198 ms -- optimize selector (removing some check steps) 57 | ;; 66.481703 ms -- remove executing timing penalty when not timing execution 58 | ;; 55.431582 ms -- optimize for single key/value pair (normal case outside of fragments) 59 | ;; -- switch to bench -- 60 | ;; 58.143757 ms 61 | ;; 56.056103 ms -- use defrecord SelectionContext 62 | ;; 56.646881 ms -- less use of promises in ResolverResultPromise 63 | ;; 51.936539 ms -- tiny optimization on empty lists 64 | ;; 44.024393 ms -- leaf optimization 65 | 66 | 67 | (bench-parse-and-execute) 68 | ;; 67.814614 ms -- base line 69 | ;; -- switch to bench -- 70 | ) 71 | 72 | -------------------------------------------------------------------------------- /dev-resources/list-default-value-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:search 3 | {:type String 4 | :resolve :placeholder 5 | :args 6 | {:terms {:type (list String) 7 | :default-value ["columbia" "river" "gorge"]}}}}} 8 | -------------------------------------------------------------------------------- /dev-resources/lists-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Container 3 | {:fields 4 | {:empty_list {:type (list String)} 5 | :non_empty_list {:type (list String)} 6 | :null_list {:type (list String)} 7 | :non_null_list {:type (non-null (list String))}}}} 8 | 9 | :queries 10 | {:container {:type :Container 11 | :args {:null {:type Boolean 12 | :default-value false}} 13 | :resolve :query/container}}} 14 | -------------------------------------------------------------------------------- /dev-resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HHmmss.SSS} %-5level %logger{0} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dev-resources/mult-inheritance.sdl: -------------------------------------------------------------------------------- 1 | interface Node { 2 | id: ID! 3 | } 4 | 5 | interface Client { 6 | ip: String 7 | } 8 | 9 | type User implements Node & Client { 10 | id: ID! 11 | ip: String 12 | } 13 | 14 | type Query { 15 | me: User 16 | } 17 | 18 | schema { 19 | query: Query 20 | } 21 | -------------------------------------------------------------------------------- /dev-resources/mutable-context-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Root 3 | {:fields 4 | {:container {:type :Container}}} 5 | :Container 6 | {:fields 7 | {:id {:type String} 8 | :leaf {:type String 9 | :resolve :leaf}}}} 10 | :queries 11 | {:root 12 | {:type :Root 13 | :args {:trigger {:type Boolean}} 14 | :resolve :root}}} 15 | -------------------------------------------------------------------------------- /dev-resources/nested-non-nullable-fields-schema.edn: -------------------------------------------------------------------------------- 1 | {:input-objects 2 | {:game_template 3 | {:fields {:id {:type (non-null Int)} 4 | :name {:type String}}}} 5 | 6 | :mutations 7 | {:create_game 8 | {:type String 9 | :args 10 | {:game_data {:type :game_template}} 11 | :resolve :mutation/create-game}}} 12 | -------------------------------------------------------------------------------- /dev-resources/no-entities-federation.sdl: -------------------------------------------------------------------------------- 1 | type User { 2 | id: Int! 3 | name: String 4 | } 5 | 6 | # Just want a union to show that _entities is not present 7 | 8 | union Stuff = User 9 | -------------------------------------------------------------------------------- /dev-resources/non-chatty-library-failures.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Query 3 | {:fields 4 | {:hello {:type (non-null String) 5 | :resolve :hello}}}}} -------------------------------------------------------------------------------- /dev-resources/non-string-default-value-schema.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:mood {:values [:happy :sad]}} 3 | 4 | :queries 5 | {:search 6 | {:type String 7 | :resolve :placeholder 8 | :args 9 | {:title {:type String 10 | :default-value "columbia"} 11 | :checked {:type (non-null Boolean) 12 | :default-value true} 13 | :count {:type Int 14 | :default-value 10} 15 | :mood {:type :mood 16 | :default-value :happy} 17 | :has_no_default {:type String} 18 | :target {:type Float 19 | :default-value 3.2}}}}} 20 | -------------------------------------------------------------------------------- /dev-resources/nullability.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Employer 3 | {:fields 4 | {:id {:type (non-null ID)} 5 | :name {:type (non-null String)}}} 6 | :User 7 | {:fields 8 | {:id {:type (non-null ID)} 9 | :employer {:type :Employer}}} 10 | :Query 11 | {:fields 12 | {:user {:type (non-null :User) 13 | :args {:id {:type (non-null ID)}} 14 | :resolve :query/user}}}}} -------------------------------------------------------------------------------- /dev-resources/object-scalars-schema.edn: -------------------------------------------------------------------------------- 1 | {:scalars 2 | {:Data 3 | {:description "Arbitrary JSON/EDN data." 4 | :parse :scalars/data.parse 5 | :serialize :scalars/data.serialize}} 6 | 7 | :queries 8 | {:echo {:type :Data 9 | :args {:input {:type :Data}} 10 | :resolve :queries/echo}}} 11 | -------------------------------------------------------------------------------- /dev-resources/opt-req-enum.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:Result 3 | {:values [OK FAIL]}} 4 | 5 | :queries 6 | {:ok {:type :Result 7 | :resolve :null} 8 | :bad {:type (non-null :Result) 9 | :resolve :null}}} 10 | -------------------------------------------------------------------------------- /dev-resources/org/example/db.clj: -------------------------------------------------------------------------------- 1 | (ns org.example.db 2 | (:require 3 | [com.walmartlabs.lacinia.schema :as schema])) 4 | 5 | (defn ^:private tagged 6 | [x] 7 | (schema/tag-with-type x (::type x))) 8 | 9 | (def ^:private humans-data 10 | (map #(assoc % ::type :human) 11 | [{:id "1000" 12 | :name "Luke Skywalker" 13 | :friends ["1002", "1003", "2000", "2001"] 14 | :appears_in ["NEWHOPE" "EMPIRE" "JEDI"] 15 | :home_planet "Tatooine" 16 | :force_side "3001"} 17 | {:id "1001" 18 | :name "Darth Vader" 19 | :friends ["1004"] 20 | :appears_in ["NEWHOPE" "EMPIRE" "JEDI"] 21 | :home_planet "Tatooine" 22 | :force_side "3000"} 23 | {:id "1003" 24 | :name "Leia Organa" 25 | :friends ["1000", "1002", "2000", "2001"] 26 | :appears_in ["NEWHOPE" "EMPIRE" "JEDI"] 27 | :home_planet "Alderaan" 28 | :force_side "3001"} 29 | {:id "1002" 30 | :name "Han Solo" 31 | :friends ["1000", "1003", "2001"] 32 | :appears_in ["NEWHOPE" "EMPIRE" "JEDI"] 33 | :force_side "3001"} 34 | {:id "1004" 35 | :name "Wilhuff Tarkin" 36 | :friends ["1001"] 37 | :appears_in ["NEWHOPE"] 38 | :force_side "3000"}])) 39 | 40 | (def ^:private droids-data 41 | (map #(assoc % ::type :droid) 42 | [{:id "2001" 43 | :name "R2-D2" 44 | :friends ["1000", "1002", "1003"] 45 | :appears_in ["NEWHOPE" "EMPIRE" "JEDI"] 46 | :primary-function "ASTROMECH"} 47 | {:id "2000" 48 | :name "C-3PO" 49 | :friends ["1000", "1002", "1003", "2001"] 50 | :appears_in ["NEWHOPE" "EMPIRE" "JEDI"] 51 | :primary-function "PROTOCOL"}])) 52 | 53 | (def ^:private character-data (concat humans-data droids-data)) 54 | 55 | 56 | (def ^:private hero-data 57 | {:NEWHOPE "2001" 58 | :EMPIRE "1002" 59 | :JEDI "1000"}) 60 | 61 | (defn ^:private first-match [data key value] 62 | (-> (filter #(= (get % key) value) data) 63 | first 64 | tagged)) 65 | 66 | (defn resolve-hero 67 | [ctx args value] 68 | (let [episode (:episode args :NEWHOPE) 69 | hero-id (get hero-data episode)] 70 | (first-match character-data :id hero-id))) 71 | 72 | (defn resolve-droid 73 | [ctx args value] 74 | (first-match droids-data :id (:id args))) 75 | 76 | (defn resolve-friends 77 | [ctx args value] 78 | (map #(first-match character-data :id %) (:friends value))) 79 | 80 | (defn resolve-human 81 | [ctx args value] 82 | (first-match humans-data :id (:id args))) 83 | -------------------------------------------------------------------------------- /dev-resources/org/example/schema.clj: -------------------------------------------------------------------------------- 1 | (ns org.example.schema 2 | (:require 3 | [clojure.edn :as edn] 4 | [clojure.java.io :as io] 5 | [com.walmartlabs.lacinia.schema :as schema] 6 | [com.walmartlabs.lacinia.util :as util] 7 | [org.example.db :as db])) 8 | 9 | (defn star-wars-schema 10 | [] 11 | (-> (io/resource "star-wars-schema.edn") 12 | slurp 13 | edn/read-string 14 | (util/attach-resolvers {:hero db/resolve-hero 15 | :human db/resolve-human 16 | :droid db/resolve-droid 17 | :friends db/resolve-friends}) 18 | schema/compile)) 19 | -------------------------------------------------------------------------------- /dev-resources/parser/aliases.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello 4 | :alias :greet 5 | :selections [{:type :field 6 | :field-name :world 7 | :alias :w} 8 | {:type :field 9 | :field-name :longitude 10 | :alias :long} 11 | {:type :field 12 | :field-name :latitude 13 | :alias :lat}]}]}] 14 | -------------------------------------------------------------------------------- /dev-resources/parser/aliases.gql: -------------------------------------------------------------------------------- 1 | { 2 | greet: hello { 3 | w: world 4 | long: longitude 5 | lat: latitude 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /dev-resources/parser/args.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello 4 | :args [{:arg-name :world 5 | :arg-value {:type :boolean 6 | :value "true"}}] 7 | :selections [{:type :field 8 | :field-name :greeting}]}]}] 9 | -------------------------------------------------------------------------------- /dev-resources/parser/args.gql: -------------------------------------------------------------------------------- 1 | { 2 | hello(world: true) { greeting } 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/arrays.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello 4 | :args [{:arg-name :i 5 | :arg-value {:type :array 6 | :value [{:type :integer 7 | :value "3"} 8 | {:type :integer 9 | :value "4"} 10 | {:type :integer 11 | :value "5"}]}} 12 | {:arg-name :s 13 | :arg-value {:type :array 14 | :value [{:type :string 15 | :value "hello"} 16 | {:type :string 17 | :value "goodbye"}]}}]}]}] 18 | -------------------------------------------------------------------------------- /dev-resources/parser/arrays.gql: -------------------------------------------------------------------------------- 1 | { 2 | hello(i: [3, 4, 5] 3 | s: ["hello", "goodbye"]) 4 | } 5 | -------------------------------------------------------------------------------- /dev-resources/parser/enum-reserved.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :introspect 4 | :args [{:arg-name :types 5 | :arg-value {:type :array 6 | :value [{:type :enum 7 | :value :query} 8 | {:type :enum 9 | :value :mutation} 10 | {:type :enum 11 | :value :subscription} 12 | {:type :enum 13 | :value :other}]}}]}]}] 14 | -------------------------------------------------------------------------------- /dev-resources/parser/enum-reserved.gql: -------------------------------------------------------------------------------- 1 | { 2 | introspect(types: [query, mutation, subscription, other]) 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/enum.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :orders 4 | :args [{:arg-name :sort 5 | :arg-value {:type :enum 6 | :value :descending}}] 7 | :selections [{:type :field 8 | :field-name :sales_date 9 | :args [{:arg-name :time_zone 10 | :arg-value {:type :enum 11 | :value :utc}}]}]}]}] 12 | -------------------------------------------------------------------------------- /dev-resources/parser/enum.gql: -------------------------------------------------------------------------------- 1 | { 2 | orders(sort: descending) { sales_date(time_zone: utc) } 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/explicit-query.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello}]}] 4 | -------------------------------------------------------------------------------- /dev-resources/parser/explicit-query.gql: -------------------------------------------------------------------------------- 1 | query { 2 | hello 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/field-directive.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :vars [{:var-name :internal 3 | :var-type {:of-type {:type :root-type 4 | :type-name :Boolean} 5 | :type :non-null}}] 6 | :selections [{:field-name :order 7 | :type :field 8 | :selections [{:type :field 9 | :field-name :id} 10 | {:type :field 11 | :field-name :customer_id 12 | :directives [{:directive-name :If 13 | :args [{:arg-name :test 14 | :arg-value {:type :variable 15 | :value :internal}}]}]} 16 | {:type :field 17 | :field-name :date}]}]}] 18 | -------------------------------------------------------------------------------- /dev-resources/parser/field-directive.gql: -------------------------------------------------------------------------------- 1 | query($internal: Boolean!) { 2 | order 3 | { 4 | id 5 | customer_id @If(test: $internal) 6 | date 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dev-resources/parser/frag-spread.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :order 4 | :selections [{:type :field 5 | :field-name :id} 6 | {:type :inline-fragment 7 | :on-type :InStore 8 | :selections [{:field-name :store_number 9 | :type :field}]} 10 | {:type :inline-fragment 11 | :on-type :OnlineOrder 12 | :selections [{:field-name :refer_code 13 | :type :field}]}]}]}] 14 | -------------------------------------------------------------------------------- /dev-resources/parser/frag-spread.gql: -------------------------------------------------------------------------------- 1 | { 2 | order { 3 | id 4 | ... on InStore { 5 | store_number 6 | } 7 | ... on OnlineOrder { 8 | refer_code 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dev-resources/parser/fragment-directives.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :order 4 | :selections [{:type :inline-fragment 5 | :on-type :InStore 6 | :directives [{:directive-name :Secure}] 7 | :selections [{:type :field 8 | :field-name :store_number}]} 9 | {:type :field 10 | :field-name :item_count} 11 | {:type :named-fragment 12 | :fragment-name :OnlineFrag 13 | :directives [{:directive-name :InternalOnly}]}]}]} 14 | {:type :fragment-definition 15 | :fragment-name :OnlineFrag 16 | :on-type :OnlineOrder 17 | :selections [{:field-name :refer_code 18 | :type :field}]}] 19 | -------------------------------------------------------------------------------- /dev-resources/parser/fragment-directives.gql: -------------------------------------------------------------------------------- 1 | query { 2 | order { 3 | ... on InStore @Secure { store_number } 4 | item_count 5 | ... OnlineFrag @InternalOnly 6 | } 7 | } 8 | 9 | fragment OnlineFrag on OnlineOrder { 10 | refer_code 11 | } 12 | -------------------------------------------------------------------------------- /dev-resources/parser/literals.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello 4 | :args [{:arg-name :i 5 | :arg-value {:type :integer 6 | :value "5"}} 7 | {:arg-name :f 8 | :arg-value {:type :float 9 | :value "3.14"}} 10 | {:arg-name :s 11 | :arg-value {:type :string 12 | :value "clojure"}} 13 | 14 | {:arg-name :bs 15 | :arg-value {:type :string 16 | :value "This is a block string indented\nfour characters.\n\n This line is indented an extra two.\n\nCharacters such as \\t are not expanded."}} 17 | {:arg-name :b 18 | :arg-value {:type :boolean 19 | :value "false"}} 20 | {:arg-name :n 21 | :arg-value {:type :null}}]}]}] 22 | -------------------------------------------------------------------------------- /dev-resources/parser/literals.gql: -------------------------------------------------------------------------------- 1 | { 2 | hello (i: 5 3 | f: 3.14 4 | s: "clojure" 5 | bs: """ 6 | This is a block string indented 7 | four characters. 8 | 9 | This line is indented an extra two. 10 | 11 | Characters such as \t are not expanded. 12 | """ 13 | b: false 14 | n: null) 15 | } 16 | -------------------------------------------------------------------------------- /dev-resources/parser/named-fragment.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :order 4 | :selections [{:type :named-fragment 5 | :fragment-name :InStoreFrag} 6 | {:type :field 7 | :field-name :item_count} 8 | {:type :named-fragment 9 | :fragment-name :OnlineFrag}]}]} 10 | {:type :fragment-definition 11 | :fragment-name :InStoreFrag 12 | :on-type :InStore 13 | :selections [{:field-name :store_number 14 | :type :field}]} 15 | {:type :fragment-definition 16 | :fragment-name :OnlineFrag 17 | :on-type :OnlineOrder 18 | :selections [{:field-name :refer_code 19 | :type :field}]}] 20 | -------------------------------------------------------------------------------- /dev-resources/parser/named-fragment.gql: -------------------------------------------------------------------------------- 1 | query { 2 | order { 3 | ... InStoreFrag 4 | item_count 5 | ... OnlineFrag 6 | } 7 | } 8 | 9 | fragment InStoreFrag on InStore { 10 | store_number 11 | } 12 | 13 | fragment OnlineFrag on OnlineOrder { 14 | refer_code 15 | } 16 | -------------------------------------------------------------------------------- /dev-resources/parser/named-operation.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :name :Friendly 3 | :selections [{:type :field 4 | :field-name :hello}]}] 5 | -------------------------------------------------------------------------------- /dev-resources/parser/named-operation.gql: -------------------------------------------------------------------------------- 1 | query Friendly { hello } 2 | -------------------------------------------------------------------------------- /dev-resources/parser/nested-fields.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello 4 | :selections [{:type :field 5 | :field-name :world} 6 | {:type :field 7 | :field-name :timestamp}]}]}] 8 | -------------------------------------------------------------------------------- /dev-resources/parser/nested-fields.gql: -------------------------------------------------------------------------------- 1 | { 2 | hello { world timestamp } 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/object.gql: -------------------------------------------------------------------------------- 1 | mutation { 2 | new_person(data: {last_name: "Phelps" 3 | first_name: "Fred" 4 | age: 39 5 | sex: male 6 | address: { 7 | street: "Hamburger Lane" 8 | city: "Wilsonville" 9 | state: "GE" 10 | zip: "87267" 11 | } 12 | notes: ["one" "two" "three"]})} 13 | 14 | -------------------------------------------------------------------------------- /dev-resources/parser/operation-directives.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :directives [{:directive-name :Whatever}] 3 | :selections [{:type :field 4 | :field-name :hello}]}] 5 | -------------------------------------------------------------------------------- /dev-resources/parser/operation-directives.gql: -------------------------------------------------------------------------------- 1 | query @Whatever { 2 | hello 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/reserved-args.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello 4 | :args [{:arg-name :query 5 | :arg-value {:type :string 6 | :value "graphql"}}]}]}] 7 | -------------------------------------------------------------------------------- /dev-resources/parser/reserved-args.gql: -------------------------------------------------------------------------------- 1 | { hello(query: "graphql") } 2 | -------------------------------------------------------------------------------- /dev-resources/parser/reserved-words.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :alias :q 4 | :field-name :query} 5 | {:type :field 6 | :alias :m 7 | :field-name :mutation} 8 | {:type :field 9 | :alias :s 10 | :field-name :subscription}]}] 11 | -------------------------------------------------------------------------------- /dev-resources/parser/reserved-words.gql: -------------------------------------------------------------------------------- 1 | { 2 | q: query 3 | m: mutation 4 | s: subscription 5 | } 6 | -------------------------------------------------------------------------------- /dev-resources/parser/simple.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :selections [{:type :field 3 | :field-name :hello}]}] 4 | -------------------------------------------------------------------------------- /dev-resources/parser/simple.gql: -------------------------------------------------------------------------------- 1 | { 2 | hello 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/variable-defaults.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :vars [{:var-name :format 3 | :var-type {:type :root-type 4 | :type-name :GreetingFormat} 5 | :default {:type :enum 6 | :value :LONG}}] 7 | :selections [{:type :field 8 | :field-name :hello 9 | :args [{:arg-name :format 10 | :arg-value {:type :variable 11 | :value :format}}]}]}] 12 | -------------------------------------------------------------------------------- /dev-resources/parser/variable-defaults.gql: -------------------------------------------------------------------------------- 1 | query($format : GreetingFormat = LONG) { 2 | hello(format : $format) 3 | } 4 | -------------------------------------------------------------------------------- /dev-resources/parser/vars.edn: -------------------------------------------------------------------------------- 1 | [{:type :query 2 | :vars [{:var-name :id 3 | :var-type {:type :non-null 4 | :of-type {:type :root-type 5 | :type-name :Int}}} 6 | {:var-name :terms 7 | :var-type {:type :list 8 | :of-type {:type :non-null 9 | :of-type {:type :root-type 10 | :type-name :String}}}} 11 | {:var-name :casesensitive 12 | :var-type {:type :root-type 13 | :type-name :Boolean}}] 14 | :selections [{:type :field 15 | :field-name :order 16 | :args [{:arg-name :order_id 17 | :arg-value {:type :variable 18 | :value :id}}]}]}] 19 | -------------------------------------------------------------------------------- /dev-resources/parser/vars.gql: -------------------------------------------------------------------------------- 1 | query($id: Int! 2 | $terms: [String!] 3 | $casesensitive: Boolean) { 4 | order(order_id: $id) 5 | } 6 | -------------------------------------------------------------------------------- /dev-resources/preamble-docs.md: -------------------------------------------------------------------------------- 1 | This text is ignored. 2 | 3 | This text contains a # mark. 4 | 5 | # Customer 6 | 7 | Someone we sell to. 8 | 9 | -------------------------------------------------------------------------------- /dev-resources/query-reserved.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:subscription 3 | {:fields {:mutation {:type Boolean}}}} 4 | :queries 5 | {:query 6 | {:type :subscription 7 | :args {:mutation {:type Boolean}} 8 | :resolve :resolve-query}}} 9 | -------------------------------------------------------------------------------- /dev-resources/root-object-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:fred {:type String 3 | :resolve :queries/fred}} 4 | :objects 5 | {:Barney 6 | {:fields 7 | {:last_name {:type String 8 | :resolve :Barney/last-name}}}} 9 | 10 | :roots {:query :Barney}} 11 | -------------------------------------------------------------------------------- /dev-resources/root-object-with-conflicts-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:last_name {:type String 3 | :resolve :queries/fred}} 4 | :objects 5 | {:Barney 6 | {:fields 7 | {:last_name {:type String 8 | :resolve :Barney/last-name}}}} 9 | 10 | :roots {:query :Barney}} 11 | -------------------------------------------------------------------------------- /dev-resources/sample_schema.sdl: -------------------------------------------------------------------------------- 1 | # Changing this will probably break tests. 2 | enum episode { 3 | NEWHOPE 4 | EMPIRE 5 | JEDI 6 | } 7 | 8 | "Extra tracing of field operations" 9 | directive @Trace (label : String!) on FIELD_DEFINITION 10 | 11 | "Date in standard ISO format" 12 | scalar Date 13 | 14 | interface Human { 15 | name: String 16 | birthDate: Date 17 | } 18 | 19 | type CharacterOutput implements Human { 20 | name: String 21 | birthDate: Date 22 | episodes: [episode] 23 | } 24 | 25 | input Character { 26 | name: String! 27 | birthDate: Date 28 | episodes: [episode] 29 | } 30 | 31 | type MyQuery { 32 | in_episode(episode: episode = NEWHOPE) : [CharacterOutput] @Trace 33 | } 34 | 35 | type OtherQuery { 36 | find_by_names(names: [String!]!) : [CharacterOutput] 37 | } 38 | 39 | type Mutation { 40 | add(character: Character = {name: "Unspecified", episodes: [NEWHOPE, EMPIRE, JEDI]}) : Boolean 41 | @deprecated(reason: "just for testing") 42 | @Trace(label: "add-character") 43 | } 44 | 45 | type Subscription { 46 | new_character(episodes: [episode!]!) : CharacterOutput 47 | } 48 | 49 | # This doesn't make much sense but is a remnant from when unions were allowed for schema operation types 50 | # (Jun 2018 spec makes it clear they must be objects, not unions). */ 51 | union Queries = MyQuery | OtherQuery 52 | 53 | schema { 54 | query: MyQuery 55 | mutation: Mutation 56 | subscription: Subscription 57 | } 58 | -------------------------------------------------------------------------------- /dev-resources/select-type-schema.edn: -------------------------------------------------------------------------------- 1 | {:interfaces 2 | {:ItemsContainer 3 | {:fields 4 | {:items {:type (non-null (list (non-null :Item)))}}}} 5 | 6 | :objects 7 | {:Order 8 | {:implements [:ItemsContainer] 9 | :fields 10 | {:id {:type (non-null String)} 11 | :items {:type (non-null (list (non-null :Item)))}}} 12 | 13 | :Item 14 | {:fields 15 | {:quantity {:type (non-null Int)} 16 | :description {:type String} 17 | :product {:type (non-null Product)}}} 18 | 19 | :Product 20 | {:fields 21 | {:name {:type (non-null String)}} 22 | :upc {:type (non-null String)}} 23 | 24 | :Query 25 | {:fields 26 | {:order {:type :Order 27 | :args {:id {:type (non-null String)} 28 | :search {:type String}}}}}}} -------------------------------------------------------------------------------- /dev-resources/selection/argument-def-directive.sdl: -------------------------------------------------------------------------------- 1 | directive @non_negative on ARGUMENT_DEFINITION 2 | 3 | type Query { 4 | sqrt(num: Float! @non_negative) : Float! 5 | } 6 | -------------------------------------------------------------------------------- /dev-resources/selection/directive-args.sdl: -------------------------------------------------------------------------------- 1 | directive @auth (role: String!) on FIELD_DEFINITION 2 | 3 | directive @limit(value: Int = 10) on FIELD 4 | 5 | schema { query: Query } 6 | 7 | type Query { 8 | basic: [String]! 9 | } 10 | 11 | -------------------------------------------------------------------------------- /dev-resources/selection/enum-types.sdl: -------------------------------------------------------------------------------- 1 | directive @auth (role: String!) on ENUM 2 | 3 | schema { query: Query } 4 | 5 | enum Rank @auth(role: "enum") { INTRO, JUNIOR, SENIOR } 6 | 7 | type User { 8 | name: String! 9 | rank: Rank 10 | } 11 | 12 | type Query { 13 | me: User 14 | } 15 | 16 | -------------------------------------------------------------------------------- /dev-resources/selection/interface-types.sdl: -------------------------------------------------------------------------------- 1 | directive @auth (role: String!) on OBJECT | INTERFACE 2 | 3 | schema { query: Query } 4 | 5 | interface User @auth(role: "basic") { 6 | name: String! 7 | } 8 | 9 | type LegacyUser implements User @auth(role: "hidden") { 10 | name: String! 11 | userId: Int! 12 | } 13 | 14 | type ActiveUser implements User { 15 | name: String! 16 | userId: String! 17 | } 18 | 19 | type Query { 20 | me: User 21 | 22 | friends: [User!]! 23 | } 24 | 25 | -------------------------------------------------------------------------------- /dev-resources/selection/object-type.sdl: -------------------------------------------------------------------------------- 1 | directive @auth (role: String!) on OBJECT | FIELD_DEFINITION 2 | 3 | schema { query: Query } 4 | 5 | type User @auth(role: "basic") { 6 | name: String! @auth(role: "advanced") 7 | department: String! 8 | } 9 | 10 | type Query { 11 | me: User 12 | 13 | friends: [User!]! 14 | } 15 | 16 | -------------------------------------------------------------------------------- /dev-resources/selection/scalar-types.sdl: -------------------------------------------------------------------------------- 1 | schema { query: Query } 2 | 3 | directive @auth (role: String!) on SCALAR 4 | 5 | scalar UUID @auth(role: "basic") 6 | 7 | type User { 8 | name: String! 9 | id: UUID 10 | } 11 | 12 | type Query { 13 | me: User 14 | } 15 | 16 | -------------------------------------------------------------------------------- /dev-resources/selection/simple.sdl: -------------------------------------------------------------------------------- 1 | directive @auth (role: String!) on FIELD_DEFINITION 2 | 3 | directive @concise on FIELD 4 | 5 | schema { query: Query } 6 | 7 | type Query { 8 | basic: String! @auth(role: "basic") 9 | } 10 | 11 | -------------------------------------------------------------------------------- /dev-resources/selection/union-types.sdl: -------------------------------------------------------------------------------- 1 | directive @auth (role: String!) on OBJECT | UNION 2 | 3 | schema { query: Query } 4 | 5 | union User @auth(role: "basic") = ActiveUser | LegacyUser 6 | 7 | type LegacyUser @auth(role: "hidden") { 8 | userName: String! 9 | userId: Int! 10 | } 11 | 12 | type ActiveUser { 13 | name: String! 14 | idCode: String! 15 | } 16 | 17 | type Query { 18 | me: User 19 | 20 | friends: [User!]! 21 | } 22 | 23 | -------------------------------------------------------------------------------- /dev-resources/selection/wrap-field-directive.sdl: -------------------------------------------------------------------------------- 1 | directive @changecase (mode: ChangeCaseMode = UPPER_CASE) on FIELD_DEFINITION 2 | 3 | enum ChangeCaseMode { UPPER_CASE LOWER_CASE AS_IS } 4 | 5 | type Query { 6 | echoUpper(input: String!) : String @changecase 7 | echoLower(input: String!) : String @changecase(mode: LOWER_CASE) 8 | echoAsIs(input: String!) : String @deprecated 9 | } -------------------------------------------------------------------------------- /dev-resources/selections-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Root 3 | {:fields 4 | {:tree {:type String} 5 | :detail {:type String 6 | :args 7 | {:int_arg {:type Int} 8 | :string_arg {:type String}}}}}} 9 | :queries 10 | {:get_root {:type :Root 11 | :resolve :root-resolve}}} 12 | -------------------------------------------------------------------------------- /dev-resources/simple-federation.sdl: -------------------------------------------------------------------------------- 1 | type User @key(fields: "id") { 2 | id: Int! 3 | name: String! 4 | } 5 | 6 | type Query { 7 | user_by_id(id: Int!) : User 8 | } 9 | 10 | type Account @key(fields: "acct_number") { 11 | acct_number: String! 12 | name: String! 13 | } 14 | 15 | type Product @key(fields: "upc") @extends { 16 | upc: String! @external 17 | reviewed_by: User 18 | } -------------------------------------------------------------------------------- /dev-resources/star-wars-schema.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:episode 3 | {:description "The episodes of the original Star Wars trilogy." 4 | :values [:NEWHOPE :EMPIRE :JEDI]}} 5 | 6 | :interfaces 7 | {:character 8 | {:fields {:id {:type String} 9 | :name {:type String} 10 | :appears_in {:type (list :episode)} 11 | :friends {:type (list :character)}}}} 12 | 13 | :objects 14 | {:droid 15 | {:implements [:character] 16 | :fields {:id {:type String} 17 | :name {:type String} 18 | :appears_in {:type (list :episode)} 19 | :friends {:type (list :character) 20 | :resolve :friends} 21 | :primary_function {:type (list String)}}} 22 | 23 | :human 24 | {:implements [:character] 25 | :fields {:id {:type String} 26 | :name {:type String} 27 | :appears_in {:type (list :episode)} 28 | :friends {:type (list :character) 29 | :resolve :friends} 30 | :home_planet {:type String}}}} 31 | 32 | :queries 33 | {:hero {:type (non-null :character) 34 | :args {:episode {:type :episode}} 35 | :resolve :hero} 36 | 37 | :human {:type (non-null :human) 38 | :args {:id {:type String 39 | :default-value "1001"}} 40 | :resolve :human} 41 | 42 | :droid {:type :droid 43 | :args {:id {:type String 44 | :default-value "2001"}} 45 | :resolve :droid}}} 46 | -------------------------------------------------------------------------------- /dev-resources/subscription-selection.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Instant 3 | {:fields {:hour {:type (non-null Int)} 4 | :minute {:type (non-null Int)} 5 | :second {:type (non-null Int)}}} 6 | 7 | :Subscription 8 | {:fields {:time_from 9 | {:type :Instant 10 | :args {:when {:type (non-null String)} 11 | :interval {:type Int 12 | :default-value 60}}}}}}} -------------------------------------------------------------------------------- /dev-resources/subscriptions-schema.edn: -------------------------------------------------------------------------------- 1 | {:directive-defs 2 | {:instrument {:locations #{:field-definition}}} 3 | 4 | :objects 5 | {:LogEvent 6 | {:fields 7 | {:severity {:type String} 8 | :message {:type String}}}} 9 | 10 | :subscriptions 11 | {:logs 12 | {:type :LogEvent 13 | :args {:severity {:type String} 14 | :fakeError {:type Boolean 15 | :default false}} 16 | :stream :stream-logs} 17 | 18 | :directive_logs 19 | {:type :LogEvent 20 | :directives [{:directive-type :instrument}] 21 | :args {:severity {:type String} 22 | :fakeError {:type Boolean 23 | :default false}} 24 | :stream :stream-logs}}} 25 | -------------------------------------------------------------------------------- /dev-resources/summarize-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Toy 3 | {:fields {:name {:type String} 4 | :packaging {:type :Packaging} 5 | :aisle {:type Int}}} 6 | :Truck 7 | {:fields {:model {:type String} 8 | :dealership {:type Dealership} 9 | :cost {:type Int}}} 10 | 11 | :Dealership 12 | {:fields {:name {:type String} 13 | :address {:type String} 14 | :city {:type String} 15 | :state {:type String} 16 | :postal_code {:type String}}}} 17 | 18 | :enums 19 | {:Packaging 20 | {:values [plastic cardboard mixed]}} 21 | 22 | :unions 23 | {:Product {:members [:Toy :Truck]}} 24 | 25 | :queries 26 | {:toy_by_name {:type Toy 27 | :resolve :placeholder 28 | :args {:name {:type String}}} 29 | 30 | :truck_by_model {:type Truck 31 | :resolve :placeholder 32 | :args {:model {:type String}}} 33 | 34 | :product_by_string {:type Product 35 | :resolve :placeholder 36 | :args {:match {:type String}}}}} 37 | -------------------------------------------------------------------------------- /dev-resources/timing-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Fast 3 | {:fields {:simple {:type String} 4 | :slow {:type :Slow 5 | :resolve :resolve-slow}}} 6 | :Slow 7 | {:fields {:simple {:type String}}}} 8 | 9 | :queries 10 | {:root {:type (non-null Fast) 11 | :args {:delay {:type Int} 12 | :value {:type String 13 | :default-value "fast!"} 14 | :nested_value {:type String 15 | :default-value "slow!!"}} 16 | :resolve :resolve-fast}}} 17 | -------------------------------------------------------------------------------- /dev-resources/tracing_demo.clj: -------------------------------------------------------------------------------- 1 | (ns tracing-demo 2 | (:require 3 | [com.walmartlabs.lacinia :as lacinia] 4 | [com.walmartlabs.lacinia.tracing :as tracing] 5 | [com.walmartlabs.lacinia-test :as lacinia-test] 6 | [com.walmartlabs.lacinia.resolve :as resolve] 7 | [com.walmartlabs.test-utils :refer [simplify]]) 8 | (:import (java.util.concurrent ThreadPoolExecutor TimeUnit SynchronousQueue))) 9 | 10 | (def star-wars-schema lacinia-test/default-schema) 11 | 12 | (comment 13 | (let [queue (SynchronousQueue.) 14 | thread-pool (ThreadPoolExecutor. 5 10 1 TimeUnit/SECONDS queue)] 15 | ;; Ensure all core thread are started and ready, however, for the tiny 16 | ;; resolvers in the example, it's hard to show parallel behavior. Need some I/O. 17 | (while (.prestartCoreThread thread-pool)) 18 | (try 19 | (binding [resolve/*callback-executor* thread-pool] 20 | 21 | (simplify 22 | (lacinia/execute 23 | star-wars-schema " 24 | { 25 | luke: human(id: \"1000\") { friends { name }} 26 | leia: human(id: \"1003\") { name } 27 | }" 28 | nil 29 | (tracing/enable-tracing nil)))) 30 | (finally 31 | (.shutdown thread-pool)))) 32 | 33 | 34 | ) -------------------------------------------------------------------------------- /dev-resources/typename-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:man 3 | {:fields {:name {:type String}}} 4 | 5 | :machine 6 | {:fields {:serial {:type String}}}} 7 | 8 | :unions 9 | {:all {:members [:man :machine]}} 10 | 11 | :queries 12 | {:get_man 13 | {:type :man 14 | :resolve :get-man} 15 | 16 | :get_machine 17 | {:type :machine 18 | :resolve :get-machine} 19 | 20 | :get_all 21 | {:type (list :all) 22 | :resolve :get-all}}} 23 | -------------------------------------------------------------------------------- /dev-resources/union-query-root-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:version {:type String 3 | :resolve :queries/version}} 4 | 5 | :objects 6 | {:Barney 7 | {:fields 8 | {:last_name {:type String 9 | :resolve :Barney/last-name}}} 10 | 11 | :Fred 12 | {:fields 13 | {:family_name {:type String 14 | :resolve :Fred/family-name}}}} 15 | 16 | :unions 17 | {:Queries {:members [:Fred :Barney]}} 18 | 19 | :roots {:query :Queries}} 20 | -------------------------------------------------------------------------------- /dev-resources/unions.sdl: -------------------------------------------------------------------------------- 1 | type Human { name : String } 2 | 3 | type Agent { id : Int 4 | alias : String } 5 | 6 | union Combatant = Human | Agent 7 | -------------------------------------------------------------------------------- /dev-resources/unknown-argument-type-schema.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:example 3 | {:type String 4 | :resolve :example 5 | :args 6 | ;; Note: UUID is not declared, but it inside the `list` qualifier: 7 | {:id {:type (list UUID)}}}}} 8 | -------------------------------------------------------------------------------- /dev-resources/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [criterium.core :as c] 4 | [clojure.java.io :as io] 5 | com.walmartlabs.lacinia.expound 6 | [com.walmartlabs.lacinia.schema :as schema] 7 | [com.walmartlabs.lacinia :as l] 8 | [clojure.spec.alpha :as s] 9 | [net.lewisship.trace :as trace] 10 | [expound.alpha :as expound])) 11 | 12 | (require 'com.walmartlabs.lacinia.expound) 13 | 14 | (alter-var-root #'s/*explain-out* (constantly expound/printer)) 15 | 16 | 17 | (defn trace-demo 18 | [] 19 | (->> (range 10) 20 | (trace/trace>> :values %) 21 | (filter even?) 22 | (take 2))) 23 | 24 | (comment 25 | (do 26 | (set! *warn-on-reflection* true) 27 | (trace/setup-default) 28 | (trace/trace :msg "Tracing is enabled")) 29 | 30 | (trace-demo) 31 | 32 | (trace/set-enable-trace! false) 33 | (trace/set-compile-trace! false) 34 | 35 | ) 36 | 37 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build* 2 | *.pyc 3 | -------------------------------------------------------------------------------- /docs/_examples/async-example.edn: -------------------------------------------------------------------------------- 1 | (require 2 | '[com.walmartlabs.lacinia.resolve :as resolve]) 3 | 4 | (defn ^:private get-user 5 | [connection user-id] 6 | ...) 7 | 8 | (defn resolve-user 9 | [context args _] 10 | (let [{:keys [id]} args 11 | {:keys [connection]} context 12 | result (resolve/resolve-promise)] 13 | (.start (Thread. 14 | #(try 15 | (resolve/deliver! result (get-user connection id)) 16 | (catch Throwable t 17 | (resolve/deliver! result nil 18 | {:message (str "Exception: " (.getMessage t))}))))) 19 | 20 | result)) 21 | -------------------------------------------------------------------------------- /docs/_examples/compile-schema.clj: -------------------------------------------------------------------------------- 1 | (ns org.example.schema 2 | (:require 3 | [clojure.edn :as edn] 4 | [clojure.java.io :as io] 5 | [com.walmartlabs.lacinia.schema :as schema] 6 | [com.walmartlabs.lacinia.util :as util] 7 | [org.example.db :as db])) 8 | 9 | (defn star-wars-schema 10 | [] 11 | (-> (io/resource "star-wars-schema.edn") 12 | slurp 13 | edn/read-string 14 | (util/inject-resolvers {:Query/hero db/resolve-hero 15 | :Query/human db/resolve-human 16 | :Query/droid db/resolve-droid 17 | :Human/friends db/resolve-friends 18 | :Droid/friends db/resolve-friends}) 19 | schema/compile)) 20 | 21 | -------------------------------------------------------------------------------- /docs/_examples/custom-scalars.edn: -------------------------------------------------------------------------------- 1 | (require 2 | '[clojure.spec.alpha :as s] 3 | '[com.walmartlabs.lacinia :as g] 4 | '[com.walmartlabs.lacinia.schema :as schema]) 5 | 6 | (import 7 | java.text.SimpleDateFormat 8 | java.util.Date) 9 | 10 | (def date-formatter 11 | "Used by custom scalar :Date." 12 | (SimpleDateFormat. "yyyy-MM-dd")) 13 | 14 | (def schema 15 | (schema/compile 16 | {:scalars 17 | {:Date 18 | {:parse #(when (string? %) 19 | (try 20 | (.parse date-formatter %) 21 | (catch Throwable _ 22 | nil))) 23 | :serialize #(try 24 | (.format date-formatter %) 25 | (catch Throwable _ 26 | nil))}} 27 | 28 | :queries 29 | {:today 30 | {:type :Date 31 | :resolve (fn [ctx args v] (Date.))} 32 | :echo 33 | {:type :Date 34 | :args {:input {:type :Date}} 35 | :resolve (fn [ctx {:keys [input]} v] input)}}})) 36 | 37 | (g/execute schema "{today}" nil nil) 38 | => {:data {:today "2018-11-21"}} 39 | (g/execute schema "{ echo(input: \"2018-11-22\") }" nil nil) 40 | => {:data {:echo "2018-11-22"}} 41 | (g/execute schema "{ echo(input: \"thanksgiving\") }" nil nil) 42 | => 43 | {:errors [{:message "Exception applying arguments to field `echo': For argument `input', unable to convert \"thanksgiving\" to scalar type `Date'.", 44 | :locations [{:line 1, :column 3}], 45 | :extensions {:field :echo, :argument :input, :value "thanksgiving", :type-name :Date}}]} 46 | 47 | -------------------------------------------------------------------------------- /docs/_examples/directive-defs.edn: -------------------------------------------------------------------------------- 1 | {:directive-defs 2 | {:access 3 | {:locations #{:field-definition} 4 | :args {:role {:type (non-null String)}}}} 5 | 6 | :objects 7 | {:Query 8 | {:fields 9 | {:ultimateAnswer 10 | {:type String 11 | :directives [{:directive-type :access 12 | :directive-args {:role "deep-thought"}}]}}}}} 13 | -------------------------------------------------------------------------------- /docs/_examples/enum-definition-description.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:Episode 3 | {:description "The episodes of the original Star Wars trilogy." 4 | :values [{:enum-value :NEWHOPE :description "The first one you saw."} 5 | {:enum-value :EMPIRE :description "The good one."} 6 | {:enum-value :JEDI :description "The one with the killer teddy bears."}]}}} 7 | -------------------------------------------------------------------------------- /docs/_examples/enum-definition.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:Episode 3 | {:description "The episodes of the original Star Wars trilogy." 4 | :values [:NEWHOPE :EMPIRE :JEDI]}}} 5 | -------------------------------------------------------------------------------- /docs/_examples/errors-result.edn: -------------------------------------------------------------------------------- 1 | {:data {:hero {:friends [nil nil nil]}} 2 | :errors [{:message "Non-nullable field was null." 3 | :locations [{:line 1 4 | :column 20}] 5 | :path [:hero :friends :arch_enemy]}]} 6 | -------------------------------------------------------------------------------- /docs/_examples/extension-result.edn: -------------------------------------------------------------------------------- 1 | {:data 2 | {:user 3 | {:id "thx1138" 4 | :name "Thex"}} 5 | :extensions 6 | {:total-db-access-ms 3}} 7 | -------------------------------------------------------------------------------- /docs/_examples/extension.edn: -------------------------------------------------------------------------------- 1 | (require '[com.walmartlabs.lacinia.resolve :refer [with-extensions]]) 2 | 3 | (defn resolve-user 4 | [context args value] 5 | (let [start-ms (System/currentTimeMillis) 6 | user-data (get-user-data (:id args)) 7 | elapsed-ms (- (System/currentTimeMillis) start-ms)] 8 | (with-extensions user-data 9 | update :total-db-access-ms (fnil + 0) elapsed-ms))) 10 | -------------------------------------------------------------------------------- /docs/_examples/fed/external.gql: -------------------------------------------------------------------------------- 1 | type User @extends @key(fields: "id") { 2 | id: String! @external 3 | favoriteProducts: [Product] 4 | } 5 | 6 | type Product @key(fields: "upc") { 7 | upc: String! 8 | name: String! 9 | price: Int! 10 | } 11 | 12 | type Query { 13 | productByUpc(upc: String!) : Product 14 | } 15 | -------------------------------------------------------------------------------- /docs/_examples/fed/internal.gql: -------------------------------------------------------------------------------- 1 | type User @key(fields: "id") { 2 | id: String! 3 | name: String! 4 | } 5 | 6 | type Query { 7 | userById(id:String!) : User 8 | } 9 | -------------------------------------------------------------------------------- /docs/_examples/fed/products.edn: -------------------------------------------------------------------------------- 1 | (ns products.server 2 | (:require 3 | [io.pedestal.http :as http] 4 | [clojure.java.io :as io] 5 | [com.walmartlabs.lacinia.pedestal2 :as lp] 6 | [com.walmartlabs.lacinia.parser.schema :refer [parse-schema]] 7 | [com.walmartlabs.lacinia.schema :as schema] 8 | [com.walmartlabs.lacinia.util :as util])) 9 | 10 | (defn resolve-users-external 11 | [_ _ reps] 12 | (for [{:keys [id]} reps] 13 | (schema/tag-with-type {:id id} 14 | :User))) 15 | 16 | (defn get-product-by-upc 17 | [context upc] 18 | ;; Peform DB query here, return map with :upc, :name, :price 19 | ) 20 | 21 | (defn get-favorite-products-for-user 22 | [context user-id] 23 | ;; Perform DB query here, return seq of maps with :upc, :name, :price 24 | ) 25 | 26 | (defn resolve-products-internal 27 | [context _ reps] 28 | (for [{:keys [upc]} reps 29 | :let [product (get-product-by-upc context upc)]] 30 | (schema/tag-with-type product :Product))) 31 | 32 | (defn resolve-product-by-upc 33 | [context {:keys [upc]} _] 34 | (get-product-by-upc context upc)) 35 | 36 | (defn resolve-favorite-products 37 | [context _ user] 38 | (let [{:keys [id]} user] 39 | (get-favorite-products-for-user context id))) 40 | 41 | (defn products-schema 42 | [] 43 | (-> "products.gql" 44 | io/resource 45 | slurp 46 | (parse-schema {:federation {:entity-resolvers {:Product resolve-products-internal 47 | :User resolve-users-external}}}) 48 | (util/inject-resolvers {:Query/productByUpc #'resolve-product-by-upc 49 | :User/favoriteProducts #'resolve-favorite-products}) 50 | schema/compile)) 51 | 52 | (defn start 53 | [] 54 | (-> (products-schema) 55 | lp/default-service 56 | http/create-server 57 | http/start)) 58 | -------------------------------------------------------------------------------- /docs/_examples/fed/query.gql: -------------------------------------------------------------------------------- 1 | { 2 | userById(id: "124c41") { 3 | name 4 | favoriteProducts { upc name price } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/_examples/fed/schema.gql: -------------------------------------------------------------------------------- 1 | scalar _Any 2 | scalar _FieldSet 3 | 4 | # a union of all types that use the @key directive 5 | union _Entity 6 | 7 | extend type Query { 8 | _entities(representations: [_Any!]!): [_Entity]! 9 | } -------------------------------------------------------------------------------- /docs/_examples/hero-query-response.edn: -------------------------------------------------------------------------------- 1 | {:data 2 | {:hero 3 | {:id "2001" 4 | :name "R2-D2" 5 | :friends [{:name "Luke Sykwalker"}, 6 | {:name "Han Solo"}, 7 | {:name "Leia Organa"}]}}} 8 | -------------------------------------------------------------------------------- /docs/_examples/interface-definition.edn: -------------------------------------------------------------------------------- 1 | {:interfaces 2 | {:Named 3 | {:fields {:name {:type String}}}} 4 | 5 | :objects 6 | {:Person 7 | {:implements [:Named] 8 | :fields {:name {:type String} 9 | :age {:type Int}}} 10 | 11 | :Business 12 | {:implements [:Named] 13 | :fields {:name {:type String} 14 | :employee_count {:type Int}}}}} 15 | -------------------------------------------------------------------------------- /docs/_examples/invoke-streamer.edn: -------------------------------------------------------------------------------- 1 | (require 2 | '[com.walmartlabs.lacinia.executor :as executor] 3 | [com.walmartlabs.lacinia.parser :as parser] 4 | [com.walmartlabs.lacinia.constants :as constants]) 5 | 6 | (let [prepared-query (-> schema 7 | (parser/parse-query query) 8 | (parser/prepare-with-query-variables variables)) 9 | source-stream-callback (fn [data] 10 | ;; Do something with the data 11 | ;; e.g. send it to a websocket client 12 | ) 13 | cleanup-fn (executor/invoke-streamer 14 | {constants/parsed-query-key prepared-query} source-stream-callback)] 15 | ;; Do something with the cleanup-fn e.g. call it when a websocket connection is closed 16 | ) 17 | -------------------------------------------------------------------------------- /docs/_examples/mutable-context.edn: -------------------------------------------------------------------------------- 1 | (require '[com.walmartlabs.lacinia.resolve :as resolve]) 2 | 3 | (defn resolve-products 4 | [_ args _] 5 | (let [search-term (:search args)] 6 | (-> (perform-product-search args) 7 | (resolve/with-context {::search-term search-term})))) 8 | 9 | (defn resolve-highlighted-name 10 | [context _ product] 11 | (let [{:keys [::search-term]} context] 12 | (-> product :name (add-highlight search-term)))) 13 | -------------------------------------------------------------------------------- /docs/_examples/object-definition.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Product 3 | {:fields {:id {:type ID} 4 | :name {:type String} 5 | :sku {:type String} 6 | :keywords {:type (list String)}}}}} 7 | -------------------------------------------------------------------------------- /docs/_examples/object-tag.edn: -------------------------------------------------------------------------------- 1 | {:unions 2 | {:Searchable 3 | {:members [:Business :Employee]}} 4 | 5 | :objects 6 | {:Business 7 | {:fields 8 | {:id {:type ID} 9 | :name {:type String}} 10 | :tag com.example.data.Business} 11 | 12 | :Employee 13 | {:fields 14 | {:id {:type ID} 15 | :employer {:type :Business} 16 | :givenName {:type String} 17 | :familyName {:type String}} 18 | :tag com.example.data.Employee} 19 | 20 | :Query 21 | {:fields 22 | {:businesses 23 | {:type (list :Business)} 24 | 25 | :search 26 | {:type (list :Searchable)}}}}} 27 | 28 | -------------------------------------------------------------------------------- /docs/_examples/overview-exec-query.edn: -------------------------------------------------------------------------------- 1 | (require 2 | '[com.walmartlabs.lacinia :refer [execute]] 3 | '[org.example.schema :refer [star-wars-schema]]) 4 | 5 | (def compiled-schema (star-wars-schema)) 6 | 7 | (execute compiled-schema 8 | "query { human(id: \"1001\") { name }}" 9 | nil nil) 10 | => {:data {:human #ordered/map([:name "Darth Vader"])}} 11 | -------------------------------------------------------------------------------- /docs/_examples/parsed_sample_schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Character {:fields 3 | {:name {:type (non-null String) 4 | :description "Character name"} 5 | :episodes {:type (list :episode)}} 6 | :description "A Star Wars character"} 7 | :Query {:fields 8 | {:findAllInEpisode 9 | {:type (list :Character) 10 | :args 11 | {:episode 12 | {:type (non-null :episode) 13 | :description "Episode for which to find characters."}} 14 | :resolve :find-all-in-episode 15 | :description "Find all characters in the given episode"}}} 16 | :Mutation {:fields 17 | {:addCharacter 18 | {:type Boolean 19 | :args {:character {:type (non-null :CharacterArg)}} 20 | :resolve :add-character}}}} 21 | 22 | :input-objects 23 | {:CharacterArg {:fields 24 | {:name {:type (non-null String)} 25 | :episodes {:type (non-null (list :episode))}}}} 26 | 27 | :enums 28 | {:episode {:values [{:enum-value :NEWHOPE} 29 | {:enum-value :EMPIRE} 30 | {:enum-value :JEDI}]}} 31 | 32 | :roots 33 | {:query :Query 34 | :mutation :Mutation}} 35 | -------------------------------------------------------------------------------- /docs/_examples/query-def-var.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:Query 3 | {:fields 4 | {:hero 5 | {:type (non-null :Character) 6 | :args {:episode {:type :episode}}} 7 | 8 | :human 9 | {:type (non-null :Human) 10 | :args {:id {:type String 11 | :default-value "1001"}}}}}}} 12 | -------------------------------------------------------------------------------- /docs/_examples/query-def.edn: -------------------------------------------------------------------------------- 1 | {:queries 2 | {:hero 3 | {:type (non-null :Character) 4 | :args {:episode {:type :episode}}} 5 | 6 | :human 7 | {:type (non-null Hhuman) 8 | :args {:id {:type String 9 | :default-value "1001"}}}}} 10 | -------------------------------------------------------------------------------- /docs/_examples/resolve-method.edn: -------------------------------------------------------------------------------- 1 | (defn- make-accessor 2 | [method-sym] 3 | (let [method-name (name method-sym) 4 | arg-types (make-array Class 0) 5 | args (make-array Object 0)] 6 | (fn [value] 7 | (let [c (class value) 8 | method (.getMethod c method-name arg-types)] 9 | (.invoke method value args))))) 10 | 11 | (defn resolve-method 12 | [method-sym] 13 | (let [f (make-accessor method-sym)] 14 | (fn [_ _ value] 15 | (f value)))) 16 | 17 | ;; Later, when injecting resolvers ... 18 | 19 | (-> ... 20 | (utils/inject-resolvers {:MyObject/myField (resolve-method 'myField)}) 21 | (schema/compile)) 22 | -------------------------------------------------------------------------------- /docs/_examples/sample_schema.txt: -------------------------------------------------------------------------------- 1 | enum episode { 2 | NEWHOPE 3 | EMPIRE 4 | JEDI 5 | } 6 | 7 | type Character { 8 | name: String! 9 | episodes: [episode] 10 | } 11 | 12 | input CharacterArg { 13 | name: String! 14 | episodes: [episode]! 15 | } 16 | 17 | type Query { 18 | findAllInEpisode(episode: episode!) : [Character] 19 | } 20 | 21 | type Mutation { 22 | addCharacter(character: CharacterArg!) : Boolean 23 | } 24 | -------------------------------------------------------------------------------- /docs/_examples/selections-tree.edn: -------------------------------------------------------------------------------- 1 | {:Character/friends [{:selections 2 | {:Character/name [nil]}}] 3 | :Human/homePlanet [nil] 4 | :Character/name [nil]} 5 | -------------------------------------------------------------------------------- /docs/_examples/selects-field.edn: -------------------------------------------------------------------------------- 1 | 2 | (require 3 | '[com.walmartlabs.lacinia.executor :as executor]) 4 | 5 | (defn resolve-hero 6 | [context args _] 7 | (if (executor/selects-field? context :Character/friends) 8 | (fetch-hero-with-friends args) 9 | (fetch-hero args))) 10 | -------------------------------------------------------------------------------- /docs/_examples/star-wars-schema.edn: -------------------------------------------------------------------------------- 1 | {:enums 2 | {:Episode 3 | {:description "The episodes of the original Star Wars trilogy." 4 | :values [:NEWHOPE :EMPIRE :JEDI]}} 5 | 6 | :interfaces 7 | {:Character 8 | {:fields {:id {:type String} 9 | :name {:type String} 10 | :appearsIn {:type (list :Episode)} 11 | :friends {:type (list :Character)}}}} 12 | 13 | :objects 14 | {:Droid 15 | {:implements [:Character] 16 | :fields {:id {:type String} 17 | :name {:type String} 18 | :appearsIn {:type (list :Episode)} 19 | :friends {:type (list :Character) 20 | :resolve :friends} 21 | :primaryFunction {:type (list String)}}} 22 | 23 | :Human 24 | {:implements [:Character] 25 | :fields {:id {:type String} 26 | :name {:type String} 27 | :appearsIn {:type (list :Episode)} 28 | :friends {:type (list :Character)} 29 | :home_planet {:type String}}} 30 | 31 | :Query 32 | {:fields 33 | {:hero {:type (non-null :Character) 34 | :args {:episode {:type :Episode}}} 35 | 36 | :human {:type (non-null :Human) 37 | :args {:id {:type String 38 | :default-value "1001"}}} 39 | 40 | :droid {:type :Droid 41 | :args {:id {:type String 42 | :default-value "2001"}}}}}}} 43 | -------------------------------------------------------------------------------- /docs/_examples/subs-schema.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:LogEvent 3 | {:fields 4 | {:severity {:type String} 5 | :message {:type String}}} 6 | 7 | :Subscription 8 | {:fields 9 | {:logs 10 | {:type :LogEvent 11 | :args {:severity {:type String}} 12 | :stream :stream-logs}}}}} 13 | -------------------------------------------------------------------------------- /docs/_examples/subs-streamer.edn: -------------------------------------------------------------------------------- 1 | (defn log-message-streamer 2 | [context args source-stream] 3 | ;; Create an object for the subscription. 4 | (let [subscription (create-log-subscription)] 5 | (on-publish subscription 6 | (fn [log-event] 7 | (-> log-event :payload source-stream))) 8 | ;; Return a function to cleanup the subscription 9 | #(stop-log-subscription subscription))) 10 | -------------------------------------------------------------------------------- /docs/_examples/tagged-resolver.edn: -------------------------------------------------------------------------------- 1 | (require '[com.walmartlabs.lacinia.resolve :refer [resolve-as ResolverResult]]) 2 | 3 | (def tagged-resolver 4 | ^ResolverResult (fn [context args value] 5 | (resolve-as ...))) 6 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/build-1.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:refer-clojure :exclude [test]) 3 | (:require [org.corfield.build :as bb])) 4 | 5 | (def lib 'net.clojars.my/clojure-game-geek) 6 | (def version "0.1.0-SNAPSHOT") 7 | (def main 'my.clojure-game-geek) 8 | 9 | (defn test "Run the tests." [opts] 10 | (bb/run-tests (assoc opts :aliases [:dev]))) 11 | 12 | (defn ci "Run the CI pipeline of tests (and build the uberjar)." [opts] 13 | (-> opts 14 | (assoc :lib lib :version version :main main) 15 | (bb/run-tests) 16 | (bb/clean) 17 | (bb/uber))) 18 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/cgg-data-1.edn: -------------------------------------------------------------------------------- 1 | {:games 2 | [{:id "1234" 3 | :name "Zertz" 4 | :summary "Two player abstract with forced moves and shrinking board" 5 | :minPlayers 2 6 | :maxPlayers 2} 7 | {:id "1235" 8 | :name "Dominion" 9 | :summary "Created the deck-building genre; zillions of expansions" 10 | :minPlayers 2} 11 | {:id "1236" 12 | :name "Tiny Epic Galaxies" 13 | :summary "Fast dice-based sci-fi space game with a bit of chaos" 14 | :minPlayers 1 15 | :maxPlayers 4} 16 | {:id "1237" 17 | :name "7 Wonders: Duel" 18 | :summary "Tense, quick card game of developing civilizations" 19 | :minPlayers 2 20 | :maxPlayers 2}]} 21 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/cgg-data-2.edn: -------------------------------------------------------------------------------- 1 | {:games 2 | [{:id "1234" 3 | :name "Zertz" 4 | :summary "Two player abstract with forced moves and shrinking board" 5 | :designers #{"200"} 6 | :minPlayers 2 7 | :maxPlayers 2} 8 | {:id "1235" 9 | :name "Dominion" 10 | :summary "Created the deck-building genre; zillions of expansions" 11 | :designers #{"204"} 12 | :minPlayers 2} 13 | {:id "1236" 14 | :name "Tiny Epic Galaxies" 15 | :summary "Fast dice-based sci-fi space game with a bit of chaos" 16 | :designers #{"203"} 17 | :minPlayers 1 18 | :maxPlayers 4} 19 | {:id "1237" 20 | :name "7 Wonders: Duel" 21 | :summary "Tense, quick card game of developing civilizations" 22 | :designers #{"201" "202"} 23 | :minPlayers 2 24 | :maxPlayers 2}] 25 | 26 | :designers 27 | [{:id "200" 28 | :name "Kris Burm" 29 | :url "http://www.gipf.com/project_gipf/burm/burm.html"} 30 | {:id "201" 31 | :name "Antoine Bauza" 32 | :url "http://www.antoinebauza.fr/"} 33 | {:id "202" 34 | :name "Bruno Cathala" 35 | :url "http://www.brunocathala.com/"} 36 | {:id "203" 37 | :name "Scott Almes"} 38 | {:id "204" 39 | :name "Donald X. Vaccarino"}]} 40 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/cgg-data-3.edn: -------------------------------------------------------------------------------- 1 | {:games 2 | [{:id "1234" 3 | :name "Zertz" 4 | :summary "Two player abstract with forced moves and shrinking board" 5 | :designers #{"200"} 6 | :minPlayers 2 7 | :maxPlayers 2} 8 | {:id "1235" 9 | :name "Dominion" 10 | :summary "Created the deck-building genre; zillions of expansions" 11 | :designers #{"204"} 12 | :minPlayers 2} 13 | {:id "1236" 14 | :name "Tiny Epic Galaxies" 15 | :summary "Fast dice-based sci-fi space game with a bit of chaos" 16 | :designers #{"203"} 17 | :minPlayers 1 18 | :maxPlayers 4} 19 | {:id "1237" 20 | :name "7 Wonders: Duel" 21 | :summary "Tense, quick card game of developing civilizations" 22 | :designers #{"201" "202"} 23 | :minPlayers 2 24 | :maxPlayers 2}] 25 | 26 | :members 27 | [{:id "37" 28 | :name "curiousattemptbunny"} 29 | {:id "1410" 30 | :name "bleedingedge"} 31 | {:id "2812" 32 | :name "missyo"}] 33 | 34 | :ratings 35 | [{:member-id "37" :game-id "1234" :rating 3} 36 | {:member-id "1410" :game-id "1234" :rating 5} 37 | {:member-id "1410" :game-id "1236" :rating 4} 38 | {:member-id "1410" :game-id "1237" :rating 4} 39 | {:member-id "2812" :game-id "1237" :rating 4} 40 | {:member-id "37" :game-id "1237" :rating 5}] 41 | 42 | :designers 43 | [{:id "200" 44 | :name "Kris Burm" 45 | :url "http://www.gipf.com/project_gipf/burm/burm.html"} 46 | {:id "201" 47 | :name "Antoine Bauza" 48 | :url "http://www.antoinebauza.fr/"} 49 | {:id "202" 50 | :name "Bruno Cathala" 51 | :url "http://www.brunocathala.com/"} 52 | {:id "203" 53 | :name "Scott Almes"} 54 | {:id "204" 55 | :name "Donald X. Vaccarino"}]} 56 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/cgg-schema-1.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:BoardGame 3 | {:description "A physical or virtual board game." 4 | :fields 5 | {:id {:type (non-null ID)} 6 | :name {:type (non-null String)} 7 | :summary {:type String 8 | :description "A one-line summary of the game."} 9 | :description {:type String 10 | :description "A long-form description of the game."} 11 | :minPlayers {:type Int 12 | :description "The minimum number of players the game supports."} 13 | :maxPlayers {:type Int 14 | :description "The maximum number of players the game supports."} 15 | :playTime {:type Int 16 | :description "Play time, in minutes, for a typical game."}}} 17 | 18 | :Query 19 | {:fields 20 | {:gameById 21 | {:type :BoardGame 22 | :description "Access a BoardGame by its unique id, if it exists." 23 | :args 24 | {:id {:type ID}}}}}}} 25 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/cgg-schema-2.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:BoardGame 3 | {:description "A physical or virtual board game." 4 | :fields 5 | {:id {:type ID} 6 | :name {:type (non-null String)} 7 | :summary {:type String 8 | :description "A one-line summary of the game."} 9 | :description {:type String 10 | :description "A long-form description of the game."} 11 | :min_players {:type Int 12 | :description "The minimum number of players the game supports."} 13 | :max_players {:type Int 14 | :description "The maximum number of players the game supports."} 15 | :play_time {:type Int 16 | :description "Play time, in minutes, for a typical game."}}}} 17 | 18 | :queries 19 | {:game_by_id 20 | {:type :BoardGame 21 | :description "Access a BoardGame by its unique id, if it exists." 22 | :args 23 | {:id {:type ID}} 24 | :resolve :query/game-by-id}}} 25 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/cgg-schema-3.edn: -------------------------------------------------------------------------------- 1 | {:objects 2 | {:BoardGame 3 | {:description "A physical or virtual board game." 4 | :fields 5 | {:id {:type (non-null ID)} 6 | :name {:type (non-null String)} 7 | :summary {:type String 8 | :description "A one-line summary of the game."} 9 | :description {:type String 10 | :description "A long-form description of the game."} 11 | :designers {:type (non-null (list :Designer)) 12 | :description "Designers who contributed to the game."} 13 | :minPlayers {:type Int 14 | :description "The minimum number of players the game supports."} 15 | :maxPlayers {:type Int 16 | :description "The maximum number of players the game supports."} 17 | :playTime {:type Int 18 | :description "Play time, in minutes, for a typical game."}}} 19 | 20 | :Designer 21 | {:description "A person who may have contributed to a board game design." 22 | :fields 23 | {:id {:type (non-null ID)} 24 | :name {:type (non-null String)} 25 | :url {:type String 26 | :description "Home page URL, if known."} 27 | :games {:type (non-null (list :BoardGame)) 28 | :description "Games designed by this designer."}}} 29 | 30 | :Query 31 | {:fields 32 | {:gameById 33 | {:type :BoardGame 34 | :description "Access a BoardGame by its unique id, if it exists." 35 | :args 36 | {:id {:type ID}}}}}}} 37 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/db-1.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.db 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io] 4 | [com.stuartsierra.component :as component])) 5 | 6 | (defrecord ClojureGameGeekDb [data] 7 | 8 | component/Lifecycle 9 | 10 | (start [this] 11 | (assoc this :data (-> (io/resource "cgg-data.edn") 12 | slurp 13 | edn/read-string 14 | atom))) 15 | 16 | (stop [this] 17 | (assoc this :data nil))) 18 | 19 | (defn find-game-by-id 20 | [db game-id] 21 | (->> db 22 | :data 23 | deref 24 | :games 25 | (filter #(= game-id (:id %))) 26 | first)) 27 | 28 | (defn find-member-by-id 29 | [db member-id] 30 | (->> db 31 | :data 32 | deref 33 | :members 34 | (filter #(= member-id (:id %))) 35 | first)) 36 | 37 | (defn list-designers-for-game 38 | [db game-id] 39 | (let [designers (:designers (find-game-by-id db game-id))] 40 | (->> db 41 | :data 42 | deref 43 | :designers 44 | (filter #(contains? designers (:id %)))))) 45 | 46 | (defn list-games-for-designer 47 | [db designer-id] 48 | (->> db 49 | :data 50 | deref 51 | :games 52 | (filter #(-> % :designers (contains? designer-id))))) 53 | 54 | (defn list-ratings-for-game 55 | [db game-id] 56 | (->> db 57 | :data 58 | deref 59 | :ratings 60 | (filter #(= game-id (:game-id %))))) 61 | 62 | (defn list-ratings-for-member 63 | [db member-id] 64 | (->> db 65 | :data 66 | deref 67 | :ratings 68 | (filter #(= member-id (:member-id %))))) 69 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/db-2.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.db 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io] 4 | [com.stuartsierra.component :as component])) 5 | 6 | (defrecord ClojureGameGeekDb [data] 7 | 8 | component/Lifecycle 9 | 10 | (start [this] 11 | (assoc this :data (-> (io/resource "cgg-data.edn") 12 | slurp 13 | edn/read-string 14 | atom))) 15 | 16 | (stop [this] 17 | (assoc this :data nil))) 18 | 19 | (defn find-game-by-id 20 | [db game-id] 21 | (->> db 22 | :data 23 | deref 24 | :games 25 | (filter #(= game-id (:id %))) 26 | first)) 27 | 28 | (defn find-member-by-id 29 | [db member-id] 30 | (->> db 31 | :data 32 | deref 33 | :members 34 | (filter #(= member-id (:id %))) 35 | first)) 36 | 37 | (defn list-designers-for-game 38 | [db game-id] 39 | (let [designers (:designers (find-game-by-id db game-id))] 40 | (->> db 41 | :data 42 | deref 43 | :designers 44 | (filter #(contains? designers (:id %)))))) 45 | 46 | (defn list-games-for-designer 47 | [db designer-id] 48 | (->> db 49 | :data 50 | deref 51 | :games 52 | (filter #(-> % :designers (contains? designer-id))))) 53 | 54 | (defn list-ratings-for-game 55 | [db game-id] 56 | (->> db 57 | :data 58 | deref 59 | :ratings 60 | (filter #(= game-id (:game-id %))))) 61 | 62 | (defn list-ratings-for-member 63 | [db member-id] 64 | (->> db 65 | :data 66 | deref 67 | :ratings 68 | (filter #(= member-id (:member-id %))))) 69 | 70 | (defn ^:private apply-game-rating 71 | [game-ratings game-id member-id rating] 72 | (->> game-ratings 73 | (remove #(and (= game-id (:game-id %)) 74 | (= member-id (:member-id %)))) 75 | (cons {:game-id game-id 76 | :member-id member-id 77 | :rating rating}))) 78 | 79 | (defn upsert-game-rating 80 | "Adds a new game rating, or changes the value of an existing game rating." 81 | [db game-id member-id rating] 82 | (-> db 83 | :data 84 | (swap! update :ratings apply-game-rating game-id member-id rating))) 85 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/deps-1.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"}} 3 | :aliases 4 | {:run-m {:main-opts ["-m" "my.clojure-game-geek"]} 5 | :run-x {:ns-default my.clojure-game-geek 6 | :exec-fn greet 7 | :exec-args {:name "Clojure"}} 8 | :build {:deps {io.github.seancorfield/build-clj 9 | {:git/tag "v0.8.2" :git/sha "0ffdb4c" 10 | ;; since we're building an app uberjar, we do not 11 | ;; need deps-deploy for clojars.org deployment: 12 | :deps/root "slim"}} 13 | :ns-default build} 14 | :test {:extra-paths ["test"] 15 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 16 | io.github.cognitect-labs/test-runner 17 | {:git/tag "v0.5.0" :git/sha "48c3c67"}}}}} 18 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/deps-2.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | com.walmartlabs/lacinia {:mvn/version "1.2-alpha-4"}} 4 | :aliases 5 | {:run-m {:main-opts ["-m" "my.clojure-game-geek"]} 6 | :run-x {:ns-default my.clojure-game-geek 7 | :exec-fn greet 8 | :exec-args {:name "Clojure"}} 9 | :build {:deps {io.github.seancorfield/build-clj 10 | {:git/tag "v0.8.2" :git/sha "0ffdb4c" 11 | ;; since we're building an app uberjar, we do not 12 | ;; need deps-deploy for clojars.org deployment: 13 | :deps/root "slim"}} 14 | :ns-default build} 15 | :test {:extra-paths ["test"] 16 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 17 | io.github.cognitect-labs/test-runner 18 | {:git/tag "v0.5.0" :git/sha "48c3c67"}}}}} 19 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/deps-3.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | com.walmartlabs/lacinia {:mvn/version "1.2-alpha-4"}} 4 | :aliases 5 | {:run-m {:main-opts ["-m" "my.clojure-game-geek"]} 6 | :run-x {:ns-default my.clojure-game-geek 7 | :exec-fn greet 8 | :exec-args {:name "Clojure"}} 9 | :build {:deps {io.github.seancorfield/build-clj 10 | {:git/tag "v0.8.2" :git/sha "0ffdb4c" 11 | ;; since we're building an app uberjar, we do not 12 | ;; need deps-deploy for clojars.org deployment: 13 | :deps/root "slim"}} 14 | :ns-default build} 15 | :dev {:extra-paths ["dev-resources"]} 16 | :test {:extra-paths ["test"] 17 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 18 | io.github.cognitect-labs/test-runner 19 | {:git/tag "v0.5.0" :git/sha "48c3c67"}}}}} 20 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/deps-4.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | com.walmartlabs/lacinia {:mvn/version "1.2-alpha-4"} 4 | com.walmartlabs/lacinia-pedestal {:mvn/version "1.1"} 5 | io.aviso/logging {:mvn/version "1.0"}} 6 | :aliases 7 | {:run-m {:main-opts ["-m" "my.clojure-game-geek"]} 8 | :run-x {:ns-default my.clojure-game-geek 9 | :exec-fn greet 10 | :exec-args {:name "Clojure"}} 11 | :build {:deps {io.github.seancorfield/build-clj 12 | {:git/tag "v0.8.2" :git/sha "0ffdb4c" 13 | ;; since we're building an app uberjar, we do not 14 | ;; need deps-deploy for clojars.org deployment: 15 | :deps/root "slim"}} 16 | :ns-default build} 17 | :dev {:extra-paths ["dev-resources"]} 18 | :test {:extra-paths ["test"] 19 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 20 | io.github.cognitect-labs/test-runner 21 | {:git/tag "v0.5.0" :git/sha "48c3c67"}}}}} 22 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/deps-5.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | com.walmartlabs/lacinia {:mvn/version "1.2-alpha-4"} 4 | com.walmartlabs/lacinia-pedestal {:mvn/version "1.1"} 5 | com.stuartsierra/component {:mvn/version "1.1.0"} 6 | io.aviso/logging {:mvn/version "1.0"}} 7 | :aliases 8 | {:run-m {:main-opts ["-m" "my.clojure-game-geek"]} 9 | :run-x {:ns-default my.clojure-game-geek 10 | :exec-fn greet 11 | :exec-args {:name "Clojure"}} 12 | :build {:deps {io.github.seancorfield/build-clj 13 | {:git/tag "v0.8.2" :git/sha "0ffdb4c" 14 | ;; since we're building an app uberjar, we do not 15 | ;; need deps-deploy for clojars.org deployment: 16 | :deps/root "slim"}} 17 | :ns-default build} 18 | :dev {:extra-paths ["dev-resources"]} 19 | :test {:extra-paths ["test"] 20 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 21 | io.github.cognitect-labs/test-runner 22 | {:git/tag "v0.5.0" :git/sha "48c3c67"}}}}} 23 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/deps-6.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | com.walmartlabs/lacinia {:mvn/version "1.2-alpha-4"} 4 | com.walmartlabs/lacinia-pedestal {:mvn/version "1.1"} 5 | org.clojure/java.jdbc {:mvn/version "0.7.12"} 6 | org.postgresql/postgresql {:mvn/version "42.5.1"} 7 | com.mchange/c3p0 {:mvn/version "0.9.5.5"} 8 | com.stuartsierra/component {:mvn/version "1.1.0"} 9 | io.aviso/logging {:mvn/version "1.0"}} 10 | :aliases 11 | {:run-m {:main-opts ["-m" "my.clojure-game-geek"]} 12 | :run-x {:ns-default my.clojure-game-geek 13 | :exec-fn greet 14 | :exec-args {:name "Clojure"}} 15 | :build {:deps {io.github.seancorfield/build-clj 16 | {:git/tag "v0.8.2" :git/sha "0ffdb4c" 17 | ;; since we're building an app uberjar, we do not 18 | ;; need deps-deploy for clojars.org deployment: 19 | :deps/root "slim"}} 20 | :ns-default build} 21 | :dev {:extra-paths ["dev-resources"]} 22 | :test {:extra-paths ["test"] 23 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 24 | io.github.cognitect-labs/test-runner 25 | {:git/tag "v0.5.0" :git/sha "48c3c67"}}}}} 26 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/docker-compose-1.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | ports: 5 | - 25432:5432 6 | image: postgres:15.1-alpine 7 | environment: 8 | POSTGRES_PASSWORD: supersecret 9 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/logback-test-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %-5level %logger - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/logback-test-2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %-5level %logger - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/schema-0.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.schema 2 | "Contains custom resolvers and a function to provide the full schema." 3 | (:require [clojure.java.io :as io] 4 | [com.walmartlabs.lacinia.util :as util] 5 | [com.walmartlabs.lacinia.schema :as schema] 6 | [clojure.edn :as edn])) 7 | 8 | (defn resolver-map 9 | [] 10 | {:Query/gameById (fn [context args value] 11 | nil)}) 12 | 13 | (defn load-schema 14 | [] 15 | (-> (io/resource "cgg-schema.edn") 16 | slurp 17 | edn/read-string 18 | (util/inject-resolvers (resolver-map)) 19 | schema/compile)) 20 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/schema-1.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.schema 2 | "Contains custom resolvers and a function to provide the full schema." 3 | (:require [clojure.java.io :as io] 4 | [com.walmartlabs.lacinia.util :as util] 5 | [com.walmartlabs.lacinia.schema :as schema] 6 | [clojure.edn :as edn])) 7 | 8 | (defn resolve-game-by-id 9 | [games-map context args value] 10 | (let [{:keys [id]} args] 11 | (get games-map id))) 12 | 13 | (defn resolver-map 14 | [] 15 | (let [cgg-data (-> (io/resource "cgg-data.edn") 16 | slurp 17 | edn/read-string) 18 | games-map (->> cgg-data 19 | :games 20 | (reduce #(assoc %1 (:id %2) %2) {}))] 21 | {:Query/gameById (partial resolve-game-by-id games-map)})) 22 | 23 | (defn load-schema 24 | [] 25 | (-> (io/resource "cgg-schema.edn") 26 | slurp 27 | edn/read-string 28 | (util/inject-resolvers (resolver-map)) 29 | schema/compile)) 30 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/schema-2.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.schema 2 | "Contains custom resolvers and a function to provide the full schema." 3 | (:require [clojure.java.io :as io] 4 | [com.walmartlabs.lacinia.util :as util] 5 | [com.walmartlabs.lacinia.schema :as schema] 6 | [clojure.edn :as edn])) 7 | 8 | (defn resolve-game-by-id 9 | [games-map context args value] 10 | (let [{:keys [id]} args] 11 | (get games-map id))) 12 | 13 | (defn resolve-board-game-designers 14 | [designers-map context args board-game] 15 | (->> board-game 16 | :designers 17 | (map designers-map))) 18 | 19 | (defn resolve-designer-games 20 | [games-map context args designer] 21 | (let [{:keys [id]} designer] 22 | (->> games-map 23 | vals 24 | (filter #(-> % :designers (contains? id)))))) 25 | 26 | (defn entity-map 27 | [data k] 28 | (reduce #(assoc %1 (:id %2) %2) 29 | {} 30 | (get data k))) 31 | 32 | (defn resolver-map 33 | [] 34 | (let [cgg-data (-> (io/resource "cgg-data.edn") 35 | slurp 36 | edn/read-string) 37 | games-map (entity-map cgg-data :games) 38 | designers-map (entity-map cgg-data :designers)] 39 | {:Query/gameById (partial resolve-game-by-id games-map) 40 | :BoardGame/designers (partial resolve-board-game-designers designers-map) 41 | :Designer/games (partial resolve-designer-games games-map)})) 42 | 43 | (defn load-schema 44 | [] 45 | (-> (io/resource "cgg-schema.edn") 46 | slurp 47 | edn/read-string 48 | (util/inject-resolvers (resolver-map)) 49 | schema/compile)) 50 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/schema-3.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.schema 2 | "Contains custom resolvers and a function to provide the full schema." 3 | (:require [clojure.java.io :as io] 4 | [com.stuartsierra.component :as component] 5 | [com.walmartlabs.lacinia.util :as util] 6 | [com.walmartlabs.lacinia.schema :as schema] 7 | [clojure.edn :as edn])) 8 | 9 | (defn resolve-game-by-id 10 | [games-map context args value] 11 | (let [{:keys [id]} args] 12 | (get games-map id))) 13 | 14 | (defn resolve-board-game-designers 15 | [designers-map context args board-game] 16 | (->> board-game 17 | :designers 18 | (map designers-map))) 19 | 20 | (defn resolve-designer-games 21 | [games-map context args designer] 22 | (let [{:keys [id]} designer] 23 | (->> games-map 24 | vals 25 | (filter #(-> % :designers (contains? id)))))) 26 | 27 | (defn entity-map 28 | [data k] 29 | (reduce #(assoc %1 (:id %2) %2) 30 | {} 31 | (get data k))) 32 | 33 | (defn resolver-map 34 | [] 35 | (let [cgg-data (-> (io/resource "cgg-data.edn") 36 | slurp 37 | edn/read-string) 38 | games-map (entity-map cgg-data :games) 39 | designers-map (entity-map cgg-data :designers)] 40 | {:Query/gameById (partial resolve-game-by-id games-map) 41 | :BoardGame/designers (partial resolve-board-game-designers designers-map) 42 | :Designer/games (partial resolve-designer-games games-map)})) 43 | 44 | (defn load-schema 45 | [] 46 | (-> (io/resource "cgg-schema.edn") 47 | slurp 48 | edn/read-string 49 | (util/inject-resolvers (resolver-map)) 50 | schema/compile)) 51 | 52 | (defrecord SchemaProvider [schema] 53 | 54 | component/Lifecycle 55 | 56 | (start [this] 57 | (assoc this :schema (load-schema))) 58 | 59 | (stop [this] 60 | (assoc this :schema nil))) 61 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/schema-5.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.schema 2 | "Contains custom resolvers and a function to provide the full schema." 3 | (:require [clojure.java.io :as io] 4 | [com.stuartsierra.component :as component] 5 | [com.walmartlabs.lacinia.util :as util] 6 | [com.walmartlabs.lacinia.schema :as schema] 7 | [my.clojure-game-geek.db :as db] 8 | [clojure.edn :as edn])) 9 | 10 | (defn game-by-id 11 | [db] 12 | (fn [_ args _] 13 | (db/find-game-by-id db (:id args)))) 14 | 15 | (defn member-by-id 16 | [db] 17 | (fn [_ args _] 18 | (db/find-member-by-id db (:id args)))) 19 | 20 | (defn board-game-designers 21 | [db] 22 | (fn [_ _ board-game] 23 | (db/list-designers-for-game db (:id board-game)))) 24 | 25 | (defn designer-games 26 | [db] 27 | (fn [_ _ designer] 28 | (db/list-games-for-designer db (:id designer)))) 29 | 30 | (defn rating-summary 31 | [db] 32 | (fn [_ _ board-game] 33 | (let [ratings (map :rating (db/list-ratings-for-game db (:id board-game))) 34 | n (count ratings)] 35 | {:count n 36 | :average (if (zero? n) 37 | 0 38 | (/ (apply + ratings) 39 | (float n)))}))) 40 | 41 | (defn member-ratings 42 | [db] 43 | (fn [_ _ member] 44 | (db/list-ratings-for-member db (:id member)))) 45 | 46 | (defn game-rating->game 47 | [db] 48 | (fn [_ _ game-rating] 49 | (db/find-game-by-id db (:game-id game-rating)))) 50 | 51 | (defn resolver-map 52 | [component] 53 | (let [{:keys [db]} component] 54 | {:Query/gameById (game-by-id db) 55 | :Query/memberById (member-by-id db) 56 | :BoardGame/designers (board-game-designers db) 57 | :BoardGame/ratingSummary (rating-summary db) 58 | :Designer/games (designer-games db) 59 | :Member/ratings (member-ratings db) 60 | :GameRating/game (game-rating->game db)})) 61 | 62 | (defn load-schema 63 | [component] 64 | (-> (io/resource "cgg-schema.edn") 65 | slurp 66 | edn/read-string 67 | (util/inject-resolvers (resolver-map component)) 68 | schema/compile)) 69 | 70 | (defrecord SchemaProvider [db schema] 71 | 72 | component/Lifecycle 73 | 74 | (start [this] 75 | (assoc this :schema (load-schema this))) 76 | 77 | (stop [this] 78 | (assoc this :schema nil))) 79 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/server-1.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.server 2 | (:require [com.stuartsierra.component :as component] 3 | [com.walmartlabs.lacinia.pedestal2 :as lp] 4 | [io.pedestal.http :as http])) 5 | 6 | (defrecord Server [schema-provider server] 7 | 8 | component/Lifecycle 9 | 10 | (start [this] 11 | (assoc this :server (-> schema-provider 12 | :schema 13 | (lp/default-service nil) 14 | http/create-server 15 | http/start))) 16 | 17 | (stop [this] 18 | (http/stop server) 19 | (assoc this :server nil))) 20 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/server-2.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.server 2 | (:require [com.stuartsierra.component :as component] 3 | [com.walmartlabs.lacinia.pedestal2 :as lp] 4 | [io.pedestal.http :as http])) 5 | 6 | (defrecord Server [schema-provider server port] 7 | 8 | component/Lifecycle 9 | 10 | (start [this] 11 | (assoc this :server (-> schema-provider 12 | :schema 13 | (lp/default-service {:port port}) 14 | http/create-server 15 | http/start))) 16 | 17 | (stop [this] 18 | (http/stop server) 19 | (assoc this :server nil))) 20 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/system-1.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.system 2 | (:require [com.stuartsierra.component :as component] 3 | [my.clojure-game-geek.schema :as schema] 4 | [my.clojure-game-geek.server :as server])) 5 | 6 | (defn new-system 7 | [] 8 | (assoc (component/system-map) 9 | :server (component/using (server/map->Server {}) 10 | [:schema-provider]) 11 | :schema-provider (schema/map->SchemaProvider {}))) 12 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/system-2.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.system 2 | (:require [com.stuartsierra.component :as component] 3 | [my.clojure-game-geek.schema :as schema] 4 | [my.clojure-game-geek.server :as server] 5 | [my.clojure-game-geek.db :as db])) 6 | 7 | (defn new-system 8 | [] 9 | (assoc (component/system-map) 10 | :db (db/map->ClojureGameGeekDb {}) 11 | :server (component/using (server/map->Server {}) 12 | [:schema-provider]) 13 | :schema-provider (component/using 14 | (schema/map->SchemaProvider {}) 15 | [:db]))) 16 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/system-3.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.system 2 | (:require [com.stuartsierra.component :as component] 3 | [my.clojure-game-geek.schema :as schema] 4 | [my.clojure-game-geek.server :as server] 5 | [my.clojure-game-geek.db :as db])) 6 | 7 | (defn new-system 8 | ([] 9 | (new-system nil)) 10 | ([opts] 11 | (let [{:keys [port] 12 | :or {port 8888}} opts] 13 | (assoc (component/system-map) 14 | :db (db/map->ClojureGameGeekDb {}) 15 | :server (component/using (server/map->Server {:port port}) 16 | [:schema-provider]) 17 | :schema-provider (component/using 18 | (schema/map->SchemaProvider {}) 19 | [:db]))))) 20 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/system_test-1.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.system-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [com.stuartsierra.component :as component] 4 | [com.walmartlabs.lacinia :as lacinia] 5 | [my.clojure-game-geek.test-utils :refer [simplify]] 6 | [my.clojure-game-geek.system :as system])) 7 | 8 | (defn- test-system 9 | "Creates a new system suitable for testing, and ensures that 10 | the HTTP port won't conflict with a default running system." 11 | [] 12 | (system/new-system {:port 8989})) 13 | 14 | (defn- q 15 | "Extracts the compiled schema and executes a query." 16 | [system query variables] 17 | (-> system 18 | (get-in [:schema-provider :schema]) 19 | (lacinia/execute query variables nil) 20 | simplify)) 21 | 22 | (deftest can-read-board-game 23 | (let [system (component/start-system (test-system))] 24 | (try 25 | (is (= {:data {:gameById {:name "Zertz" 26 | :summary "Two player abstract with forced moves and shrinking board" 27 | :maxPlayers 2 28 | :minPlayers 2 29 | :playTime nil}}} 30 | (q system 31 | "{ gameById(id: 1234) { name summary minPlayers maxPlayers playTime }}" 32 | nil))) 33 | (finally 34 | (component/stop-system system))))) 35 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/test_utils-1.clj: -------------------------------------------------------------------------------- 1 | (ns my.clojure-game-geek.test-utils 2 | (:require [clojure.walk :as walk]) 3 | (:import (clojure.lang IPersistentMap))) 4 | 5 | (defn simplify 6 | "Converts all ordered maps nested within the map into standard hash maps, and 7 | sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems." 8 | [m] 9 | (walk/postwalk 10 | (fn [node] 11 | (cond 12 | (instance? IPersistentMap node) 13 | (into {} node) 14 | 15 | (seq? node) 16 | (vec node) 17 | 18 | :else 19 | node)) 20 | m)) 21 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/user-1.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [my.clojure-game-geek.schema :as s] 4 | [com.walmartlabs.lacinia :as lacinia])) 5 | 6 | (def schema (s/load-schema)) 7 | 8 | (defn q 9 | [query-string] 10 | (lacinia/execute schema query-string nil nil)) 11 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/user-2.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require [my.clojure-game-geek.schema :as s] 3 | [com.walmartlabs.lacinia :as lacinia] 4 | [clojure.walk :as walk]) 5 | (:import (clojure.lang IPersistentMap))) 6 | 7 | (def schema (s/load-schema)) 8 | 9 | (defn simplify 10 | "Converts all ordered maps nested within the map into standard hash maps, and 11 | sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems." 12 | [m] 13 | (walk/postwalk 14 | (fn [node] 15 | (cond 16 | (instance? IPersistentMap node) 17 | (into {} node) 18 | 19 | (seq? node) 20 | (vec node) 21 | 22 | :else 23 | node)) 24 | m)) 25 | 26 | (defn q 27 | [query-string] 28 | (-> (lacinia/execute schema query-string nil nil) 29 | simplify)) 30 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/user-3.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require [my.clojure-game-geek.schema :as s] 3 | [com.walmartlabs.lacinia :as lacinia] 4 | [com.walmartlabs.lacinia.pedestal2 :as lp] 5 | [io.pedestal.http :as http] 6 | [clojure.java.browse :refer [browse-url]] 7 | [clojure.walk :as walk]) 8 | (:import (clojure.lang IPersistentMap))) 9 | 10 | (def schema (s/load-schema)) 11 | 12 | (defn simplify 13 | "Converts all ordered maps nested within the map into standard hash maps, and 14 | sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems." 15 | [m] 16 | (walk/postwalk 17 | (fn [node] 18 | (cond 19 | (instance? IPersistentMap node) 20 | (into {} node) 21 | 22 | (seq? node) 23 | (vec node) 24 | 25 | :else 26 | node)) 27 | m)) 28 | 29 | (defn q 30 | [query-string] 31 | (-> (lacinia/execute schema query-string nil nil) 32 | simplify)) 33 | 34 | (defonce server nil) 35 | 36 | (defn start-server 37 | [_] 38 | (let [server (-> (lp/default-service schema nil) 39 | http/create-server 40 | http/start)] 41 | (browse-url "http://localhost:8888/ide") 42 | server)) 43 | 44 | (defn stop-server 45 | [server] 46 | (http/stop server) 47 | nil) 48 | 49 | (defn start 50 | [] 51 | (alter-var-root #'server start-server) 52 | :started) 53 | 54 | (defn stop 55 | [] 56 | (alter-var-root #'server stop-server) 57 | :stopped) 58 | -------------------------------------------------------------------------------- /docs/_examples/tutorial/user-4.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require [com.stuartsierra.component :as component] 3 | [my.clojure-game-geek.system :as system] 4 | [com.walmartlabs.lacinia :as lacinia] 5 | [clojure.java.browse :refer [browse-url]] 6 | [clojure.walk :as walk]) 7 | (:import (clojure.lang IPersistentMap))) 8 | 9 | (defn simplify 10 | "Converts all ordered maps nested within the map into standard hash maps, and 11 | sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems." 12 | [m] 13 | (walk/postwalk 14 | (fn [node] 15 | (cond 16 | (instance? IPersistentMap node) 17 | (into {} node) 18 | 19 | (seq? node) 20 | (vec node) 21 | 22 | :else 23 | node)) 24 | m)) 25 | 26 | (defonce system (system/new-system)) 27 | 28 | (defn q 29 | [query-string] 30 | (-> system 31 | :schema-provider 32 | :schema 33 | (lacinia/execute query-string nil nil) 34 | simplify)) 35 | 36 | (defn start 37 | [] 38 | (alter-var-root #'system component/start-system) 39 | (browse-url "http://localhost:8888/ide") 40 | :started) 41 | 42 | (defn stop 43 | [] 44 | (alter-var-root #'system component/stop-system) 45 | :stopped) 46 | -------------------------------------------------------------------------------- /docs/_examples/union-definition.edn: -------------------------------------------------------------------------------- 1 | {:unions 2 | {:SearchResult 3 | {:members [:Person :Photo]}} 4 | 5 | :objects 6 | {:Person 7 | {:fields {:name {:type String} 8 | :age {:type Int}}} 9 | 10 | :Photo 11 | {:fields {:imageURL {:type String} 12 | :title {:type String} 13 | :height {:type Int} 14 | :width {:type Int}}} 15 | 16 | :Query 17 | {:fields 18 | {:search 19 | {:type (list :SearchResult) 20 | :args {:term String}}}}}} 21 | -------------------------------------------------------------------------------- /docs/_examples/unions-query-response.edn: -------------------------------------------------------------------------------- 1 | {:data 2 | {:search 3 | [{:name "Nik-Nik"} 4 | {:imageURL "http://www.telegraph.co.uk/content/dam/film/ewok-xlarge.jpg" 5 | :title "an Ewok in The Return of the Jedi"} 6 | ]}} 7 | 8 | -------------------------------------------------------------------------------- /docs/_exts/api_link.py: -------------------------------------------------------------------------------- 1 | from docutils import nodes, utils 2 | 3 | 4 | def api_link_inner(baseurl, rawtext, text, options): 5 | 6 | package, varname = text.strip().split( "/") 7 | 8 | if package == '': 9 | package = 'com.walmartlabs.lacinia' 10 | else: 11 | package = 'com.walmartlabs.lacinia.' + package 12 | 13 | ref = '%s/%s.html#var-%s' % (baseurl, package, varname) 14 | title = '%s/%s' % (package, varname) 15 | node = nodes.reference(rawtext, utils.unescape(title), refuri=ref, **options) 16 | 17 | return [node], [] 18 | 19 | 20 | def api_link_role(role, rawtext, text, lineno, inliner, options={}, content=[]): 21 | 22 | return api_link_inner('http://walmartlabs.github.io/apidocs/lacinia', rawtext, text, options) 23 | 24 | def setup(app): 25 | app.add_role('api', api_link_role) 26 | 27 | return { 28 | 'version': '0.1', 29 | 'parallel_read_safe': True, 30 | 'parallel_write_safe': True, 31 | } 32 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* Fixes code blocks that get squeezed by sidebars */ 2 | div.highlight-clojure { 3 | clear: right; 4 | } 5 | 6 | a.headerlink { 7 | float: right; 8 | } 9 | -------------------------------------------------------------------------------- /docs/_static/tutorial/deps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walmartlabs/lacinia/b3d34cd9644f02282b012ec6e50f88bfc1dcab3e/docs/_static/tutorial/deps.png -------------------------------------------------------------------------------- /docs/_static/tutorial/graphiql-basic-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walmartlabs/lacinia/b3d34cd9644f02282b012ec6e50f88bfc1dcab3e/docs/_static/tutorial/graphiql-basic-query.png -------------------------------------------------------------------------------- /docs/_static/tutorial/graphiql-doc-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walmartlabs/lacinia/b3d34cd9644f02282b012ec6e50f88bfc1dcab3e/docs/_static/tutorial/graphiql-doc-browser.png -------------------------------------------------------------------------------- /docs/_static/tutorial/graphiql-initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walmartlabs/lacinia/b3d34cd9644f02282b012ec6e50f88bfc1dcab3e/docs/_static/tutorial/graphiql-initial.png -------------------------------------------------------------------------------- /docs/clojure.rst: -------------------------------------------------------------------------------- 1 | Clojure 1.9 2 | =========== 3 | 4 | Lacinia targets Clojure 1.9, and makes specific use of ``clojure.spec``. 5 | 6 | To use Lacinia with Clojure 1.8, modify your :file:`project.clj` to include ``clojure-future-spec``:: 7 | 8 | :dependencies [[org.clojure/clojure "1.8.0"] 9 | [com.walmartlabs/lacinia "x.y.z"] 10 | [clojure-future-spec ""] 11 | ...] 12 | 13 | -------------------------------------------------------------------------------- /docs/deprecation.rst: -------------------------------------------------------------------------------- 1 | Deprecation 2 | =========== 3 | 4 | Schemas grow and change over time. 5 | GraphQL values backwards compatibility quite highly, so changes to a schema are typically additive: 6 | introducing novel fields, types, and enum values. 7 | 8 | However, when the implementation of a field is just `wrong`, it can be kept for compatibility, but 9 | deprecated. 10 | 11 | Both :doc:`fields ` and :doc:`enums ` can include a ``:deprecated`` key. 12 | Remember that queries, mutations, and subscriptions are (under the covers) just fields, so they can be 13 | deprecated as well. 14 | 15 | The value for the key can either be ``true``, or a string description of the reason the field is deprecated. 16 | Typically, the description indicates an alternative field to use instead. 17 | 18 | Deprecation does *not* affect execution of the field in any way; the deprecation flag and reason simply shows up 19 | in :doc:`introspection `. 20 | 21 | When using the :doc:`Schema Definition Language `, elements may be marked with the 22 | ``@deprecated`` :doc:`directive `. 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/federation/external.rst: -------------------------------------------------------------------------------- 1 | External Entities 2 | ================= 3 | 4 | The previous example showed an internal entity that can be extended; this example shows a different service providing 5 | its own internal entity, but also extending the ``User`` entity. 6 | 7 | .. literalinclude:: /_examples/fed/external.gql 8 | 9 | Note the use of the ``@extends`` directive, this indicates that ``User`` (in the products service) is a stub for the full 10 | ``User`` entity in the users service. 11 | 12 | You must ensure that the external ``User`` includes the same ``@key`` directive (or directives), and the same primary key 13 | fields; here ``id`` must be present, since it is part of the primary key. 14 | The ``@external`` directive indicates that the field is provided by another service (the users service). 15 | 16 | The ``favoriteProducts`` field on ``User`` is an addition provided by this service, the products service. 17 | Like any other field, a resolver must be provided for it. 18 | We'll see how that works shortly. 19 | 20 | Notice that this service adds the ``productByUpc`` query to the ``Query`` object; the Apollo GraphQL gateway 21 | merges together all the queries defined by all the implementing services. 22 | 23 | Again, the point of the gateway is that it mixes and matches from all the implementing services; clients should 24 | *only* go through the gateway since that's the only place where this merged view of all the individual schemas 25 | exists. 26 | 27 | The gateway is capable of building a plan that involves multiple steps to satisfy a client query. 28 | 29 | For example, consider this gateway query: 30 | 31 | .. literalinclude:: /_examples/fed/query.gql 32 | 33 | The gateway will start with a query to the users service; it will invoke the ``userById`` query there and will 34 | select both the ``name`` field (as specified in the client query) and the ``id`` field (since that's specified 35 | in the ``@key`` directive on the ``User`` entity). 36 | 37 | A second query will be sent to the products service. 38 | This query is used to get those favorite products; but to understand exactly 39 | how that works, we must first discuss `representations`. -------------------------------------------------------------------------------- /docs/federation/implementation.rst: -------------------------------------------------------------------------------- 1 | Service Implementation 2 | ====================== 3 | 4 | At this point, we've discussed what goes into each implementing service's schema, and a bit about how 5 | each service is responsible for resolving representations; let's finally see how this all fits together with 6 | Lacinia. 7 | 8 | Below is a sketch of how this comes together in the products service: 9 | 10 | .. literalinclude:: /_examples/fed/products.edn 11 | 12 | The ``resolve-users-external`` function is used to convert a seq of ``User`` representations 13 | into a seq of ``User`` entity stubs; this is called from the resolver for the ``_entities`` query whose type 14 | is a list of the ``_Entities`` union, therefore each value :doc:`must be tagged ` with the ``:User`` type. 15 | 16 | .. sidebar:: Return type 17 | 18 | Return type here is just like a normal field resolver that returns a GraphQL list; it may be seq of values, or a 19 | :doc:`ResolverResult ` that delivers such a seq. 20 | 21 | ``resolve-products-internal`` does the same for ``Product`` representations, but since this is the 22 | products service, the expected behavior is to perform a query against an external data store and 23 | ensure the results match the structure of the ``Product`` entity. 24 | 25 | ``resolve-product-by-upc`` is the resolver function for the ``productByUpc`` query. 26 | Since the field type is ``Product`` there's no need to tag the value. 27 | 28 | ``resolve-favorite-products`` is the resolver function for the ``User/favoriteProducts`` field. 29 | This is passed the ``User`` (provided by ``resolve-users-external``); it extracts the ``id`` and passes 30 | it to ``get-favorite-products-for-user``. 31 | 32 | The remainder is bare-bones scaffolding to read, parse, and compile the schema and build a Pedestal 33 | service endpoint around it. 34 | 35 | Pay careful attention to the call to :api:`parser-schema/parse-schema`; the presence of the 36 | ``:federation`` option is critical; this adds the necessary base types and directives 37 | before parsing the schema definition, and then adds the ``_entities`` query and ``_Entities`` union 38 | afterwards, among other things. 39 | 40 | The ``:entity-resolvers`` map is critical; this maps from a type name to a entity resolver; 41 | this information is used to build the field resolver function for the ``_entities`` query. 42 | 43 | .. warning:: 44 | 45 | A lot of details are left out of this, such as initializing the database and storing 46 | the database connection into the application context, where functions like 47 | ``get-product-by-upc`` can access it. 48 | 49 | This is only a sketch to help you connect the dots. -------------------------------------------------------------------------------- /docs/federation/internal.rst: -------------------------------------------------------------------------------- 1 | Internal Entities 2 | ================= 3 | 4 | Defining an entity that is internal is quite straight-forward, it is almost the same as in 5 | traditional GraphQL. 6 | 7 | .. sidebar:: Examples 8 | 9 | Here, and in the remaining examples, we've simplified the example from 10 | Apollo GraphQL's documentation to just two services: 11 | users and products. 12 | 13 | .. literalinclude:: /_examples/fed/internal.gql 14 | 15 | When federation is enabled, a 16 | `number of new directives `_ are automatically available, 17 | including ``@key``, which defines the primary key, or primary keys, for the entity. 18 | 19 | The above example would be the schema for a users service that can be extended by other services. 20 | -------------------------------------------------------------------------------- /docs/federation/reps.rst: -------------------------------------------------------------------------------- 1 | Representations 2 | =============== 3 | 4 | A `representation` is a map that can be transferred from one implementing service to another, within the same federation. 5 | This is necessary to allow work started in one service to continue in another; consider the query: 6 | 7 | .. literalinclude:: /_examples/fed/query.gql 8 | 9 | The gateway will query the ``User/favoriteProducts`` field on the products service as the second step on this query ... but 10 | where does the ``User`` come from? 11 | 12 | After the gateway performs the initial query on the users service, it builds a representation of the specific ``User`` 13 | to pass to the products service, using information from the ``@key`` directive: 14 | 15 | .. code-block:: json 16 | 17 | {"__typename": "User", 18 | "id": "124c41"} 19 | 20 | This representation is JSON, and is passed to an implementing service's ``_entities`` query, which is automaticaly added 21 | to the implementing service's schema by Lacinia: 22 | 23 | .. literalinclude:: /_examples/fed/schema.gql 24 | 25 | The ``_Entity`` union will contain all entities, internal or external, in the local schema; for the products service, this 26 | will be ``User`` (external) and ``Product`` (internal). 27 | 28 | The ``_entities`` query exists to convert some number of representations (here, as scalar type ``_Any``) into entities 29 | (either stub entities or full entities). 30 | The gateway sends a request that passes the representations in, and uses fragments to extract the data needed 31 | by the original client query:: 32 | 33 | query($representations:[_Any!]!) { 34 | _entities(representations:$representations) { 35 | ... on User { 36 | favoriteProducts {upc name price} 37 | } 38 | } 39 | } 40 | 41 | So, in the products service, the ``_entities`` resolver converts the representation into a stub ``User`` object, 42 | containing just enough information so that the ``favoriteProducts`` resolver can perform whatever database query 43 | it uses. 44 | The response from the products service is merged together with the response from the users service and a final 45 | response can be returned to the gateway service's client. 46 | 47 | -------------------------------------------------------------------------------- /docs/input-objects.rst: -------------------------------------------------------------------------------- 1 | Input Objects 2 | ============= 3 | 4 | .. sidebar:: GraphQL Spec 5 | 6 | Read about :spec:`input objects `. 7 | 8 | In some cases, it is desirable for a query to include arguments with more complex data than a single value. 9 | A typical example would be passing a bundle of values as part of a 10 | :doc:`mutation ` operation (rather than an unmanageable number of individual field arguments). 11 | 12 | Input objects are defined like ordinary objects, with a number of restrictions: 13 | 14 | - Field types are limited to scalars, enums, and input objects (or list and non-null wrappers around 15 | those types) 16 | - There are no field resolvers for input objects; these are values passed in their entirety from 17 | the client to the server 18 | - Input objects do not implement interfaces 19 | 20 | Input objects are defined as keys of the top-level ``:input-objects`` key in the schema. 21 | 22 | -------------------------------------------------------------------------------- /docs/interfaces.rst: -------------------------------------------------------------------------------- 1 | Interfaces 2 | ========== 3 | 4 | GraphQL supports the notion of *interfaces*, collections of fields and their arguments. 5 | 6 | .. sidebar:: GraphQL Spec 7 | 8 | Read about :spec:`interfaces `. 9 | 10 | 11 | To keep things simple, interfaces can not extend other interfaces. 12 | Likewise, objects can implement multiple interfaces, but can not extend other objects. 13 | 14 | Interfaces are valid types, they can be specified as the return type of a query, mutation, 15 | or as the type of a field. 16 | 17 | .. literalinclude:: _examples/interface-definition.edn 18 | :language: clojure 19 | 20 | 21 | An interface definition may include a ``:description`` key; the value is a string exposed through :doc:`introspection`. 22 | 23 | The description on an interface field, or on an argument of an interface field, will be inherited by 24 | the object field (or argument) unless overriden. 25 | This helps to eliminate duplication of documentation between an interface and the object implementing 26 | the interface. 27 | 28 | The :doc:`object definition ` must include all the fields of all extended interfaces. 29 | 30 | 31 | .. tip:: 32 | 33 | When a field or operation type is an interface, 34 | the field resolver may return any of a number of different 35 | concrete object types, and Lacinia has no way to determine which; 36 | this information must be :doc:`explicitly provided `. 37 | -------------------------------------------------------------------------------- /docs/introspection.rst: -------------------------------------------------------------------------------- 1 | Introspection 2 | ============= 3 | 4 | Introspection is a key part of GraphQL: the schema is self-describing. 5 | 6 | .. sidebar:: GraphQL Spec 7 | 8 | Read about :spec:`introspection `. 9 | 10 | Introspection data is derived directly from the schema. 11 | Often, a ``:description`` key is added to the schema to provide additional help. 12 | 13 | Introspection is necessary to support the in-browser `graphiql`_ IDE. 14 | 15 | Introspection can also be leveraged by smart clients, presumably custom in-browser or mobile applications, 16 | to help deal with schema evolution. 17 | A smart client can use introspection to determine if a particular field exists before 18 | including that field in a query request; this can help defuse the process of introducing 19 | a new field (in the server) at the same time as a new client that needs to make use of that field. 20 | 21 | 22 | .. _graphiql: https://github.com/graphql/graphiql 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/mutations.rst: -------------------------------------------------------------------------------- 1 | Mutations 2 | ========= 3 | 4 | Mutations parallel :doc:`queries `, except that the root field resolvers may make 5 | changes to underlying data in addition to exposing data. 6 | 7 | The :doc:`field resolver ` for a mutation will, as with a query, be passed nil 8 | as its value argument (the third argument). 9 | A mutation is expected to perform some state changing operation, then return a value that 10 | indicates the new state; this value will be recursively resolved and selected, just as with 11 | a query. 12 | 13 | Mutations are defined as fields of the :doc:`Mutation object `. 14 | 15 | When a single query includes more than one mutation, the mutations *must* execute in the client-specified 16 | order. 17 | This is different from queries, which allow for each root query to run in 18 | :doc:`parallel `. 19 | 20 | Typically, mutations are only allowed when the incoming request is explicitly an HTTP POST. 21 | However, that is beyond the scope of Lacinia (it doesn't know about the HTTP request, just 22 | the query string extracted from the HTTP request). 23 | -------------------------------------------------------------------------------- /docs/queries.rst: -------------------------------------------------------------------------------- 1 | Queries 2 | ======= 3 | 4 | .. sidebar:: GraphQL Spec 5 | 6 | Read about :spec:`operations `. 7 | 8 | Queries are responsible for generating the initial resolved values that will be 9 | picked apart to form the result map. 10 | 11 | Other than that, queries are just the same as any other field. 12 | Queries have a type, and accept arguments. 13 | 14 | Queries are defined as the fields of a special object, :doc:`Query object `. 15 | 16 | .. literalinclude:: _examples/query-def-var.edn 17 | :language: clojure 18 | 19 | The :doc:`field resolver ` for a query is passed nil 20 | as the the value (the third parameter). 21 | Outside of this, the query field resolver is the same as any field resolver 22 | anywhere else in the schema. 23 | 24 | In the GraphQL specification, it is noted that queries are idempotent; if 25 | the query document includes multiple queries, they are allowed to execute 26 | in :doc:`parallel `. 27 | 28 | 29 | :queries key 30 | ------------ 31 | 32 | The above is the "modern" way to define queries; an older approach is still supported. 33 | Queries may instead be defined using the ``:queries`` key of the schema. 34 | 35 | .. literalinclude:: _examples/query-def.edn 36 | :language: clojure 37 | -------------------------------------------------------------------------------- /docs/resolve/attach.rst: -------------------------------------------------------------------------------- 1 | Injecting Resolvers 2 | =================== 3 | 4 | Schemas start as EDN files, which has the advantage that symbols do not have to be quoted 5 | (useful when using the ``list`` and ``non-null`` qualifiers on types). 6 | However, EDN is data, not code, which makes it nonsensical to define field resolvers directly in the schema file. 7 | 8 | One option is to use ``assoc-in`` to attach resolvers after reading the EDN, but before invoking 9 | :api:`schema/compile`. 10 | This can become quite cumbersome in practice. 11 | 12 | Instead, the standard approach is use 13 | :api:`util/inject-resolvers`, [#attach-resolvers]_ which is a concise way of matching fields to resolvers, adding 14 | the ``:resolve`` key to the nested field definitions. 15 | 16 | .. literalinclude:: /_examples/compile-schema.clj 17 | :language: clojure 18 | 19 | The keys in the map passed to ``inject-resolvers`` use the namespace to define the object (such as ``Human`` or ``Query``) and the local name to define the field within the object (``hero`` and so forth). 20 | 21 | The ``inject-resolvers`` step occurs **before** the schema is compiled. 22 | 23 | .. [#attach-resolvers] An older approach is still supported, via the function 24 | :api:`util/attach-resolvers`, but ``inject-resolvers`` is preferred, as it is simpler. 25 | -------------------------------------------------------------------------------- /docs/resolve/context.rst: -------------------------------------------------------------------------------- 1 | Application Context 2 | =================== 3 | 4 | The application context passed to your field resolvers is normally set by the initial call to 5 | :api:`/execute`. 6 | Lacinia uses the context for its own book-keeping (the keys it places into the map are namespaced to 7 | avoid collisions) but otherwise the same map is passed to all field resolvers. 8 | 9 | In specific cases, it is useful to allow a field resolver to modify the application context, with the change 10 | exposed just to the fields nested below, but to any depth. 11 | 12 | For example, in this query:: 13 | 14 | { 15 | products(search: "fuzzy") { 16 | category { 17 | name 18 | product { 19 | upc 20 | name 21 | highlightedName 22 | } 23 | } 24 | } 25 | 26 | Here, the search term is provided to the ``products`` field, but is again needed by the ``highlightedName`` 27 | field, to highlight the parts of the name that match the search term. 28 | 29 | The resolver for the ``products`` field can communicate this information 30 | "down tree" to the resolver for the ``highlighted_name`` field, by 31 | using the :api:`resolve/with-context` function. 32 | 33 | .. literalinclude:: /_examples/mutable-context.edn 34 | :language: clojure 35 | :emphasize-lines: 7 36 | 37 | The map provided to ``with-context`` will be merged into the application context before any nested resolvers are invoked. 38 | In this way, the new key, ``::search-term``, is only present in the context for field resolvers below ``products`` field. 39 | 40 | Some field resolvers returns lists of values; the entire list can be wrapped in this way **OR** individual values within the 41 | list may be wrapped. 42 | 43 | .. tip:: 44 | 45 | Remember that query execution is top to bottom, then the final result map is assembled from the leaves back up to 46 | the roots. 47 | 48 | 49 | When using `lacinia-pedestal `_, the default behavior is to capture the Ring request map and supply it 50 | in the application context under the ``:request`` key. 51 | -------------------------------------------------------------------------------- /docs/resolve/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | Formatting a Date 5 | ----------------- 6 | 7 | This is a common scenario: you have a Date or Timestamp value and it needs to be 8 | in a specific format in the result map. 9 | 10 | In this example, the field resolver will extract the key from the 11 | container's resolved value, and format it: 12 | 13 | .. code-block:: clojure 14 | 15 | (defn resolve-updated-at 16 | [context args resolved-value] 17 | (->> resolved-value 18 | :updated-at 19 | (format "%tm-%` 13 | to communicate errors back to Lacinia for inclusion in the ``:errors`` key of the result. 14 | 15 | Failure to catch exceptions is even more damaging when using :doc:`async field resolvers `, 16 | as this can cause query execution to entirely halt, due to resolver result promises never being 17 | delivered. 18 | -------------------------------------------------------------------------------- /docs/resolve/field-resolver-protocol.rst: -------------------------------------------------------------------------------- 1 | FieldResolver Protocol 2 | ====================== 3 | 4 | In the majority of cases, a field resolver is simply a function that accepts the three parameters: 5 | context, args, and value. 6 | 7 | However, when structuring large systems using 8 | `Component `_ (or any similar approach), this can be inconvienient, as it 9 | does not make it possible to structure field resolvers as components. 10 | 11 | The :api:`resolve/FieldResolver` protocol addresses this: it defines a single method, ``resolve-value``. 12 | This method is the analog of an ordinary field resolver function. 13 | 14 | The support for this protocol is baked directly into 15 | :api:`schema/compile`. 16 | 17 | Just like field resolver functions, FieldResolver instances can resolve a value directly by 18 | returning it, or can return a ResolverResult. 19 | -------------------------------------------------------------------------------- /docs/resolve/index.rst: -------------------------------------------------------------------------------- 1 | Field Resolvers 2 | =============== 3 | 4 | Field resolvers are how Lacinia goes beyond data modelling to actually providing access to data. 5 | 6 | .. sidebar:: GraphQL Spec 7 | 8 | Read about :spec:`value resolution `. 9 | 10 | Field resolvers are attached to fields, including the :doc:`root objects <../roots>` ``Query``, ``Mutation``, and ``Subscription``. 11 | It is only inside field resolvers that a Lacinia application can connect to a database or 12 | an external system: field resolvers are where the data actually *comes* from. 13 | 14 | In essence, the top-level operations perform the initial work in a request, getting the root 15 | object (or collection of objects). 16 | 17 | Field resolvers in nested fields are responsible for extracting and transforming that root data. 18 | In some cases, a field resolver may need to perform additional queries against a back-end data store. 19 | 20 | 21 | .. toctree:: 22 | :hidden: 23 | 24 | overview 25 | attach 26 | type-tags 27 | exceptions 28 | resolve-as 29 | field-resolver-protocol 30 | async 31 | selections 32 | context 33 | extensions 34 | examples 35 | -------------------------------------------------------------------------------- /docs/resolve/type-tags.rst: -------------------------------------------------------------------------------- 1 | Explicit Types 2 | ============== 3 | 4 | For structured types, Lacinia needs to know what type of data is returned by the field resolver, 5 | so that it can, as necessary, process query fragments. 6 | 7 | When the type of field is a concrete object type, Lacinia automatically tags the value with 8 | the schema type. 9 | 10 | When the type of a field is an interface or union, it is necessary for the field resolver 11 | to explicitly tag the value with its object type. 12 | 13 | Using tag-with-type 14 | ------------------- 15 | 16 | The function :api:`schema/tag-with-type` exists for this purpose. 17 | The tag value is a keyword matching an object definition. 18 | 19 | When a field returns a list of an interface, or a list of a union, 20 | then each individual resolved value must be tagged with its concrete type. 21 | It is allowed and expected that different values in the collection will have 22 | different concrete types. 23 | 24 | Generally, type tagging is just metadata added to a map (or Clojure record type). 25 | However, Lacinia supports tagging of arbitrary objects that don't support Clojure metadata 26 | ... but ``tag-with-type`` will return a wrapper type in that case. When using Java types, 27 | make sure that ``tag-with-type`` is the last thing a field resolver does. 28 | 29 | 30 | Using record types 31 | ------------------ 32 | 33 | As an alternative to ``tag-with-type``, it is possible to associate an object with a Java class; typically 34 | this is a record type created using ``defrecord``. 35 | 36 | The ``:tag`` key of the object definition must be set to the the class name (as a symbol). 37 | 38 | .. literalinclude:: /_examples/object-tag.edn 39 | :language: clojure 40 | :emphasize-lines: 10,18 41 | 42 | This only works if the field resolver functions return the corresponding record types, rather than 43 | ordinary Clojure maps. 44 | In the above example, the field resolvers would need to invoke the ``map->Business`` or ``map->Employee`` constructor 45 | functions as appropriate. 46 | 47 | .. tip:: 48 | 49 | The ``:tag`` value is a Java class name, not a namespaced Clojure name. 50 | That means no slash character, and dashes in the namespace must be converted to underscores. 51 | 52 | Container type 53 | -------------- 54 | 55 | When a field resolver is invoked, the context value for key ``:com.walmartlabs.lacinia/container-type-name`` 56 | will be the name of the concrete type (a keyword) for the resolved value passed into the resolver. 57 | This will be nil for top-level operations. 58 | 59 | When the type of the containing field is a union or interface, this value will be the specific concrete object type 60 | for the actual resolved value. 61 | -------------------------------------------------------------------------------- /docs/resources.rst: -------------------------------------------------------------------------------- 1 | Other Resources 2 | =============== 3 | 4 | * `First Release Announcement `_ 5 | 6 | A quick introduction to Lacinia, and goes into detail about the problems it solves for us at Walmart. 7 | 8 | * A talk at `Clojure/West 2017 `_: `Power to the (Mobile) People: Clojure and GraphQL` 9 | is available on `YouTube `_. 10 | 11 | * A good introductory `Blog Post by James Borden `_. 12 | 13 | * A talk at `Clojure/Conj 2017 `_: 14 | `The Power of Lacinia & Hystrix in Production` is available on `You Tube `_. 15 | 16 | * Blog Post: `The Case for Clojure and GraphQL: Replacing Django `_. 17 | 18 | * Blog Post: `Through the Looking Graph: Experiences adopting GraphQL on a Clojure/script project `_. 19 | -------------------------------------------------------------------------------- /docs/roots.rst: -------------------------------------------------------------------------------- 1 | Root Object Names 2 | ================= 3 | 4 | Top-level query, mutation, and subscription operations are represented in Lacinia as fields on 5 | special objects. 6 | These objects are the "operation root objects". 7 | The default names of these objects are ``Query``, ``Mutation``, and ``Subscription``. 8 | 9 | When compiling an input schema, these objects will be created if they do not already exist. 10 | 11 | The ``:roots`` key of the input schema is used to override the default names. Inside ``:roots``, you 12 | may specify keys ``:query``, ``:mutation``, or ``:subscription``, with the value being the name of the 13 | corresponding object. There is rarely a need to rename the objects from default values, however. 14 | 15 | If the objects already exist, then 16 | any fields on the objects are automatically available operations. 17 | In the larger GraphQL world (beyond Lacinia), this is the typical way of defining operations. 18 | 19 | Beyond that, the operations from the ``:queries``, ``:mutations``, and ``:subscriptions`` maps of the input schema will be 20 | merged into the fields of the corresponding root object; this is supported, but not prefered. 21 | 22 | Name collisions are not allowed; a schema compile exception is thrown if an operation (via ``:queries``, etc.) 23 | conflicts with a field of the corresponding root object. 24 | 25 | Unions 26 | ------ 27 | 28 | It is allowed for the root type to be a union. 29 | This can be a handy way to organize many different operations. 30 | 31 | In this case, Lacinia creates a new object 32 | and merges the fields of the members of the union, along with any operations from the input schema. 33 | 34 | Again, the field names must not conflict. 35 | 36 | The new object becomes the root operation object. 37 | -------------------------------------------------------------------------------- /docs/samples.rst: -------------------------------------------------------------------------------- 1 | Sample Projects 2 | =============== 3 | 4 | `boardgamegeek-graphql-proxy `_ 5 | Howard Lewis Ship created this simple proxy to expose part of the 6 | `BoardGameGeek `_ database as GraphQL, using Lacinia. 7 | 8 | It was used for examples in his 9 | Clojure/West 2017 talk: `Power to the (Mobile) People: Clojure and GraphQL `_. 10 | 11 | `leaderboard-api `_ 12 | A simple API to track details about games and high scores. 13 | Built on top of Compojure and PostgreSQL. 14 | See `this blog post `_ by the author. 15 | 16 | `Event sourcing tutorial `_ 17 | This project consists of multiple components creating a bank simulation. 18 | The `graphql-endpoint `_ 19 | leverages Kafka to do queries, mutations and subscriptions. 20 | Also part of the project is a `frontend `_ 21 | using `re-graph `_. 22 | 23 | `Fullstack Learning Project `_ 24 | A port of `The Fullstack Tutorial for GraphQL `_, ported to Clojure and Lacinia. 25 | 26 | `Hacker News GraphQL `_ 27 | A version of Hacker News implemented using GraphQL and `Datomic `_ on the backend, 28 | and `re-frame `_ on the front end. 29 | 30 | `Lacinia LDAP backend `_ 31 | A sample library for querying a LDAP/Active directory using GraphQL 32 | 33 | `Lacinia Qliksense backend `_ 34 | A sample library for querying an Qliksense server (Repository API) using GraphQL 35 | -------------------------------------------------------------------------------- /docs/spec.rst: -------------------------------------------------------------------------------- 1 | clojure.spec 2 | ============ 3 | 4 | Lacinia makes use of clojure.spec; specifically, the arguments to 5 | :api:`schema/compile` and 6 | :api:`parser.schema/parse-schema` are *always* validated with spec. 7 | 8 | This is useful, especially for ``compile``, and the data structure passed in for compilation is complex and 9 | deeply nested. 10 | 11 | However, the exceptions thrown by clojure.spec can be challenging to read. 12 | 13 | The use of `Expound `_ is recommended; it does a much better job of formatting 14 | that wealth of data for a person to read. 15 | 16 | For example, it omits all the extraneous detail, making it much easier to find where the problem exists:: 17 | 18 | -- Spec failed -------------------- 19 | {:objects 20 | {:Henry 21 | {:fields 22 | {:higgins 23 | {:type ..., 24 | :resolve ..., 25 | :deprecated 7.0}}}}} 26 | ^^^ 27 | should satisfy 28 | true? 29 | or 30 | string? 31 | 32 | 33 | Further, Lacinia includes an extra namespace, not loaded by default: ``com.walmartlabs.lacinia.expound``. 34 | This namespace simply defines spec messages for some of the trickier specs defined by Lacinia. 35 | -------------------------------------------------------------------------------- /docs/subscriptions/index.rst: -------------------------------------------------------------------------------- 1 | Subscriptions 2 | ============= 3 | 4 | Subscriptions are GraphQL's approach to server-side push. 5 | The description is a bit abstract, as the specification keeps all options open on how 6 | subscriptions are to be implemented. 7 | 8 | .. sidebar:: GraphQL Spec 9 | 10 | Read about :spec:`subscriptions `. 11 | 12 | With subscriptions, a client can establish a long-lived connection to a server, and 13 | will receive new data on the connection as it becomes available to the server. 14 | 15 | Common use cases for subscriptions are updating a conversation page as new messages are added, 16 | updating a dashboard as interesting events about a system occur, or monitoring the progress 17 | of some long-lived process. 18 | 19 | .. toctree:: 20 | :hidden: 21 | 22 | overview 23 | streamer 24 | resolver 25 | -------------------------------------------------------------------------------- /docs/subscriptions/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | The specification discusses a `source stream` and a `response stream`. 5 | 6 | Lacinia implements the source stream as a callback function. 7 | The response stream is largely the responsibility of 8 | the `web tier `_. 9 | 10 | - Lacinia invokes a streamer function once, to initialize the subscription stream. 11 | 12 | - The streamer is provided with a source stream callback function; as new values are available 13 | they are passed to this callback. 14 | 15 | Typically, the streamer will create a thread, core.async process, or other long-lived 16 | construct to feed values to the source stream. 17 | 18 | - Whenever the source stream callback is passed a value, 19 | Lacinia will execute the subscription as a query, which will generate a 20 | new response (with the standard ``:data`` and/or ``:errors`` keys). 21 | 22 | - The response will be converted as necessary and streamed to the client, forming 23 | the response stream. 24 | 25 | - The streamer must return a function that will be invoked to perform cleanup. 26 | This cleanup function typically stops whatever process was started earlier. 27 | 28 | Subscriptions are operations, like queries or mutations. 29 | They are defined as fields of the :doc:`Subscription object <../roots>`. 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/subscriptions/resolver.rst: -------------------------------------------------------------------------------- 1 | Resolver 2 | ======== 3 | 4 | Unlike a query or mutation, the :doc:`field resolver <../resolve/index>` 5 | for a subscription always starts with a specific value, provided by the streamer, via 6 | the source stream callback. 7 | 8 | Because of this, the resolver is optional: if not provided, a default resolver is used, one that simply returns 9 | the source stream value. 10 | 11 | However, it still makes sense to implement a resolver in some cases. 12 | 13 | Both the resolver and the streamer receive the same map of arguments: it is reasonable that some 14 | may be used by the streamer (for example, to filter which values go into the source stream), 15 | and some by the resolver (to control the selections on the source value). 16 | 17 | -------------------------------------------------------------------------------- /docs/tracing.rst: -------------------------------------------------------------------------------- 1 | Resolver Tracing 2 | ================ 3 | 4 | When scaling a service, it is invaluable to know where queries are spending their execution time; Lacinia 5 | can help you here; when enabled, Lacinia will collect performance tracing 6 | information compatible with `Apollo GraphQL `_. 7 | 8 | Timing collection is enabled by passing the context through the 9 | :api:`tracing/enable-tracing` 10 | function: 11 | 12 | .. literalinclude:: /_examples/tracing.edn 13 | :language: clojure 14 | 15 | .. sidebar:: Extensions key? 16 | 17 | GraphQL supports a third result key, ``extensions``, as 18 | described in :spec:`the spec `. 19 | It exists just for this kind of extra information in the response. 20 | 21 | Note that tracing is an `execution` option, not a `schema compilation` option; it's just a matter of setting 22 | up the application context (via ``enable-tracing``) before parsing and executing the query. 23 | 24 | When the field resolvers are :doc:`asynchronous `, you'll often see that the ``startOffset`` and ``duration`` 25 | of multiple fields represent overlapping time periods, which is exactly what you want. 26 | 27 | Generally, resolver tracing maps are added to the list in order of completion, but the exact order is 28 | not guaranteed. 29 | 30 | Enabling tracing adds a lot of overhead to execution and parsing, both for the essential overhead of collecting 31 | the tracing information, but also because certain optimizations are disabled when tracing is enabled; 32 | for example default field resolvers (where no explicit resolver is provided) are heavily optimized normally, to 33 | avoid unecessary function calls and object creation. 34 | 35 | .. warning:: 36 | 37 | Tracing should never be enabled in production. This can be accomplished by removing the ``com.walmartlabs.lacinia.pedestal2/enable-tracing-interceptor`` 38 | interceptor from the pipeline (when using lacinia-pedestal). 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/tutorial/domain.rst: -------------------------------------------------------------------------------- 1 | Domain 2 | ====== 3 | 4 | Our goal will be to provide a GraphQL interface to data about board games 5 | (one of the author's hobbies), as a limited version of 6 | `Board Game Geek `_. 7 | 8 | Board Game Geek itself is a huge resource, with decades of information about games, game designers and publishers, 9 | tracking which users own which games, game ratings, forums for discussing games, and far, far more. 10 | We'll focus on just a couple of simple elements of the full design. 11 | 12 | The basic types in the final system are as follows: 13 | 14 | .. graphviz:: 15 | 16 | digraph { 17 | 18 | BoardGame 19 | Publisher 20 | Designer 21 | Member 22 | GameRating 23 | 24 | BoardGame -> GameRating [taillabel="1", headlabel="n"] 25 | BoardGame -> {Publisher, Designer} [taillabel="n", headlabel="m"] 26 | GameRating -> Member [taillabel="n", headlabel="1" ] 27 | 28 | } 29 | 30 | A BoardGame may be published by multiple Publisher companies (the Publisher may 31 | be different for different countries, or may simply vary over time). 32 | 33 | A BoardGame may have any number of Designers. 34 | 35 | Users of Clojure Game Geek, represented as type Member, may provide their personal ratings for board games. 36 | 37 | Even this tiny silver of functionality it sufficiently meaty to give us a taste for building 38 | full applications. We'll start by creating an empty project, in the next chapter. 39 | -------------------------------------------------------------------------------- /docs/tutorial/index.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | .. toctree:: 5 | :hidden: 6 | 7 | prereqs 8 | domain 9 | create-project 10 | init-schema 11 | game-data 12 | designer-data 13 | pedestal 14 | component 15 | member-ratings 16 | mutable-database 17 | rating-mutation 18 | database-1 19 | testing-1 20 | database-2 21 | 22 | wrap-up 23 | 24 | Our goal with this tutorial is to build up the essentials of a full application 25 | implemented in Lacinia, starting from nothing. 26 | 27 | Unlike many of the snippets used elsewhere in the Lacinia documentation, this will be something 28 | you can fork and experiment with yourself. 29 | 30 | Along the way, we hope you'll learn quite a bit about not just Lacinia and GraphQL, 31 | but about building Clojure applications in general. 32 | 33 | You can pull down the full source for the tutorial from GitHub: https://github.com/walmartlabs/clojure-game-geek 34 | -------------------------------------------------------------------------------- /docs/tutorial/prereqs.rst: -------------------------------------------------------------------------------- 1 | Pre-Requisites 2 | ============== 3 | 4 | You should have a basic understanding of GraphQL, which you can pick up from this documentation, 5 | and from `the GraphQL home page `_. 6 | 7 | You should be familiar with, but by no means an expert in, Clojure. 8 | 9 | You should have a `recent build of Clojure `_, including the ``clj`` command. 10 | 11 | You should have an editor or IDE ready to go, set up for editing Clojure code. 12 | 13 | A skim of the Lacinia reference documentation (the rest of this manual, outside of 14 | this tutorial) is also helpful, or you can follow links provided as we go. 15 | 16 | The later chapters use a database stored in a Docker container [#dockerfn]_ ; 17 | you should download and install `Docker `_ and 18 | ensure that you can run the ``docker`` command. 19 | 20 | With those basics installed and ready, we can build an empty project and work from there, but first 21 | we'll talk about the application we will be building. 22 | 23 | .. [#dockerfn] A Docker container is 24 | the `Inception `_ of computers; a 25 | container is essentially a 26 | light-weight virtual machine that runs inside your computer. 27 | 28 | To the `PostgreSQL `_ server we'll be running inside the container, it will appear as if 29 | the entire computer is running Linux, just as if Linux and PostgreSQL were installed 30 | on a bare-metal computer. 31 | 32 | Docker images 33 | are smaller and less demanding than full operating system virtual machines. In fact 34 | frequently you will run several interconnected containers together. 35 | 36 | Docker includes infrastructure for downloading the images from a central repository. 37 | Ultimately, it's faster and easier to get PostgreSQL running 38 | inside a container that to install the database onto your computer. 39 | -------------------------------------------------------------------------------- /docs/tutorial/wrap-up.rst: -------------------------------------------------------------------------------- 1 | Tutorial Wrapup 2 | =============== 3 | 4 | That's as far as we've made it with the tutorial so far. 5 | 6 | Looking forward to more updates, someday :-). 7 | -------------------------------------------------------------------------------- /docs/unions.rst: -------------------------------------------------------------------------------- 1 | Unions 2 | ====== 3 | 4 | A union type is a type that may be any of a list of possible objects. 5 | 6 | .. sidebar:: GraphQL Spec 7 | 8 | Read about :spec:`unions `. 9 | 10 | A union is a type defined in terms of different objects: 11 | 12 | .. literalinclude:: _examples/union-definition.edn 13 | :language: clojure 14 | 15 | A union definition must include a ``:members`` key, a sequence of object types. 16 | 17 | The above example identifies the ``SearchResult` union type to be either a ``Person`` (with fields 18 | ``name`` and ``age``), or a ``Photo`` (with fields ``imageURL``, ``title``, ``height``, and ``width``). 19 | 20 | Unions must define at least one type; each member type must be an object type (members may not be 21 | reference scalar types, interfaces, or other unions). 22 | 23 | When a client makes a union request, they must use the fragment spread syntax to identify what 24 | is to be returned based on the runtime type of object: 25 | 26 | .. code-block:: js 27 | 28 | { search (term:"ewok") { 29 | ... on Person { name } 30 | ... on Photo { imageURL title } 31 | }} 32 | 33 | This breaks down what will be returned in the result map based on the type of the value produced 34 | by the ``search`` query. Sometimes there will be a ``name`` key in the result, and other times 35 | an ``image-url`` and ``title`` key. 36 | This may vary result by result even within a single request: 37 | 38 | .. literalinclude:: _examples/unions-query-response.edn 39 | :language: clojure 40 | 41 | .. tip:: 42 | 43 | When a field or operation type is a union, 44 | the field resolver may return any of a number of different 45 | concrete object types, and Lacinia has no way to determine which; 46 | this information must be :doc:`explicitly provided `. 47 | -------------------------------------------------------------------------------- /perf/.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | *.pdf 3 | -------------------------------------------------------------------------------- /perf/create-charts.R: -------------------------------------------------------------------------------- 1 | #!Rscript 2 | 3 | # Execute with `Rscript create-charts.R` 4 | # Reads perf/benchmarks.csv and generates two PNG charts into perf 5 | 6 | library("ggplot2") 7 | 8 | uniquefactor <- function(v) { 9 | factor(v, ordered=TRUE, levels=unique(v)) 10 | } 11 | 12 | # Read the benchmarks in as 'b' 13 | 14 | # Need to do some work here to convert the commit and kind columns into ordered factors. 15 | # There's probably some mojo to do it all in the call to read.csv, but this works. 16 | 17 | b <- read.csv("benchmarks.csv", stringsAsFactors=FALSE) 18 | b$date = factor(b$date, ordered=TRUE) 19 | b$commit = uniquefactor(b$commit) 20 | b$kind = factor(b$kind, ordered=TRUE) 21 | 22 | create_chart <- function(file_name, type_name, data) { 23 | parse.plot <- 24 | qplot(b$commit, data, color=b$kind, shape=b$kind, group=b$kind) + 25 | geom_point(size=4) + 26 | labs(x="Commit", 27 | y=paste(type_name ,"Time (ms)"), 28 | title=paste(type_name, "Time Benchmarks"), 29 | color="Benchmark", 30 | shape="Benchmark") + 31 | geom_path() + 32 | theme(plot.title = element_text(size=18), 33 | axis.text.x=element_text(angle=45, hjust=1)) 34 | 35 | cat("Saving chart as ", file_name, "\n", sep="") 36 | 37 | ggsave(file_name, parse.plot, width=10, height=7) 38 | } 39 | 40 | create_chart("parse-time.pdf", "Parse", b$parse) 41 | create_chart("exec-time.pdf", "Execution", b$exec) 42 | -------------------------------------------------------------------------------- /src/com/walmartlabs/lacinia/constants.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns ^:no-doc com.walmartlabs.lacinia.constants 16 | "A handy place to define namespaced constants") 17 | 18 | (def schema-key 19 | "Context key storing the compiled schema." 20 | ::schema) 21 | 22 | (def parsed-query-key 23 | "Context key storing the parsed and prepared query." 24 | ::parsed-query) 25 | 26 | (def ^{:added "0.17.0"} selection-key 27 | "Context key storing the current selection." 28 | :com.walmartlabs.lacinia/selection) 29 | -------------------------------------------------------------------------------- /src/com/walmartlabs/lacinia/describe.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2021-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | ; 15 | (ns ^:no-doc com.walmartlabs.lacinia.describe) 16 | 17 | (defprotocol Describe 18 | 19 | "Describe an object, usually for inclusion as part of an error message." 20 | 21 | (description-for [_])) -------------------------------------------------------------------------------- /src/com/walmartlabs/lacinia/expound.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.expound 16 | "Adds improved spec messages to Lacinia specs, when using [Expound](https://github.com/bhb/expound). 17 | 18 | This namespace should simply be required; it exports no functions or constants. 19 | 20 | Expound is an optional library for Lacinia and must be added to your project explicitly." 21 | {:added "0.26.0"} 22 | (:require 23 | [expound.alpha :refer [defmsg]] 24 | [com.walmartlabs.lacinia.schema :as schema])) 25 | 26 | (defmsg ::schema/resolver-type "implement the com.walmartlabs.lacinia.resolve/FieldResolver protocol") 27 | 28 | (defmsg ::schema/wrapped-type-modifier "type wrappers should be either (list type) or (non-null type)") 29 | 30 | (defmsg ::schema/graphql-identifier "must be a valid GraphQL identifier: contain only letters, numbers, and underscores") 31 | 32 | (defmsg ::schema/not-a-conformer "scalar parse and serialize functions must now be simple functions, not clojure.spec conformers (see release notes)") 33 | -------------------------------------------------------------------------------- /src/com/walmartlabs/lacinia/parser/docs.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.parser.docs 16 | "Parsing of a Markdown file for purposes of attaching documentation to a schema." 17 | {:added "0.27.0"} 18 | (:require 19 | [clojure.string :as str] 20 | [com.walmartlabs.lacinia.internal-utils :refer [cond-let]])) 21 | 22 | (def ^:private header-re 23 | ;; Don't really care how deep the heading is 24 | #"\#+\s+(.*?)\s*$") 25 | 26 | (defn ^:private combine-block 27 | [lines] 28 | (->> lines 29 | (str/join "\n") 30 | str/trim)) 31 | 32 | (defn parse-docs 33 | "Parses an input document. Returns a map who keys are the keyword versions of headers, and whose values 34 | are the (trimmed) content immediately beneath that header. 35 | 36 | The result is a documentation map that can be provided to [[inject-descriptions]]." 37 | [input] 38 | (loop [lines (str/split-lines input) 39 | result {} 40 | header nil 41 | block []] 42 | (cond-let 43 | (nil? lines) 44 | (if header 45 | (assoc result header (combine-block block)) 46 | result) 47 | 48 | :let [line (first lines) 49 | more-lines (next lines) 50 | [_ new-header] (re-matches header-re line)] 51 | 52 | (some? new-header) 53 | (recur more-lines 54 | (if header 55 | (assoc result header (combine-block block)) 56 | result) 57 | (keyword new-header) 58 | []) 59 | 60 | (some? header) 61 | (recur more-lines result header (conj block line)) 62 | 63 | :else 64 | ;; In the preamble, if any, before first header. 65 | (recur more-lines result header block)))) 66 | -------------------------------------------------------------------------------- /src/com/walmartlabs/lacinia/validation/no_unused_fragments.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.validation.no-unused-fragments 16 | {:no-doc true} 17 | (:require 18 | [clojure.set :as set] 19 | [com.walmartlabs.lacinia.internal-utils :refer [q cond-let]]) 20 | (:import (clojure.lang PersistentQueue))) 21 | 22 | (defn ^:private all-fragments-used 23 | [fragments root-selections] 24 | (loop [result (transient #{}) 25 | queue (-> (PersistentQueue/EMPTY) 26 | (into root-selections) 27 | (into (vals fragments)))] 28 | (cond-let 29 | 30 | :let [selection (peek queue)] 31 | 32 | (nil? selection) 33 | (persistent! result) 34 | 35 | :let [{:keys [fragment-name]} selection 36 | queue' (pop queue)] 37 | 38 | ;; Named fragments do not, themselves, have sub-selections 39 | fragment-name 40 | (recur (conj! result fragment-name) queue') 41 | 42 | :let [sub-selections (:selections selection)] 43 | 44 | (seq sub-selections) 45 | (recur result (into queue' sub-selections)) 46 | 47 | :else 48 | (recur result queue')))) 49 | 50 | (defn no-unused-fragments 51 | "Validates if all fragment definitions are spread 52 | within operations, or spread within other fragments 53 | spread within operations." 54 | [prepared-query] 55 | (let [{:keys [fragments selections]} prepared-query 56 | f-locations (into {} (map (fn [[f-name {location :location}]] 57 | {f-name location}) 58 | fragments)) 59 | f-definitions (set (keys fragments)) 60 | f-names-used (all-fragments-used fragments selections)] 61 | (for [unused-f-definition (set/difference f-definitions f-names-used)] 62 | {:message (format "Fragment %s is never used." 63 | (q unused-f-definition)) 64 | :locations [(unused-f-definition f-locations)]}))) 65 | -------------------------------------------------------------------------------- /src/com/walmartlabs/lacinia/validation/scalar_leafs.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.validation.scalar-leafs 16 | {:no-doc true} 17 | (:require 18 | [com.walmartlabs.lacinia.internal-utils :refer [q cond-let]] 19 | [com.walmartlabs.lacinia.selection :as selection])) 20 | 21 | (defn ^:private validate-selection 22 | "Recursively checks if all specified fields are scalar or enum types. 23 | Non-scalar fields should contain either nested :fields 24 | or :fragments. 25 | Fragments are not validated again, only their presence is checked. 26 | Returns empty sequence if all fields are valid, otherwise returns 27 | a sequence of error maps, e.g. 28 | `[{:message \"Field `friends' (of type `character')must have at least one selection.\" 29 | :locations [{:line 1 :column 7}]}]`" 30 | [selection] 31 | (cond-let 32 | ;; Fragment spreads do not ever have sub-selections, and are validated 33 | ;; elsewhere. 34 | (= :named-fragment (selection/selection-kind selection)) 35 | nil 36 | 37 | (:leaf? selection) 38 | nil 39 | 40 | :let [sub-selections (:selections selection)] 41 | 42 | (seq sub-selections) 43 | (mapcat validate-selection sub-selections) 44 | 45 | :else 46 | [{:message (format "Field %s must have at least one selection." 47 | (-> selection :field-definition :qualified-name q)) 48 | :locations [(:location selection)]}])) 49 | 50 | (defn ^:private validate-fragment 51 | "Validates fragment once to avoid validating it separately for 52 | each selection." 53 | [[_ fragment]] 54 | (validate-selection fragment)) 55 | 56 | (defn scalar-leafs 57 | "A GraphQL query is valid only if all leaf nodes (fields without 58 | sub selections) are of scalar or enum types." 59 | [prepared-query] 60 | (let [selections (:selections prepared-query) 61 | fragments (:fragments prepared-query)] 62 | (concat 63 | (mapcat validate-fragment fragments) 64 | (mapcat validate-selection selections)))) 65 | -------------------------------------------------------------------------------- /src/com/walmartlabs/lacinia/validator.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.validator 16 | "Implements query validation (eg. typechecking of vars, fragment types, etc.), 17 | but also a place where complexity analysis may someday occur." 18 | {:no-doc true} 19 | (:require [com.walmartlabs.lacinia.validation.scalar-leafs :refer [scalar-leafs]] 20 | [com.walmartlabs.lacinia.validation.fragments :refer [validate-fragments]] 21 | [com.walmartlabs.lacinia.validation.no-unused-fragments 22 | :refer [no-unused-fragments]])) 23 | 24 | 25 | ;;------------------------------------------------------------------------------- 26 | ;; ## Rules 27 | 28 | (def ^:private default-rules 29 | [scalar-leafs 30 | 31 | ;; fragments 32 | validate-fragments 33 | no-unused-fragments]) 34 | 35 | ;; ————————————————————————————————————————————————————————————————————————————— 36 | ;; ## Public API 37 | 38 | (defn validate 39 | "Performs validation of the parsed and prepared query against 40 | a set of default rules. 41 | 42 | The 3-arity version is for compatibility (especially w.r.t. lacinia-pedestal). 43 | 44 | Returns a sequence of error maps, which will be empty if there are no errors." 45 | ([prepared-query] 46 | (mapcat #(% prepared-query) default-rules)) 47 | ([_schema prepared-query _opts] 48 | (validate prepared-query))) 49 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/documentation_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.documentation-test 16 | "Tests that documentation for fields and field args can be inhertited from interfaces." 17 | (:require 18 | [clojure.test :refer [deftest is]] 19 | [com.walmartlabs.test-utils :as utils])) 20 | 21 | (def ^:private schema 22 | (utils/compile-schema "doc-inheritance-schema.edn" {})) 23 | 24 | (defn ^:private q [query] 25 | (utils/execute schema query)) 26 | 27 | (deftest field-description-may-inherit-from-interface 28 | (is (= {:data 29 | {:ex1 30 | {:fields 31 | [{:description "ex1/alpha" 32 | :name "alpha"} 33 | {:description "sierra/bravo" 34 | :name "bravo"}]} 35 | :ex2 36 | {:fields 37 | [{:description "ex2/alpha" 38 | :name "alpha"} 39 | {:description "ex2/bravo" 40 | :name "bravo"}]}}} 41 | (q " 42 | { ex1: __type (name: \"ex1\") { 43 | 44 | fields { 45 | name 46 | description 47 | } 48 | } 49 | 50 | ex2: __type(name: \"ex2\") { 51 | fields { 52 | name 53 | description 54 | } 55 | } 56 | }")))) 57 | 58 | (deftest arg-description-may-inherit-from-interface 59 | (is (= {:data 60 | {:ex3 61 | {:fields 62 | [{:args 63 | [{:description "ex3/delta" 64 | :name "delta"} 65 | {:description "tango/charlie/echo" 66 | :name "echo"}]}]}}} 67 | (q " 68 | { ex3: __type(name: \"ex3\") { 69 | fields { 70 | args { 71 | name 72 | description 73 | } 74 | } 75 | } 76 | }")))) 77 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/expound_tests.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.expound-tests 16 | "Tests that useful Expound messages are emitted for spec failures." 17 | (:require 18 | [clojure.test :refer [deftest is use-fixtures]] 19 | [com.walmartlabs.test-reporting :refer [reporting]] 20 | com.walmartlabs.lacinia.expound 21 | [com.walmartlabs.lacinia.schema :as schema] 22 | [com.walmartlabs.lacinia.parser.schema :as ps] 23 | [clojure.spec.alpha :as s] 24 | [clojure.string :as str])) 25 | 26 | (defmacro expect 27 | [spec value & substrings] 28 | `(let [explain# (with-out-str 29 | (s/explain ~spec ~value))] 30 | (reporting {:explain explain#} 31 | (doseq [s# ~(vec substrings)] 32 | (is (str/includes? explain# s#)))))) 33 | 34 | (deftest uses-messages 35 | (expect ::schema/resolve nil 36 | "fn?" 37 | "implement the com.walmartlabs.lacinia.resolve/FieldResolver protocol")) 38 | 39 | (deftest correctly-reports-incorrect-type-modifier 40 | (expect ::schema/field {:type '(something :String)} 41 | "type wrappers should be either (list type) or (non-null type)")) 42 | 43 | (deftest can-report-enum-value 44 | (expect ::schema/enum-value 123 "string?" "simple-symbol?" "simple-keyword?") 45 | (expect ::schema/enum-value "this-and-that" "must be a valid GraphQL identifier")) 46 | 47 | (deftest sdl-function-map 48 | (expect ::ps/fn-map {:foo :bar :gnip {:q 'gnop}} 49 | "fn?" "simple-keyword?")) 50 | 51 | ;; Really want a good message for this incompatible change in 0.31.0 52 | 53 | (deftest parse-and-serialize-must-be-bare-functions 54 | (expect ::schema/parse (schema/as-conformer identity) 55 | "scalar parse and serialize functions must now be simple functions" 56 | "not clojure.spec conformers" 57 | "see release notes")) 58 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/extensions_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2018-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.extensions-test 16 | (:require 17 | [clojure.test :refer [deftest is]] 18 | [com.walmartlabs.lacinia.resolve :as r] 19 | [com.walmartlabs.test-utils :refer [compile-schema execute]])) 20 | 21 | (def ^:private compiled-schema 22 | (compile-schema "extensions-schema.edn" 23 | {:queries/extension (fn [_ _ _] 24 | (r/with-extensions "OK" 25 | assoc-in [:foo :bar] :baz)) 26 | :queries/warning (fn [_ _ _] 27 | (r/with-warning "WARN" 28 | {:message "Warning!" 29 | :foo :bar}))})) 30 | 31 | (deftest with-extensions 32 | (is (= {:data {:extension "OK"} 33 | :extensions {:foo {:bar :baz}}} 34 | (execute compiled-schema "{ extension }")))) 35 | 36 | (deftest with-warning 37 | (is (= {:data {:warning "WARN"} 38 | :extensions {:warnings [{:extensions {:foo :bar} 39 | :locations [{:column 3 40 | :line 1}] 41 | :message "Warning!" 42 | :path [:warning]}]}} 43 | (execute compiled-schema "{ warning }")))) 44 | 45 | (deftest combined 46 | (is (= {:data {:extension "OK" 47 | :warning "WARN"} 48 | :extensions {:foo {:bar :baz} 49 | :warnings [{:extensions {:foo :bar} 50 | :locations [{:column 3 51 | :line 1}] 52 | :message "Warning!" 53 | :path [:warning]}]}} 54 | (execute compiled-schema "{ warning extension }")))) 55 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/field_tests.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2019-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.field-tests 16 | (:require 17 | [clojure.test :refer [deftest is]] 18 | [com.walmartlabs.test-utils :refer [expect-exception]] 19 | [com.walmartlabs.lacinia.schema :as schema])) 20 | 21 | (deftest field-references-unknown-type 22 | (expect-exception 23 | "Field `Insect/legs' references unknown type `Six'." 24 | {:field-name :Insect/legs 25 | :schema-types {:object [:Insect 26 | :Mutation 27 | :Query 28 | :Subscription] 29 | :scalar [:Boolean 30 | :Float 31 | :ID 32 | :Int 33 | :String]}} 34 | (schema/compile {:objects 35 | {:Insect 36 | {:fields 37 | {:legs {:type :Six}}}}}))) 38 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/input_types_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2021-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.input-types-test 16 | (:require [clojure.test :refer [deftest is]] 17 | [com.walmartlabs.test-utils :refer [execute compile-schema-injected]])) 18 | 19 | (deftest mix-of-literals-and-dynamics 20 | (let [resolver (fn [_ args _] 21 | (is (= {:filter {:or [{:color {:equals "blue"}} 22 | {:color {:equals "red"}}]}} 23 | args)) 24 | {:nodes [{:color "blue"}]}) 25 | schema (compile-schema-injected "dyn-args-schema.edn" 26 | {:queries/cars resolver}) 27 | query "query($color: String) { 28 | cars(filter: {or: [{color: {equals: \"blue\"}}, 29 | {color: {equals: $color}}]}) { 30 | nodes { 31 | color 32 | } 33 | } 34 | }" 35 | args {:color "red"}] 36 | (is (= {:data 37 | {:cars 38 | {:nodes [{:color "blue"}]}}} 39 | (execute schema query args nil))))) 40 | 41 | 42 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/invariant_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.walmartlabs.lacinia.invariant-test 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [clojure.test :refer [deftest testing is]] 6 | [com.walmartlabs.lacinia :as lacinia] 7 | [com.walmartlabs.lacinia.schema :as schema] 8 | [com.walmartlabs.lacinia.util :as util] 9 | [com.walmartlabs.lacinia.parser :refer [parse-query invariant? prepare-with-query-variables]] 10 | [com.walmartlabs.test-schema :refer [test-schema]] 11 | [com.walmartlabs.test-utils :as utils :refer [simplify]])) 12 | 13 | (def ^:private compiled-schema (schema/compile test-schema)) 14 | 15 | (deftest invariant-query-is-unchanged-by-prepare 16 | (let [parsed (parse-query compiled-schema "{ hero { name } }") 17 | prepared (prepare-with-query-variables parsed nil)] 18 | (is (invariant? parsed)) 19 | (is (identical? parsed prepared)))) 20 | 21 | (defmacro expect-invariant 22 | [q] 23 | `(is (= true (invariant? (parse-query compiled-schema ~q))))) 24 | 25 | (defmacro expect-variant 26 | [q] 27 | `(is (= false (invariant? (parse-query compiled-schema ~q))))) 28 | 29 | (deftest field-args-do-not-trigger-variant 30 | (expect-invariant "{ hero (episode: JEDI) { name }}")) 31 | 32 | (deftest fragments-do-not-trigger-variant 33 | (expect-invariant " 34 | { 35 | hero { ...character } 36 | } 37 | 38 | fragment character on character { id name } 39 | ")) 40 | 41 | (deftest skip-or-include-are-variant 42 | (expect-variant "{ hero { id name @skip(if: true) } }") 43 | 44 | (expect-variant " 45 | { 46 | hero { ...character } 47 | } 48 | 49 | fragment character on character { id @skip(if: false) name }")) 50 | 51 | (deftest unused-variables-are-invariant 52 | (expect-invariant 53 | "query ($id: String) { 54 | hero { name } 55 | }")) 56 | 57 | (deftest used-variables-are-variant 58 | (expect-variant 59 | "query ($ep: episode) { 60 | hero(episode: $ep) { name } 61 | }") 62 | 63 | 64 | (expect-variant " 65 | query($ep: episode) { ...heroQuery } 66 | 67 | fragment heroQuery on Query { 68 | hero(episode: $ep) { name } 69 | } 70 | 71 | ")) 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/mutable_context_tests.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.mutable-context-tests 16 | (:require 17 | [clojure.test :refer [deftest is]] 18 | [com.walmartlabs.lacinia.resolve :as resolve] 19 | [com.walmartlabs.test-utils :refer [compile-schema execute]])) 20 | 21 | (def ^:private schema (compile-schema "mutable-context-schema.edn" 22 | {:root (fn [_ args _] 23 | (cond-> {:container {:id "0001" 24 | :leaf "DEFAULT"}} 25 | (:trigger args) (resolve/with-context {::leaf-value "OVERRIDE"}))) 26 | :leaf (fn [context _ container] 27 | (or (::leaf-value context) 28 | (:leaf container)))})) 29 | 30 | (deftest resolver-may-modify-nested-context 31 | (is (= {:data {:disabled {:container {:id "0001" 32 | :leaf "DEFAULT"}} 33 | :enabled {:container {:id "0001" 34 | :leaf "OVERRIDE"}}}} 35 | (execute schema 36 | "{ enabled: root(trigger: true) { container { id leaf }} 37 | disabled: root { container { id leaf }}}")))) 38 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/parser/docs_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.parser.docs-test 16 | (:require 17 | [clojure.test :refer [deftest is]] 18 | [com.walmartlabs.lacinia.parser.docs :refer [parse-docs]] 19 | [clojure.java.io :as io])) 20 | 21 | (defn ^:private parse 22 | [path] 23 | (-> path 24 | io/resource 25 | slurp 26 | parse-docs)) 27 | 28 | (deftest basic-parse 29 | (is (= {:Character "A person who appears in one of the movies. 30 | 31 | Persons are more than humans, and may include droids and computers." 32 | :Character/name "The primary name of the character. 33 | 34 | For droids, this is the announced name, such as \"AreToo\"."} 35 | (parse "basic-docs.md")))) 36 | 37 | (deftest empty-parse 38 | (is (= {} 39 | (parse-docs "")))) 40 | 41 | (deftest ignores-before-first-header 42 | (is (= {:Customer "Someone we sell to."} 43 | (parse "preamble-docs.md")))) 44 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/query_ops_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.query-ops-test 16 | (:require 17 | [clojure.test :refer [deftest is]] 18 | [com.walmartlabs.test-utils :refer [compile-schema execute]] 19 | [com.walmartlabs.test-schema :refer [test-schema]] 20 | [com.walmartlabs.lacinia.schema :as schema])) 21 | 22 | (def default-schema (schema/compile test-schema)) 23 | 24 | (deftest may-identify-op-when-single-op 25 | (is (= {:data {:human {:name "Han Solo"}}} 26 | (execute default-schema 27 | "query solo($id: String!) { human(id: $id) { name } }" 28 | {:id "1002"} 29 | nil 30 | {:operation-name "solo"})))) 31 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/roots_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.roots-test 16 | "Tests related to specifying operation root object names." 17 | (:require 18 | [clojure.test :refer [deftest is]] 19 | [com.walmartlabs.lacinia.schema :as schema] 20 | [com.walmartlabs.test-utils :refer [execute compile-schema]])) 21 | 22 | (deftest default-root-name 23 | (let [schema (schema/compile {})] 24 | (is (= {:data {:__schema {:queryType {:name "Query"}}}} 25 | (execute schema "{__schema { queryType { name } } }"))))) 26 | 27 | (deftest can-specify-root-name 28 | (let [schema (schema/compile {:roots {:query :MyQueries}})] 29 | (is (= {:data {:__schema {:queryType {:name "MyQueries"}}}} 30 | (execute schema "{__schema { queryType { name } } }"))))) 31 | 32 | (deftest root-object-may-contain-fields 33 | (let [schema (compile-schema "root-object-schema.edn" 34 | {:queries/fred (constantly "Flintstone") 35 | :Barney/last-name (constantly "Rubble")})] 36 | (is (= {:data {:barney "Rubble" 37 | :fred "Flintstone"}} 38 | (execute schema "{ 39 | fred 40 | barney: last_name 41 | }") 42 | )))) 43 | 44 | (deftest name-collisions-are-detected 45 | (let [e (is (thrown? Exception 46 | (compile-schema "root-object-with-conflicts-schema.edn" 47 | {:queries/fred (constantly "Flintstone") 48 | :Barney/last-name (constantly "Rubble")})))] 49 | (is (= "Name collision compiling schema: `Barney/last_name' already exists with value from :queries." 50 | (.getMessage e))) 51 | (is (= {:field-name :last_name} 52 | (ex-data e))))) 53 | 54 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/timeout_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2018-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.timeout-test 16 | (:require 17 | [clojure.test :refer [deftest is]] 18 | [com.walmartlabs.lacinia :as lacinia] 19 | [com.walmartlabs.lacinia.schema :as schema])) 20 | 21 | (defn create-schema 22 | [timeout-ms] 23 | (schema/compile 24 | {:queries {:wait {:type :String 25 | :resolve (fn [_ _ _] 26 | (Thread/sleep timeout-ms) 27 | "ok")}}})) 28 | 29 | (def ^:private expected-result {:data {:wait "ok"}}) 30 | 31 | 32 | (defmacro timed 33 | [& body] 34 | `(let [start# (System/currentTimeMillis) 35 | result# (do ~@body) 36 | elapsed# (- (System/currentTimeMillis) start#)] 37 | [elapsed# result#])) 38 | 39 | (defn execute 40 | [schema options] 41 | (lacinia/execute schema "{ wait }" nil nil options)) 42 | 43 | (deftest no-timeout 44 | (let [schema (create-schema 100) 45 | [elapsed result] (timed (execute schema nil))] 46 | (is (= expected-result result)) 47 | (is (<= 100 elapsed)))) 48 | 49 | (deftest explicit-timeout 50 | (let [schema (create-schema 100) 51 | [elapsed result] (timed (execute schema {:timeout-ms 20}))] 52 | (is (= {:errors [{:message "Query execution timed out."}]} result)) 53 | ;; Allow for some overhead ... 54 | (is (<= 20 elapsed 30)))) 55 | 56 | (deftest explicit-error 57 | (let [schema (create-schema 100) 58 | [elapsed result] (timed (execute schema {:timeout-ms 50 59 | :timeout-error {:message "Too slow!"}}))] 60 | (is (= {:errors [{:message "Too slow!"}]} result)) 61 | ;; Allow for some overhead ... 62 | (is (<= 50 elapsed 60)))) 63 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/typename_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.typename-test 16 | "Tests for the __typename metadata field on objects." 17 | (:require 18 | [clojure.test :refer [deftest is]] 19 | [com.walmartlabs.lacinia.schema :as schema] 20 | [com.walmartlabs.lacinia :refer [execute]] 21 | [com.walmartlabs.test-utils :refer [simplify compile-schema]])) 22 | 23 | (defn ^:private get-man 24 | [_ _ _] 25 | {:name "Adam"}) 26 | 27 | (defn ^:private get-machine 28 | [_ _ _] 29 | {:serial "X01"}) 30 | 31 | (defn ^:private get-all 32 | [_ _ _] 33 | [(schema/tag-with-type (get-man nil nil nil) :man) 34 | (schema/tag-with-type (get-machine nil nil nil) :machine)]) 35 | 36 | (def ^:private compiled-schema 37 | (compile-schema "typename-schema.edn" 38 | {:get-man get-man 39 | :get-machine get-machine 40 | :get-all get-all})) 41 | 42 | (defn ^:private q 43 | [query] 44 | (simplify 45 | (execute compiled-schema query nil nil))) 46 | 47 | (deftest type-name-on-static-type 48 | ;; The :get_man query has a known concrete type, so 49 | ;; the type tag is supplied by Lacinia. 50 | (is (= {:data 51 | {:get_man 52 | {:__typename :man 53 | :name "Adam"}}} 54 | (q "{ get_man { __typename name }}")))) 55 | 56 | (deftest type-name-on-dynamic-types 57 | (is (= {:data {:get_all [{:__typename :man 58 | :name "Adam"} 59 | {:__typename :machine 60 | :serial "X01"}]}} 61 | (q "{ get_all { __typename 62 | ... on man { name } 63 | ... on machine { serial } 64 | } 65 | }")))) 66 | -------------------------------------------------------------------------------- /test/com/walmartlabs/lacinia/wrapped_values_in_lists_tests.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2017-present Walmart, Inc. 2 | ; 3 | ; Licensed under the Apache License, Version 2.0 (the "License") 4 | ; you may not use this file except in compliance with the License. 5 | ; You may obtain a copy of the License at 6 | ; 7 | ; http://www.apache.org/licenses/LICENSE-2.0 8 | ; 9 | ; Unless required by applicable law or agreed to in writing, software 10 | ; distributed under the License is distributed on an "AS IS" BASIS, 11 | ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ; See the License for the specific language governing permissions and 13 | ; limitations under the License. 14 | 15 | (ns com.walmartlabs.lacinia.wrapped-values-in-lists-tests 16 | "Tests that wrapped values inside lists (returned by field resolvers) are properly handled." 17 | (:require 18 | [clojure.test :refer [deftest is]] 19 | [com.walmartlabs.lacinia.resolve :refer [with-context]] 20 | [com.walmartlabs.lacinia.schema :as schema] 21 | [com.walmartlabs.test-utils :as utils])) 22 | 23 | 24 | 25 | (def ^:private compiled-schema 26 | (schema/compile 27 | {:objects 28 | {:IdObj 29 | {:fields 30 | {:id {:type 'String} 31 | :xid {:type 'String 32 | :resolve (fn [context _ obj] 33 | (str (::root-prefix context) "-" 34 | (::item-prefix context) "-" 35 | (:id obj)))}}}} 36 | 37 | :queries 38 | {:objects 39 | {:type '(list :IdObj) 40 | :args {:root_prefix {:type 'String}} 41 | :resolve (fn [_ args _] 42 | (with-context 43 | (->> [100 200 555] 44 | (map #(hash-map :id (format "%05d" %))) 45 | (map-indexed (fn [i v] 46 | (with-context v 47 | {::item-prefix (str "i" i)})))) 48 | {::root-prefix (:root_prefix args)}))}}})) 49 | 50 | 51 | (deftest exposes-context-to-sub-resolvers 52 | (is (= {:data {:objects [{:id "00100" 53 | :xid "r-i0-00100"} 54 | {:id "00200" 55 | :xid "r-i1-00200"} 56 | {:id "00555" 57 | :xid "r-i2-00555"}]}} 58 | (utils/execute compiled-schema 59 | "{ objects(root_prefix: \"r\") { id xid }}")))) 60 | --------------------------------------------------------------------------------