├── .gitignore ├── .merlin ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── DEVELOPING.md ├── LICENSE ├── Makefile ├── README.md ├── appveyor.yml ├── azure-pipelines.yml ├── bsconfig.json ├── ci ├── azure_run.sh ├── before_script_linux.sh ├── before_script_osx.sh ├── build_script_linux.sh ├── build_script_osx.sh ├── download_binaries.sh ├── travis_before_install.sh └── wait_for_builds.sh ├── convertSchema.js ├── copyPlatformBinaryInPlace.js ├── discover ├── discover.ml └── dune ├── doc ├── missing_variables.gif ├── misspelled_field.gif └── removed_field.gif ├── dune ├── dune-project ├── dune-workspace.dev-bs ├── dune-workspace.dev-native ├── esy.json ├── esyi.lock.json ├── graphql_ppx.opam ├── graphql_ppx_base.opam ├── graphql_schema.json ├── jbuild-ignore ├── package.json ├── refresh-switches.sh ├── schema.gql ├── sendIntrospectionQuery.js ├── src ├── base │ ├── ast_serializer_apollo.ml │ ├── build_config.cppo.ml │ ├── compat.ml │ ├── dirty_checker.ml │ ├── dune │ ├── generator_utils.ml │ ├── graphql_ast.ml │ ├── graphql_lexer.ml │ ├── graphql_parser.ml │ ├── graphql_parser_document.ml │ ├── graphql_parser_value.ml │ ├── graphql_ppx_base.ml │ ├── graphql_printer.ml │ ├── log.ml │ ├── multi_visitor.ml │ ├── option.ml │ ├── ppx_config.ml │ ├── read_schema.ml │ ├── result_decoder.ml │ ├── result_ext.ml │ ├── result_structure.ml │ ├── rule_known_argument_names.ml │ ├── rule_no_unused_variables.ml │ ├── schema.ml │ ├── source_pos.ml │ ├── traversal_utils.ml │ ├── type_utils.ml │ └── validations.ml ├── bucklescript │ ├── dune │ ├── graphql_ppx.ml │ ├── output_bucklescript_decoder.ml │ ├── output_bucklescript_encoder.ml │ ├── output_bucklescript_module.ml │ ├── output_bucklescript_unifier.ml │ └── output_bucklescript_utils.ml ├── dune └── native │ ├── dune │ ├── graphql_ppx.ml │ ├── output_native_decoder.ml │ ├── output_native_encoder.ml │ ├── output_native_module.ml │ ├── output_native_unifier.ml │ └── output_native_utils.ml ├── tests_apollo ├── __tests__ │ └── astOutput.re └── bsconfig.json ├── tests_bucklescript ├── __tests__ │ ├── apolloMode.re │ ├── apolloMode.rei │ ├── argNamedQuery.re │ ├── argNamedQuery.rei │ ├── comment.re │ ├── customDecoder.re │ ├── customDecoder.rei │ ├── customScalars.re │ ├── enumInput.re │ ├── enumInput.rei │ ├── fragmentDefinition.re │ ├── fragmentDefinition.rei │ ├── interface.re │ ├── interface.rei │ ├── lists.re │ ├── lists.rei │ ├── listsArgs.re │ ├── listsArgs.rei │ ├── listsInput.re │ ├── listsInput.rei │ ├── mutation.re │ ├── mutation.rei │ ├── nested.re │ ├── nested.rei │ ├── nonrecursiveInput.re │ ├── nonrecursiveInput.rei │ ├── record.re │ ├── record.rei │ ├── recursiveInput.re │ ├── recursiveInput.rei │ ├── scalars.re │ ├── scalars.rei │ ├── scalarsArgs.re │ ├── scalarsArgs.rei │ ├── scalarsInput.re │ ├── scalarsInput.rei │ ├── skipDirectives.re │ ├── skipDirectives.rei │ ├── subscription.re │ ├── subscription.rei │ ├── typename.re │ ├── typename.rei │ ├── union.re │ ├── union.rei │ ├── unionPartial.re │ ├── unionPartial.rei │ ├── variant.re │ └── variant.rei └── bsconfig.json ├── tests_native ├── arg_named_query.ml ├── comment.ml ├── custom_decoder.ml ├── custom_scalars.ml ├── dune ├── enum_input.ml ├── fragment_definition.ml ├── interface.ml ├── list_args.ml ├── list_inputs.ml ├── lists.ml ├── main.ml ├── mutation.ml ├── nested.ml ├── nonrecursive_input.ml ├── record.ml ├── recursive_input.ml ├── scalars.ml ├── scalars_args.ml ├── scalars_input.ml ├── skip_directives.ml ├── test_shared.ml ├── typename.ml ├── union.ml ├── union_partial.ml └── variant.ml └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /bin 3 | /graphql_ppx*.exe 4 | /node_modules 5 | /tests_bucklescript/lib 6 | /tests_bucklescript/graphql_schema.json 7 | /tests_bucklescript/node_modules 8 | /tests_apollo/lib 9 | /**/.graphql_ppx_cache 10 | /tests_apollo/graphql_schema.json 11 | /tests_apollo/node_modules 12 | /lib 13 | /ppx 14 | /graphql_ppx.exe 15 | dune.flags 16 | jbuild.flags 17 | .merlin 18 | _opam 19 | -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | S src 2 | S tests 3 | B _build 4 | B _build/src 5 | 6 | ####{BSB GENERATED: NO EDIT 7 | FLG -ppx /Users/mhallin/Dropbox/Projects/lang_ocaml/graphql_ppx/./graphql_ppx.native 8 | FLG -ppx /Users/mhallin/Dropbox/Projects/lang_ocaml/graphql_ppx/node_modules/bs-platform/bin/bsppx.exe 9 | S /Users/mhallin/Dropbox/Projects/lang_ocaml/graphql_ppx/node_modules/bs-platform/lib/ocaml 10 | B /Users/mhallin/Dropbox/Projects/lang_ocaml/graphql_ppx/node_modules/bs-platform/lib/ocaml 11 | FLG -nostdlib -no-alias-deps -color always -w -40+6+7+27+32..39+44+45 12 | S tests 13 | B lib/bs/tests 14 | ####BSB GENERATED: NO EDIT} 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /.vscode 3 | /graphql_ppx.exe 4 | /node_modules 5 | /tests_bucklescript/lib 6 | /tests_apollo/lib 7 | /lib 8 | /ppx 9 | /graphql_ppx.* 10 | /doc 11 | /lib 12 | /ci 13 | /src -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | sudo: required 8 | 9 | services: 10 | - docker 11 | 12 | before_install: 13 | - bash -e ci/travis_before_install.sh 14 | - if [ "$TRAVIS_OS_NAME" = "osx" ]; then export TARGET_NAME=darwin-x64; fi 15 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then export TARGET_NAME=linux-x64; fi 16 | 17 | before_script: 18 | - IS_GRAPHQL_PPX_CI=true yarn 19 | - ./ci/before_script_$TRAVIS_OS_NAME.sh 20 | 21 | script: 22 | - ./ci/build_script_$TRAVIS_OS_NAME.sh 23 | - make only-test TARGET_BUCKLESCRIPT=1 24 | - NODE_ENV=production make only-test TARGET_BUCKLESCRIPT=1 25 | - mv graphql_ppx.exe graphql_ppx-$TARGET_NAME.exe 26 | 27 | deploy: 28 | provider: releases 29 | skip_cleanup: true 30 | file_glob: true 31 | file: graphql_ppx-*.exe 32 | api_key: 33 | secure: "xFRAioN2zdN/iDNFNTzjTp+bRPBxpdokJRYCWo4sA4FExvSrf5pibHGjapmESTdWkSziNHVWDqQr8oWeN93sGEuPn/faKhhqtn8ikeqMpB9PVIc/1X9+ScIWD9meMG8hPEDJsD5W/dgsJs75M4P9bmVDKvQ4X96rSyl0r5RRRFqTHc4Irc1Io5XoLSftuA++DVFvDUAlrfOHguGRK1LtMcDX7ei8CTbiTrYshn6LabeqV12nVxVYCqKQROqQ/RQCFtRvWKe6z98Zp1roOafX4X4k9Q0w5BUx6x7hniOQoaQZpyZukrOlAqwJsURxiH+MrwJPv52steFz2CXPTSPY6LTHlmc39yhULmnkwMIFjJGQJQbgmb+7XWMfZZV5he7OarnqiCVJRIoTvOMbPfKEhswns4mf1NrEsSBoWJMGAot+q8/Gx5eMZaPRnr8SNYL5e3TJmu7g/J/0nJb7gZHZieljiZotwadS1AA2JUoqhPnkuZpVK9xMXOVbzOUuqhOF3P/fbT2FTpMjCtUPxpf0A7nBC92kb5uVKw/WKQVZUe3D1fjh/a8dzUPHyQ9a+7wnfOPId36GM0tFGZhvpuFgt/+lCh7d1MJFOvn5Y3nPIqUHJT6pWvLckBCY//lUkVF7d5ddXrEXNywA62ZOYojPK5qYQIGyWNeE9x8kpiVaYfs=" 34 | on: 35 | tags: true 36 | 37 | 38 | jobs: 39 | include: 40 | - stage: Wait for Windows binaries to become available 41 | addons: 42 | apt: 43 | sources: [] 44 | packages: [] 45 | before_install: skip 46 | before_script: skip 47 | deploy: [] 48 | script: bash -e ci/wait_for_builds.sh 49 | 50 | - stage: Deploy to npmjs 51 | addons: 52 | apt: 53 | sources: [] 54 | packages: [] 55 | before_install: skip 56 | before_script: skip 57 | script: 58 | - bash -e ci/download_binaries.sh 59 | deploy: 60 | provider: npm 61 | email: mhallin@fastmail.com 62 | api_key: 63 | secure: "QwfYKxbPceGZcz1gVtaydLvnhu1txRzxE7KM1eCWKxG1OWliOk6MOqSdkgTwUBqvQJY5b9m01eVfR23dFE1V3prFDg8tMiT5WaQcYRmZ+RYVlXo4vsuMzbRXaqMFudsPZL/U5Vu7wyJxJqI4JwW6FmByygpT8+FqcvAu1OKCQmPCLspBlxufkN2qOrvEEBsSYaBJjE9lUZDmHsaS/IiN3XpiiE8y1fBlvqtu1B5vtupTTRC72tsqrwDI1SFs+vjIZhuW8rFJzmgWlfG+BeNuryq1QUz1J4/Zsr9TczrpDMzv2nbmIL/gY7TIPZZbgHEDCWNmA7hBlrICXHpsYEzigIKqWqh5QxfafNbE4dc8FtCI9PdKxX+6BGt1BJfTRHjsI0JAaT+RMnB4p9JHw+8hvtIJqGCEpP6ettFS6mAjp6DoxtjHdIendYhHiMhSUt0HJBOlXxrut3L6i00+dVUqXcnHOYib1HxUx4qUPiSq1OTqh4nqfknuq/tUCGUZKjpjoq/7gnRtUL4MJPnHu6FVpE+0baShOyk9O2i9xAVOPsgbWfshvUjUAHjjhKC1nc/m3CDcjV5fEWzBcx4kvqHdIJRi3MLE89SSCpNppud4bdnp9NS/eF3SLGtqYIrWmC0IAC6mpcEnROAoeyFpuZ0fvYki0SS/xcHwsCbAvU2sM7c=" 64 | on: 65 | tags: true 66 | skip_cleanup: true 67 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | This document describes how to develop and test this ppx plugin. 4 | This is a different workflow than when consuming this ppx plugin for 5 | use in npm. It involves installing native opam packages as 6 | dependencies. 7 | 8 | 9 | ## Developing With `opam` 10 | 11 | OPAM is a package and environment manager for OCaml. To work on graphql_ppx 12 | using OPAM, you first need to make sure you're running on OCaml version 4.02.3: 13 | 14 | ```sh 15 | opam init 16 | opam switch 4.02.3 17 | eval `opam config env` 18 | ``` 19 | 20 | Then, you can install and build graphql_ppx: 21 | 22 | ```sh 23 | opam pin add graphql_ppx_base . -n 24 | opam install graphql_ppx_base --deps-only # Downloads and builds the OCaml dependencies 25 | make # Builds graphql_ppx itself 26 | ``` 27 | 28 | ### Test 29 | 30 | You first need to install the JavaScript dependencies to run the test suite: 31 | 32 | ```sh 33 | npm install # Or yarn install if you're using Yarn 34 | ``` 35 | 36 | Then, simply run 37 | 38 | ```sh 39 | make test 40 | ``` 41 | 42 | to run the tests. 43 | 44 | 45 | ## Developing With `esy` 46 | 47 | You can work on the GraphQL ppx plugin by installing native 48 | dependencies using `esy` (`esy` is like `npm` for native). 49 | 50 | ### Install Dependencies And Build 51 | 52 | ```sh 53 | npm install -g esy@preview 54 | esy install 55 | esy build 56 | ``` 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Magnus Hallin 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DUNE?=dune 2 | 3 | OS:=$(shell uname -s) 4 | TARGET_BUCKLESCRIPT?=$(shell echo `opam switch show` | grep -s 4.02.3) 5 | 6 | build: 7 | $(DUNE) build src/bucklescript/graphql_ppx.exe 8 | if [ "$(TARGET_BUCKLESCRIPT)" = "" ]; then $(DUNE) build src/native/graphql_ppx.a; fi 9 | cp _build/default/src/bucklescript/graphql_ppx.exe . 10 | 11 | buildall: 12 | $(DUNE) build --workspace=dune-workspace.dev-native src/native/graphql_ppx.a 13 | $(DUNE) build --workspace=dune-workspace.dev-bs src/bucklescript/graphql_ppx.exe 14 | ([ -x _build/4.02.3/src/bucklescript/graphql_ppx.exe ] \ 15 | && cp _build/4.02.3/src/bucklescript/graphql_ppx.exe .) 16 | 17 | test: build only-test 18 | 19 | only-test: graphql_schema.json 20 | if [ "$(TARGET_BUCKLESCRIPT)" != "" ]; then (cd tests_bucklescript && ../node_modules/.bin/bsb -clean-world && ../node_modules/.bin/bsb -make-world); fi 21 | if [ "$(TARGET_BUCKLESCRIPT)" != "" ]; then (cd tests_apollo && ../node_modules/.bin/bsb -clean-world && ../node_modules/.bin/bsb -make-world); fi 22 | if [ "$(TARGET_BUCKLESCRIPT)" != "" ]; then ./node_modules/.bin/jest --verbose tests_bucklescript/lib/js tests_apollo/lib/js; fi 23 | if [ "$(TARGET_BUCKLESCRIPT)" = "" ]; then dune runtest; fi 24 | 25 | graphql_schema.json: schema.gql 26 | node convertSchema $< > $@ 27 | 28 | clean: 29 | $(DUNE) clean 30 | rm -rf _build graphql_ppx.exe tests_bucklescript/lib tests_apollo/lib 31 | 32 | .PHONY: build test only-test clean buildall 33 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: 2 | - x64 3 | 4 | environment: 5 | matrix: 6 | - FORK_USER: ocaml 7 | FORK_BRANCH: master 8 | CYG_ROOT: C:\cygwin64 9 | OPAM_SWITCH: 4.02.3+mingw64c 10 | PACKAGE: graphql_ppx_base 11 | OPAMYES: 1 12 | INSTALL: false 13 | REVDEPS: false 14 | IS_GRAPHQL_PPX_CI: true 15 | TARGET_PLATFORM: win-x64 16 | 17 | - FORK_USER: ocaml 18 | FORK_BRANCH: master 19 | CYG_ROOT: C:\cygwin 20 | OPAM_SWITCH: 4.02.3+mingw32c 21 | PACKAGE: graphql_ppx_base 22 | OPAMYES: 1 23 | INSTALL: false 24 | REVDEPS: false 25 | IS_GRAPHQL_PPX_CI: true 26 | TARGET_PLATFORM: win-x86 27 | 28 | install: 29 | - ps: Install-Product node LTS 30 | - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/$env:FORK_USER/ocaml-ci-scripts/$env:FORK_BRANCH/appveyor-install.ps1")) 31 | - yarn 32 | 33 | build_script: 34 | - call %CYG_ROOT%\bin\bash.exe -l %APPVEYOR_BUILD_FOLDER%\appveyor-opam.sh 35 | - call %CYG_ROOT%\bin\bash.exe -l -c "cd /cygdrive/c/projects/graphql-ppx && make build" 36 | 37 | test_script: 38 | - node convertSchema schema.gql > graphql_schema.json 39 | - call %CYG_ROOT%\bin\bash.exe -l -c "cd /cygdrive/c/projects/graphql-ppx && make test" 40 | - call %CYG_ROOT%\bin\bash.exe -l -c "NODE_ENV=production cd /cygdrive/c/projects/graphql-ppx && make test" 41 | - move graphql_ppx.exe graphql_ppx-%TARGET_PLATFORM%.exe 42 | 43 | artifacts: 44 | - path: graphql_ppx-*.exe 45 | 46 | deploy: 47 | provider: GitHub 48 | auth_token: 49 | secure: cNoylUr8IbJuXONnyQFAUTNzU0GsZkq4mnAhvyzqPgbtesQSJM0rCJGDPhLjMHo+ 50 | artifact: /graphql_ppx-.*\.exe/ 51 | on: 52 | appveyor_repo_tag: true 53 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: bucklescript_linux 3 | 4 | pool: 5 | vmImage: 'Ubuntu 16.04' 6 | 7 | steps: 8 | - script: OCAML_VERSION=4.02.3 ./ci/azure_run.sh 9 | 10 | - job: bucklescript_mac 11 | 12 | pool: 13 | vmImage: xcode9-macos10.13 14 | 15 | steps: 16 | - script: OCAML_VERSION=4.02.3 ./ci/azure_run.sh 17 | 18 | - job: native_4_04_2_linux 19 | 20 | pool: 21 | vmImage: 'Ubuntu 16.04' 22 | 23 | steps: 24 | - script: OCAML_VERSION=ocaml-base-compiler.4.04.2 ./ci/azure_run.sh 25 | 26 | - job: native_4_04_2_mac 27 | 28 | pool: 29 | vmImage: xcode9-macos10.13 30 | 31 | steps: 32 | - script: OCAML_VERSION=ocaml-base-compiler.4.04.2 ./ci/azure_run.sh 33 | 34 | - job: native_4_05_0_linux 35 | 36 | pool: 37 | vmImage: 'Ubuntu 16.04' 38 | 39 | steps: 40 | - script: OCAML_VERSION=ocaml-base-compiler.4.05.0 ./ci/azure_run.sh 41 | 42 | - job: native_4_05_0_mac 43 | 44 | pool: 45 | vmImage: xcode9-macos10.13 46 | 47 | steps: 48 | - script: OCAML_VERSION=ocaml-base-compiler.4.05.0 ./ci/azure_run.sh 49 | 50 | - job: native_4_06_1_linux 51 | 52 | pool: 53 | vmImage: 'Ubuntu 16.04' 54 | 55 | steps: 56 | - script: OCAML_VERSION=ocaml-base-compiler.4.06.1 ./ci/azure_run.sh 57 | 58 | - job: native_4_06_1_mac 59 | 60 | pool: 61 | vmImage: xcode9-macos10.13 62 | 63 | steps: 64 | - script: OCAML_VERSION=ocaml-base-compiler.4.06.1 ./ci/azure_run.sh 65 | 66 | - job: native_4_07_1_linux 67 | 68 | pool: 69 | vmImage: 'Ubuntu 16.04' 70 | 71 | steps: 72 | - script: OCAML_VERSION=ocaml-base-compiler.4.07.1 ./ci/azure_run.sh 73 | 74 | - job: native_4_07_1_mac 75 | 76 | pool: 77 | vmImage: xcode9-macos10.13 78 | 79 | steps: 80 | - script: OCAML_VERSION=ocaml-base-compiler.4.07.1 ./ci/azure_run.sh -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql_ppx", 3 | "bsc-flags": [ "-bs-super-errors" ] 4 | } -------------------------------------------------------------------------------- /ci/azure_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | set +x 5 | 6 | echo "Agent OS: $AGENT_OS" 7 | echo "OCaml version: $OCAML_VERSION" 8 | 9 | export BUCKLESCRIPT_OCAML="4.02.3" 10 | 11 | if [ "$OCAML_VERSION" = "$BUCKLESCRIPT_OCAML" ]; then 12 | export TARGET_BUCKLESCRIPT=1 13 | else 14 | export TARGET_BUCKLESCRIPT= 15 | fi 16 | 17 | case "$AGENT_OS" in 18 | Darwin) 19 | export TARGET_NAME=darwin-x64 20 | ;; 21 | Linux) 22 | export TARGET_NAME=linux-x64 23 | ;; 24 | esac 25 | 26 | echo "graphql_ppx target name: $TARGET_NAME" 27 | echo "Targeting bucklescript: $TARGET_BUCKLESCRIPT" 28 | 29 | case "$AGENT_OS" in 30 | Darwin) 31 | brew update 32 | brew unlink python 33 | brew install aspcud awscli yarn opam ocaml || true 34 | 35 | OPAMYES=1 opam init 36 | OPAMYES=1 opam switch create $OCAML_VERSION 37 | OPAMYES=1 opam switch $OCAML_VERSION 38 | 39 | eval $(opam env) 40 | 41 | OPAMYES=1 opam update 42 | OPAMYES=1 opam pin add graphql_ppx_base . -n 43 | OPAMYES=1 opam install graphql_ppx_base --deps-only --with-test 44 | 45 | if [ "$TARGET_BUCKLESCRIPT" != "1" ]; then 46 | OPAMYES=1 opam pin add graphql_ppx . -n 47 | OPAMYES=1 opam install graphql_ppx --deps-only --with-test 48 | fi 49 | 50 | make 51 | ;; 52 | 53 | Linux) 54 | 55 | if [ "$TARGET_BUCKLESCRIPT" = "1" ]; then 56 | chmod -R a+w . 57 | docker run --rm -v `pwd`:/workspace ocaml/opam2:alpine sh -c "\ 58 | sudo apk add m4 && \ 59 | OPAMYES=1 opam switch create $OCAML_VERSION && 60 | OPAMYES=1 opam switch $OCAML_VERSION && 61 | eval \$(opam env) && 62 | OPAMYES=1 opam update && \ 63 | (cd /workspace && \ 64 | OPAMYES=1 opam pin add graphql_ppx_base . -n && \ 65 | OPAMYES=1 opam install graphql_ppx_base --deps-only && \ 66 | make \ 67 | )" 68 | else 69 | sudo apt-get update -y 70 | sudo apt-get install -y git 71 | echo | sh <(curl -sL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh) 72 | OPAMYES=1 opam init --disable-sandboxing --bare 73 | OPAMYES=1 opam switch create $OCAML_VERSION 74 | OPAMYES=1 opam switch $OCAML_VERSION 75 | eval $(opam env) 76 | OPAMYES=1 opam update 77 | OPAMYES=1 opam pin add graphql_ppx_base . -n 78 | OPAMYES=1 opam install graphql_ppx_base --deps-only --with-test 79 | OPAMYES=1 opam pin add graphql_ppx . -n 80 | OPAMYES=1 opam install graphql_ppx --deps-only --with-test 81 | fi 82 | ;; 83 | esac 84 | 85 | if [ "$TARGET_BUCKLESCRIPT" = "1" ]; then 86 | IS_GRAPHQL_PPX_CI=true yarn 87 | fi 88 | 89 | make only-test 90 | 91 | if [ "$TARGET_BUCKLESCRIPT" = "1" ]; then 92 | NODE_ENV=production make only-test 93 | mv graphql_ppx.exe graphql_ppx-$TARGET_NAME.exe 94 | fi 95 | 96 | -------------------------------------------------------------------------------- /ci/before_script_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker pull ocaml/opam:alpine_ocaml-4.02.3 4 | -------------------------------------------------------------------------------- /ci/before_script_osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | eval `opam config env` 4 | 5 | OPAMYES=1 opam pin add graphql_ppx_base . -n 6 | OPAMYES=1 opam install graphql_ppx_base --deps-only 7 | -------------------------------------------------------------------------------- /ci/build_script_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | chmod -R a+w . 4 | docker run --rm -v `pwd`:/workspace -it ocaml/opam2:alpine sh -c "\ 5 | sudo apk add m4 && \ 6 | OPAMYES=1 opam switch create 4.02.3 && 7 | eval \$(opam env) && 8 | OPAMYES=1 opam update && \ 9 | (cd /workspace && \ 10 | OPAMYES=1 opam pin add graphql_ppx_base . -n && \ 11 | OPAMYES=1 opam install graphql_ppx_base --deps-only && \ 12 | make \ 13 | )" 14 | -------------------------------------------------------------------------------- /ci/build_script_osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | eval `opam config env` 6 | 7 | make 8 | -------------------------------------------------------------------------------- /ci/download_binaries.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | if [ -z "$TRAVIS_TAG" ]; then 4 | echo "TRAVIS_TAG variable empty, will not download binaries" 5 | exit 0 6 | fi 7 | 8 | BASE_URL=https://github.com/mhallin/graphql_ppx/releases/download/$TRAVIS_TAG/graphql_ppx- 9 | 10 | mkdir -p bin 11 | 12 | for platform in linux-x64 darwin-x64 win-x64 win-x86; do 13 | (cd bin && curl -fOL $BASE_URL$platform.exe) 14 | done 15 | -------------------------------------------------------------------------------- /ci/travis_before_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | case "$TRAVIS_OS_NAME" in 6 | osx) 7 | brew update 8 | brew unlink python 9 | brew install aspcud awscli yarn opam ocaml 10 | curl -L https://github.com/aktau/github-release/releases/download/v0.7.2/darwin-amd64-github-release.tar.bz2 | tar xjf - 11 | mv bin/darwin/amd64/github-release . 12 | 13 | OPAMYES=1 opam init 14 | OPAMYES=1 opam switch create 4.02.3 15 | OPAMYES=1 opam switch 4.02.3 16 | 17 | eval $(opam env) 18 | ;; 19 | linux) 20 | source "$HOME/.nvm/nvm.sh" 21 | nvm install 9 22 | nvm use 9 23 | curl -L https://github.com/aktau/github-release/releases/download/v0.7.2/linux-amd64-github-release.tar.bz2 | tar xjf - 24 | mv bin/linux/amd64/github-release . 25 | ;; 26 | esac 27 | -------------------------------------------------------------------------------- /ci/wait_for_builds.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | if [ -z "$TRAVIS_TAG" ]; then 4 | echo "TRAVIS_TAG variable empty, will not wait for builds" 5 | exit 0 6 | fi 7 | 8 | CURL="curl -ifs" 9 | BASE_URL=https://github.com/mhallin/graphql_ppx/releases/download/$TRAVIS_TAG/graphql_ppx- 10 | START_TIME=$(date "+%s") 11 | 12 | ## Wait at most 30 minutes for all platform binaries to be ready 13 | 14 | for platform in linux-x64 darwin-x64 win-x64 win-x86; do 15 | echo Checking binary for $platform 16 | 17 | while ! $CURL $BASE_URL$platform.exe > /dev/null; do 18 | if [ $(( $(date "+%s") - $START_TIME )) -gt 1800 ]; then 19 | echo Timed out waiting for builds to finish 20 | exit 1 21 | fi 22 | 23 | echo Build task for $platform not yet finished, waiting... 24 | sleep 10 25 | done 26 | done 27 | 28 | echo 'All binaries found' 29 | -------------------------------------------------------------------------------- /convertSchema.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const graphqlTools = require('graphql-tools'); 3 | const graphql = require('graphql'); 4 | const fs = require('fs'); 5 | 6 | const argv = require("yargs") 7 | .usage('Usage: $0 ') 8 | .command("schema", "Path to the schema file", { alias: "schema" }) 9 | .required(1, "The schema file is required") 10 | .help("?") 11 | .alias("?", "help").argv; 12 | 13 | const introspectionQuery = ` 14 | query IntrospectionQuery { 15 | __schema { 16 | queryType { name } 17 | mutationType { name } 18 | subscriptionType { name } 19 | types { 20 | ...FullType 21 | } 22 | directives { 23 | name 24 | description 25 | locations 26 | args { 27 | ...InputValue 28 | } 29 | } 30 | } 31 | } 32 | fragment FullType on __Type { 33 | kind 34 | name 35 | description 36 | fields(includeDeprecated: true) { 37 | name 38 | description 39 | args { 40 | ...InputValue 41 | } 42 | type { 43 | ...TypeRef 44 | } 45 | isDeprecated 46 | deprecationReason 47 | } 48 | inputFields { 49 | ...InputValue 50 | } 51 | interfaces { 52 | ...TypeRef 53 | } 54 | enumValues(includeDeprecated: true) { 55 | name 56 | description 57 | isDeprecated 58 | deprecationReason 59 | } 60 | possibleTypes { 61 | ...TypeRef 62 | } 63 | } 64 | fragment InputValue on __InputValue { 65 | name 66 | description 67 | type { ...TypeRef } 68 | defaultValue 69 | } 70 | fragment TypeRef on __Type { 71 | kind 72 | name 73 | ofType { 74 | kind 75 | name 76 | ofType { 77 | kind 78 | name 79 | ofType { 80 | kind 81 | name 82 | ofType { 83 | kind 84 | name 85 | ofType { 86 | kind 87 | name 88 | ofType { 89 | kind 90 | name 91 | ofType { 92 | kind 93 | name 94 | } 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | }`; 102 | 103 | const typeDefs = fs.readFileSync(argv._[0], {encoding: 'utf-8'}); 104 | const schema = graphqlTools.makeExecutableSchema({ typeDefs, resolverValidationOptions: { requireResolversForResolveType: false } }); 105 | graphqlTools.addMockFunctionsToSchema({ schema }); 106 | 107 | graphql.graphql(schema, introspectionQuery).then(result => 108 | process.stdout.write(JSON.stringify(result, null, ' '))); 109 | -------------------------------------------------------------------------------- /copyPlatformBinaryInPlace.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | 5 | var arch = process.arch; 6 | var platform = process.platform; 7 | 8 | if (arch === 'ia32') { 9 | arch = 'x86'; 10 | } 11 | 12 | if (platform === 'win32') { 13 | platform = 'win'; 14 | } 15 | 16 | var filename = 'bin/graphql_ppx-' + platform + '-' + arch + '.exe'; 17 | var supported = fs.existsSync(filename); 18 | 19 | if (!supported) { 20 | console.error('graphql_ppx does not support this platform :('); 21 | console.error(''); 22 | console.error('graphql_ppx comes prepacked as built binaries to avoid large'); 23 | console.error('dependencies at build-time.'); 24 | console.error(''); 25 | console.error('If you want graphql_ppx to support this platform natively,'); 26 | console.error('please open an issue at our repository, linked above. Please'); 27 | console.error('specify that you are on the ' + platform + ' platform,'); 28 | console.error('on the ' + arch + ' architecture.'); 29 | 30 | if (!process.env.IS_GRAPHQL_PPX_CI) { 31 | process.exit(1); 32 | } 33 | } 34 | 35 | if (process.env.IS_GRAPHQL_PPX_CI) { 36 | console.log('graphql_ppx: IS_GRAPHQL_PPX_CI has been set, skipping moving binary in place'); 37 | process.exit(0); 38 | } 39 | 40 | if (!fs.existsSync('ppx')) { 41 | copyFileSync(filename, 'ppx'); 42 | fs.chmodSync('ppx', 0755); 43 | } 44 | 45 | if (!fs.existsSync('ppx.exe')) { 46 | copyFileSync(filename, 'ppx.exe'); 47 | fs.chmodSync('ppx.exe', 0755); 48 | } 49 | 50 | function copyFileSync(source, dest) { 51 | if (typeof fs.copyFileSync === 'function') { 52 | fs.copyFileSync(source, dest); 53 | } else { 54 | fs.writeFileSync(dest, fs.readFileSync(source)); 55 | } 56 | } -------------------------------------------------------------------------------- /discover/discover.ml: -------------------------------------------------------------------------------- 1 | module C = Configurator.V1 2 | 3 | let () = 4 | C.main ~name:"graphql_ppx" (fun c -> 5 | let system = C.ocaml_config_var_exn c "system" in 6 | let flags = if system = "linux" then ["-ccopt"; "-static"] else [] in 7 | C.Flags.write_sexp "dune.flags" flags) 8 | -------------------------------------------------------------------------------- /discover/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name discover) 3 | (libraries dune.configurator)) 4 | 5 | (rule 6 | (targets dune.flags) 7 | (action (run ./discover.exe))) 8 | 9 | -------------------------------------------------------------------------------- /doc/missing_variables.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhallin/graphql_ppx/5796b3759bdf0d29112f48e43a2f0623f7466e8a/doc/missing_variables.gif -------------------------------------------------------------------------------- /doc/misspelled_field.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhallin/graphql_ppx/5796b3759bdf0d29112f48e43a2f0623f7466e8a/doc/misspelled_field.gif -------------------------------------------------------------------------------- /doc/removed_field.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhallin/graphql_ppx/5796b3759bdf0d29112f48e43a2f0623f7466e8a/doc/removed_field.gif -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (alias 2 | (name graphql_ppx) 3 | (deps src/bucklescript/graphql_ppx.exe 4 | src/native/graphql_ppx.a)) 5 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | 3 | (name "graphql_ppx") -------------------------------------------------------------------------------- /dune-workspace.dev-bs: -------------------------------------------------------------------------------- 1 | (lang dune 1.4) 2 | 3 | (context (opam (switch 4.02.3))) 4 | (context (opam (switch 4.04.2))) 5 | (context (opam (switch 4.05.0))) 6 | (context (opam (switch 4.06.1))) 7 | (context (opam (switch 4.07.1))) 8 | -------------------------------------------------------------------------------- /dune-workspace.dev-native: -------------------------------------------------------------------------------- 1 | (lang dune 1.4) 2 | 3 | (context (opam (switch 4.04.2))) 4 | (context (opam (switch 4.05.0))) 5 | (context (opam (switch 4.06.1))) 6 | (context (opam (switch 4.07.1))) 7 | -------------------------------------------------------------------------------- /esy.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql_ppx", 3 | "version": "0.0.4", 4 | "description": "GraphQL syntax extension for Bucklescript/ReasonML", 5 | "license": "BSD 3-clause", 6 | "esy": { 7 | "build": [ 8 | [ "dune", "build", "@graphql_ppx" ] 9 | ], 10 | "install": [ 11 | "esy-installer" 12 | ], 13 | "buildsInSource": "_build" 14 | }, 15 | "dependencies": { 16 | "@opam/ocaml-migrate-parsetree": "*", 17 | "@opam/result": "*", 18 | "@opam/yojson": "*", 19 | "@opam/ppx_tools_versioned": "*", 20 | "@opam/dune": "*" 21 | }, 22 | "scripts": { 23 | "test": ["make", "test"] 24 | }, 25 | "peerDependencies": { 26 | "ocaml": " >= 4.2.1 < 4.7.0" 27 | }, 28 | "devDependencies": { 29 | "ocaml": "~4.2.3" 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /graphql_ppx.opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | name: "graphql_ppx" 3 | version: "0.0.4" 4 | maintainer: "Magnus Hallin " 5 | authors: "Magnus Hallin " 6 | homepage: "https://github.com/mhallin/graphql_ppx" 7 | bug-reports: "https://github.com/mhallin/graphql_ppx/issues" 8 | description: "PPX to decode GraphQL responses." 9 | license: "BSD 3-clause" 10 | dev-repo: "git@github.com:mhallin/graphql_ppx.git" 11 | tags: ["bucklescript" "reasonml" "ppx" "graphql"] 12 | build: [ 13 | [make "build"] 14 | ] 15 | available: [ocaml-version >= "4.04.1"] 16 | depends: [ 17 | "ocaml-migrate-parsetree" {build} 18 | "result" {build} 19 | "yojson" {build} 20 | "ppx_tools_versioned" {build} 21 | "dune" {build} 22 | "cppo" {build} 23 | "ppxlib" {build} 24 | "alcotest" {test} 25 | ] 26 | -------------------------------------------------------------------------------- /graphql_ppx_base.opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | name: "graphql_ppx_base" 3 | version: "0.0.4" 4 | maintainer: "Magnus Hallin " 5 | authors: "Magnus Hallin " 6 | homepage: "https://github.com/mhallin/graphql_ppx" 7 | bug-reports: "https://github.com/mhallin/graphql_ppx/issues" 8 | description: "Shared internal library for graphql_ppx." 9 | license: "BSD 3-clause" 10 | dev-repo: "git@github.com:mhallin/graphql_ppx.git" 11 | tags: ["bucklescript" "reasonml" "ppx" "graphql"] 12 | build: [ 13 | [make "build"] 14 | ] 15 | available: [ocaml-version >= "4.02.3"] 16 | depends: [ 17 | "ocaml-migrate-parsetree" {build} 18 | "result" {build} 19 | "yojson" {build} 20 | "ppx_tools_versioned" {build} 21 | "dune" {build} 22 | "cppo" {build} 23 | ] 24 | -------------------------------------------------------------------------------- /jbuild-ignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql_ppx", 3 | "version": "0.2.9", 4 | "description": "GraphQL PPX rewriter for Bucklescript/ReasonML", 5 | "main": "index.js", 6 | "repository": "https://github.com/mhallin/graphql_ppx", 7 | "author": "Magnus Hallin ", 8 | "license": "BSD-3-Clause", 9 | "bin": { 10 | "send-introspection-query": "./sendIntrospectionQuery.js" 11 | }, 12 | "scripts": { 13 | "postinstall": "node copyPlatformBinaryInPlace.js" 14 | }, 15 | "devDependencies": { 16 | "@glennsl/bs-jest": "^0.4.5", 17 | "bs-platform": "^4.0.3", 18 | "graphql": "^0.13.2", 19 | "graphql-tag": "^2.6.1", 20 | "graphql-tools": "^4.0.3" 21 | }, 22 | "dependencies": { 23 | "request": "^2.82.0", 24 | "yargs": "^11.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /refresh-switches.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | SWITCHES="4.02.3 4.04.2 4.05.0 4.06.1 4.07.1" 6 | ORIGINAL_SWITCH=$(opam switch show) 7 | 8 | for switch in $SWITCHES; do 9 | echo "## REFRESHING $switch ##" 10 | opam switch $switch 11 | eval $(opam env) 12 | opam pin add graphql_ppx_base . -n --kind=path -y 13 | OPAMYES=1 opam install graphql_ppx_base --deps-only 14 | if [ "$switch" != "4.02.3" ]; then 15 | opam pin add graphql_ppx . -n --kind=path -y 16 | OPAMYES=1 opam install graphql_ppx --deps-only 17 | fi 18 | done 19 | 20 | opam switch $ORIGINAL_SWITCH 21 | eval $(opam env) 22 | -------------------------------------------------------------------------------- /schema.gql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | mutation: Mutation 4 | subscription: Subscription 5 | } 6 | 7 | interface User { 8 | id: ID! 9 | } 10 | 11 | interface Name { 12 | name: String! 13 | } 14 | 15 | interface Anonymous { 16 | anonymousId: Int! 17 | } 18 | 19 | type AdminUser implements User & Name { 20 | id: ID! 21 | name: String! 22 | } 23 | 24 | type AnonymousUser implements User & Anonymous { 25 | id: ID! 26 | anonymousId: Int! 27 | } 28 | 29 | type OtherUser implements User { 30 | id: ID! 31 | } 32 | 33 | type Query { 34 | stringField: String! 35 | variousScalars: VariousScalars! 36 | lists: Lists! 37 | users: [User!]! 38 | 39 | scalarsInput(arg: VariousScalarsInput!): String! 40 | listsInput(arg: ListsInput!): String! 41 | recursiveInput(arg: RecursiveInput!): String! 42 | nonrecursiveInput(arg: NonrecursiveInput!): String! 43 | enumInput(arg: SampleField!): String! 44 | argNamedQuery(query: Int!): Int! 45 | customScalarField( 46 | argOptional: CustomScalar 47 | argRequired: CustomScalar! 48 | ): CustomScalarObject! 49 | 50 | dogOrHuman: DogOrHuman! 51 | 52 | nestedObject: NestedObject! 53 | } 54 | 55 | type Mutation { 56 | mutationWithError: MutationWithErrorResult! 57 | } 58 | 59 | type Subscription { 60 | simpleSubscription: DogOrHuman! 61 | } 62 | 63 | type MutationWithErrorResult { 64 | value: SampleResult 65 | errors: [SampleError!] 66 | } 67 | 68 | type SampleResult { 69 | stringField: String! 70 | } 71 | 72 | type SampleError { 73 | field: SampleField! 74 | message: String! 75 | } 76 | 77 | enum SampleField { 78 | FIRST 79 | SECOND 80 | THIRD 81 | } 82 | 83 | type VariousScalars { 84 | nullableString: String 85 | string: String! 86 | nullableInt: Int 87 | int: Int! 88 | nullableFloat: Float 89 | float: Float! 90 | nullableBoolean: Boolean 91 | boolean: Boolean! 92 | nullableID: ID 93 | id: ID! 94 | } 95 | 96 | type Lists { 97 | nullableOfNullable: [String] 98 | nullableOfNonNullable: [String!] 99 | nonNullableOfNullable: [String]! 100 | nonNullableOfNonNullable: [String!]! 101 | } 102 | 103 | input VariousScalarsInput { 104 | nullableString: String 105 | string: String! 106 | nullableInt: Int 107 | int: Int! 108 | nullableFloat: Float 109 | float: Float! 110 | nullableBoolean: Boolean 111 | boolean: Boolean! 112 | nullableID: ID 113 | id: ID! 114 | } 115 | 116 | input ListsInput { 117 | nullableOfNullable: [String] 118 | nullableOfNonNullable: [String!] 119 | nonNullableOfNullable: [String]! 120 | nonNullableOfNonNullable: [String!]! 121 | } 122 | 123 | input NonrecursiveInput { 124 | field: String 125 | enum: SampleField 126 | } 127 | 128 | input RecursiveInput { 129 | otherField: String 130 | inner: RecursiveInput 131 | enum: SampleField 132 | } 133 | 134 | type Dog { 135 | name: String! 136 | barkVolume: Float! 137 | } 138 | 139 | type Human { 140 | name: String! 141 | } 142 | 143 | union DogOrHuman = Dog | Human 144 | 145 | type NestedObject { 146 | inner: NestedObject 147 | 148 | field: String! 149 | } 150 | 151 | type WithArgField { 152 | argField(arg1: String, arg2: Int): NestedObject 153 | } 154 | 155 | scalar CustomScalar 156 | 157 | type CustomScalarObject { 158 | nullable: CustomScalar 159 | nonNullable: CustomScalar! 160 | } 161 | -------------------------------------------------------------------------------- /sendIntrospectionQuery.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var argv = require("yargs") 3 | .usage('Usage: $0 [--headers "key:value"]') 4 | .command("url", "URL of the GraphQL endpoint", { alias: "url" }) 5 | .required(1, "URL is required") 6 | .option("H", { 7 | alias: "headers", 8 | describe: "Additional Headers to send with introspection query", 9 | type: "array", 10 | coerce: arg => { 11 | let additionalHeaders = {}; 12 | for (const header of arg) { 13 | const separator = header.indexOf(":"); 14 | const name = header.substring(0, separator).trim(); 15 | const value = header.substring(separator + 1).trim(); 16 | if (!(name && value)) { 17 | throw new Error('Headers should be specified as "Name: Value"'); 18 | } 19 | additionalHeaders[name] = value; 20 | } 21 | return additionalHeaders; 22 | } 23 | }) 24 | .help("?") 25 | .alias("?", "help") 26 | .example("$0 https://example.com/graphql", "Get GraphQL Schema") 27 | .example(`$0 https://example.com/graphql --headers "Authorisation: "`, "Get GraphQL Schema with Authorisation header").argv; 28 | 29 | var request = require("request"); 30 | var fs = require("fs"); 31 | var introspectionQuery = ` 32 | query IntrospectionQuery { 33 | __schema { 34 | queryType { name } 35 | mutationType { name } 36 | subscriptionType { name } 37 | types { 38 | ...FullType 39 | } 40 | directives { 41 | name 42 | description 43 | locations 44 | args { 45 | ...InputValue 46 | } 47 | } 48 | } 49 | } 50 | fragment FullType on __Type { 51 | kind 52 | name 53 | description 54 | fields(includeDeprecated: true) { 55 | name 56 | description 57 | args { 58 | ...InputValue 59 | } 60 | type { 61 | ...TypeRef 62 | } 63 | isDeprecated 64 | deprecationReason 65 | } 66 | inputFields { 67 | ...InputValue 68 | } 69 | interfaces { 70 | ...TypeRef 71 | } 72 | enumValues(includeDeprecated: true) { 73 | name 74 | description 75 | isDeprecated 76 | deprecationReason 77 | } 78 | possibleTypes { 79 | ...TypeRef 80 | } 81 | } 82 | fragment InputValue on __InputValue { 83 | name 84 | description 85 | type { ...TypeRef } 86 | defaultValue 87 | } 88 | fragment TypeRef on __Type { 89 | kind 90 | name 91 | ofType { 92 | kind 93 | name 94 | ofType { 95 | kind 96 | name 97 | ofType { 98 | kind 99 | name 100 | ofType { 101 | kind 102 | name 103 | ofType { 104 | kind 105 | name 106 | ofType { 107 | kind 108 | name 109 | ofType { 110 | kind 111 | name 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | }`; 120 | 121 | const requestOptions = { 122 | json: true, 123 | body: { query: introspectionQuery }, 124 | headers: { "user-agent": "node.js", ...argv.headers } 125 | }; 126 | 127 | request.post(argv._[0], requestOptions, function(error, response, body) { 128 | if (error) { 129 | console.error("Could not send introspection query: ", error); 130 | process.exit(1); 131 | } 132 | 133 | if (response.statusCode !== 200) { 134 | console.error("Non-ok status code from API: ", response.statusCode, response.statusMessage); 135 | process.exit(1); 136 | } 137 | 138 | var result = JSON.stringify(body, null, 2); 139 | 140 | fs.writeFileSync("graphql_schema.json", result, { encoding: "utf-8" }); 141 | }); 142 | -------------------------------------------------------------------------------- /src/base/ast_serializer_apollo.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ast 2 | open Source_pos 3 | 4 | let ser_optional serializer = function 5 | | None -> `Null 6 | | Some item -> serializer item 7 | 8 | let ser_list_to_array serializer items = 9 | `List (List.map serializer items) 10 | 11 | let ser_optional_list serializer = function 12 | | None | Some { item = []; _ } -> `List [] 13 | | Some { item; _ } -> ser_list_to_array serializer item 14 | 15 | let ser_name { item = name; _ } = 16 | `Assoc [ 17 | "kind", `String "Name"; 18 | "value", `String name; 19 | ] 20 | 21 | let rec ser_type = function 22 | | { item = Tr_named name; _ } -> `Assoc [ 23 | "kind", `String "NamedType"; 24 | "name", ser_name name; 25 | ] 26 | | { item = Tr_list inner; _ } -> `Assoc [ 27 | "kind", `String "ListType"; 28 | "type", ser_type inner; 29 | ] 30 | | { item = Tr_non_null_named name; _ } -> `Assoc [ 31 | "kind", `String "NonNullType"; 32 | "type", `Assoc [ 33 | "kind", `String "NamedType"; 34 | "name", ser_name name; 35 | ] 36 | ] 37 | | { item = Tr_non_null_list inner; _ } -> `Assoc [ 38 | "kind", `String "NonNullType"; 39 | "type", `Assoc [ 40 | "kind", `String "ListType"; 41 | "type", ser_type inner; 42 | ]; 43 | ] 44 | 45 | 46 | let rec ser_value = function 47 | | { item = Iv_null; _ } -> `Assoc [ "kind", `String "NullValue" ] 48 | | { item = Iv_int i; _ } -> `Assoc [ 49 | "kind", `String "IntValue"; 50 | "value", `String (string_of_int i); 51 | ] 52 | | { item = Iv_float f; _ } -> `Assoc [ 53 | "kind", `String "FloatValue"; 54 | "value", `String (string_of_float f); 55 | ] 56 | | { item = Iv_string s; _ } -> `Assoc [ 57 | "kind", `String "StringValue"; 58 | "value", `String s; 59 | ] 60 | | { item = Iv_boolean b; _ } -> `Assoc [ 61 | "kind", `String "BooleanValue"; 62 | "value", `Bool b; 63 | ] 64 | | { item = Iv_enum e; _ } -> `Assoc [ 65 | "kind", `String "EnumValue"; 66 | "value", `String e; 67 | ] 68 | | { item = Iv_variable v; span; _ } -> `Assoc [ 69 | "kind", `String "Variable"; 70 | "name", ser_name { item = v; span }; 71 | ] 72 | | { item = Iv_list l; _ } -> `Assoc [ 73 | "kind", `String "ListValue"; 74 | "values", ser_list_to_array ser_value l; 75 | ] 76 | | { item = Iv_object o; _ } -> `Assoc [ 77 | "kind", `String "ObjectValue"; 78 | "fields", ser_list_to_array ser_object_field o; 79 | ] 80 | 81 | and ser_object_field (k, v) = `Assoc [ 82 | "kind", `String "ObjectField"; 83 | "name", ser_name k; 84 | "value", ser_value v; 85 | ] 86 | 87 | let ser_argument (name, value) = `Assoc [ 88 | "kind", `String "Argument"; 89 | "name", ser_name name; 90 | "value", ser_value value; 91 | ] 92 | 93 | let ser_arguments args = ser_optional_list ser_argument args 94 | 95 | 96 | let ser_variable_definition (name, def) = 97 | `Assoc [ 98 | "kind", `String "VariableDefinition"; 99 | "variable", `Assoc [ 100 | "kind", `String "Variable"; 101 | "name", ser_name name; 102 | ]; 103 | "type", ser_type def.vd_type; 104 | "defaultValue", ser_optional ser_value def.vd_default_value; 105 | ] 106 | 107 | let ser_variable_definitions defs = ser_optional_list ser_variable_definition defs 108 | 109 | let ser_directive {item; _} = 110 | `Assoc [ 111 | "kind", `String "Directive"; 112 | "name", ser_name item.d_name; 113 | "arguments", ser_arguments item.d_arguments; 114 | ] 115 | 116 | let ser_directives = ser_list_to_array ser_directive 117 | 118 | let ser_type_condition tc = ser_type { item = Tr_named tc; span = tc.span } 119 | 120 | let rec ser_selection_set sset = `Assoc [ 121 | "kind", `String "SelectionSet"; 122 | "selections", ser_list_to_array ser_selection sset.item; 123 | ] 124 | 125 | and ser_selection = function 126 | | Field { item; _ } -> `Assoc [ 127 | "kind", `String "Field"; 128 | "alias", ser_optional ser_name item.fd_alias; 129 | "name", ser_name item.fd_name; 130 | "arguments", ser_arguments item.fd_arguments; 131 | "directives", ser_directives item.fd_directives; 132 | "selectionSet", ser_optional ser_selection_set item.fd_selection_set; 133 | ] 134 | | FragmentSpread { item; _ } -> `Assoc [ 135 | "kind", `String "FragmentSpread"; 136 | "name", ser_name item.fs_name; 137 | "directives", ser_directives item.fs_directives; 138 | ] 139 | | InlineFragment { item; _ } -> `Assoc [ 140 | "kind", `String "InlineFragment"; 141 | "typeCondition", ser_optional ser_type_condition item.if_type_condition; 142 | "directives", ser_directives item.if_directives; 143 | "selectionSet", ser_selection_set item.if_selection_set; 144 | ] 145 | 146 | let ser_definition = function 147 | | Operation { item; _ } -> `Assoc [ 148 | "kind", `String "OperationDefinition"; 149 | "name", ser_optional ser_name item.o_name; 150 | "operation", (match item.o_type with 151 | | Query -> `String "query" 152 | | Mutation -> `String "mutation" 153 | | Subscription -> `String "subscription"); 154 | "variableDefinitions", ser_variable_definitions item.o_variable_definitions; 155 | "directives", ser_directives item.o_directives; 156 | "selectionSet", ser_selection_set item.o_selection_set; 157 | ] 158 | | Fragment { item; _ } -> `Assoc [ 159 | "kind", `String "FragmentDefintion"; 160 | "name", ser_name item.fg_name; 161 | "typeCondition", ser_type_condition item.fg_type_condition; 162 | "directives", ser_directives item.fg_directives; 163 | "selectionSet", ser_selection_set item.fg_selection_set; 164 | ] 165 | 166 | let def_end = function 167 | | Operation { span = (_, e); _ } | Fragment { span = (_, e); _ } -> e.index 168 | 169 | let document_end = List.fold_left (fun end_ def -> max end_ (def_end def)) 0 170 | 171 | let serialize_document source defs = 172 | `Assoc [ 173 | "kind", `String "Document"; 174 | "definitions", ser_list_to_array ser_definition defs; 175 | "loc", `Assoc [ 176 | "start", `Int 0; 177 | "end", `Int (defs |> document_end); 178 | "source", `StringExpr source; 179 | "locationOffset", `Assoc [ 180 | "column", `Int 1; 181 | "line", `Int 1; 182 | ]; 183 | "name", `String "GraphQL request"; 184 | ]; 185 | ] 186 | -------------------------------------------------------------------------------- /src/base/build_config.cppo.ml: -------------------------------------------------------------------------------- 1 | #if OCAML_VERSION >= (4, 3, 0) 2 | 3 | let capitalize_ascii = String.capitalize_ascii 4 | let uncapitalize_ascii = String.uncapitalize_ascii 5 | let is_bucklescript = false 6 | 7 | #else 8 | 9 | let capitalize_ascii = String.capitalize 10 | let uncapitalize_ascii = String.uncapitalize 11 | let is_bucklescript = true 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/base/compat.ml: -------------------------------------------------------------------------------- 1 | let capitalize_ascii = Build_config.capitalize_ascii 2 | let uncapitalize_ascii = Build_config.uncapitalize_ascii 3 | 4 | -------------------------------------------------------------------------------- /src/base/dirty_checker.ml: -------------------------------------------------------------------------------- 1 | let log_head = "[dirty_checker]" 2 | 3 | exception File_not_found 4 | 5 | let hash file = 6 | let () = Log.log ("[calc hash of] "^file) in 7 | Digest.file file 8 | 9 | let read_hash src = 10 | let () = Log.log ("[read hash from] "^src) in 11 | let hash_file = open_in_bin src in 12 | let hash = Digest.input hash_file in 13 | close_in hash_file; 14 | hash 15 | 16 | let write_hash hash dest = 17 | let () = Log.log ("[write hash to] "^dest) in 18 | let dest = open_out_bin dest in 19 | Digest.output dest hash; 20 | close_out dest 21 | 22 | let find_file file = 23 | if Sys.file_exists file then 24 | let () = Log.log ("[found] "^file) in 25 | Some file 26 | else 27 | let () = Log.log ("[not found] "^file) in 28 | None 29 | 30 | type checker = { 31 | src: string; 32 | hash_path: string; 33 | dirty_callback: string -> unit; 34 | } 35 | 36 | exception Dirty_update_failure of string 37 | 38 | let dirty_update hash checker = 39 | Log.log "[resource dirty!]"; 40 | match checker.dirty_callback checker.src with 41 | | () -> 42 | write_hash hash checker.hash_path; 43 | Log.must_log "[update ok]" 44 | | exception Dirty_update_failure msg -> 45 | Log.must_log ("[update error] "^msg) 46 | 47 | let check checker = 48 | match find_file checker.src with 49 | | None -> raise File_not_found 50 | | Some file -> 51 | let new_hash = hash file in 52 | match find_file (checker.hash_path) with 53 | | None -> dirty_update new_hash checker (* dirty *) 54 | | Some hash_file -> 55 | let old_hash = read_hash hash_file in 56 | match Digest.compare old_hash new_hash with 57 | | 0 -> Log.log "[resource unchanged]" 58 | | _ -> dirty_update new_hash checker (* dirty *) 59 | -------------------------------------------------------------------------------- /src/base/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name graphql_ppx_base) 3 | (public_name graphql_ppx_base) 4 | (libraries unix 5 | result 6 | yojson) 7 | (preprocess (pps ppx_tools_versioned.metaquot_402))) 8 | 9 | (rule 10 | (targets build_config.ml) 11 | (deps (:first-dep build_config.cppo.ml)) 12 | (action 13 | (chdir ${ROOT} 14 | (run %{bin:cppo} -V OCAML:%{ocaml_version} %{first-dep} -o %{targets})))) 15 | -------------------------------------------------------------------------------- /src/base/generator_utils.ml: -------------------------------------------------------------------------------- 1 | type error_marker = { 2 | mutable has_error: bool 3 | } 4 | 5 | let raise_error_with_loc = Ppx_config.raise_error_with_loc 6 | 7 | let raise_error map_loc span message = 8 | raise_error_with_loc (map_loc span) message 9 | 10 | let some_or o d = match o with 11 | | Some v -> v 12 | | None -> d 13 | 14 | let capitalize_ascii = Compat.capitalize_ascii 15 | let uncapitalize_ascii = Compat.uncapitalize_ascii 16 | 17 | type output_config = { 18 | map_loc: Source_pos.source_position * Source_pos.source_position -> Source_pos.ast_location; 19 | delimiter: string option; 20 | schema: Schema.schema; 21 | full_document: Graphql_ast.document; 22 | } 23 | 24 | let filter_map f l = 25 | let rec loop acc = function 26 | | [] -> List.rev acc 27 | | head :: tail -> match f head with 28 | | None -> loop acc tail 29 | | Some v -> loop (v :: acc) tail 30 | in loop [] l 31 | -------------------------------------------------------------------------------- /src/base/graphql_ast.ml: -------------------------------------------------------------------------------- 1 | open Source_pos 2 | 3 | type type_ref = 4 | | Tr_named of string spanning 5 | | Tr_list of type_ref spanning 6 | | Tr_non_null_named of string spanning 7 | | Tr_non_null_list of type_ref spanning 8 | 9 | type input_value = 10 | | Iv_null 11 | | Iv_int of int 12 | | Iv_float of float 13 | | Iv_string of string 14 | | Iv_boolean of bool 15 | | Iv_enum of string 16 | | Iv_variable of string 17 | | Iv_list of input_value spanning list 18 | | Iv_object of (string spanning * input_value spanning) list 19 | 20 | type variable_definition = { 21 | vd_type: type_ref spanning; 22 | vd_default_value: input_value spanning option 23 | } 24 | 25 | type variable_definitions = (string spanning * variable_definition) list 26 | 27 | type arguments = (string spanning * input_value spanning) list 28 | 29 | type directive = { 30 | d_name: string spanning; 31 | d_arguments: arguments spanning option; 32 | } 33 | 34 | type fragment_spread = { 35 | fs_name: string spanning; 36 | fs_directives: directive spanning list 37 | } 38 | 39 | type field = { 40 | fd_alias: string spanning option; 41 | fd_name: string spanning; 42 | fd_arguments: arguments spanning option; 43 | fd_directives: directive spanning list; 44 | fd_selection_set: selection list spanning option; 45 | } 46 | 47 | and inline_fragment = { 48 | if_type_condition: string spanning option; 49 | if_directives: directive spanning list; 50 | if_selection_set: selection list spanning; 51 | } 52 | 53 | and selection = 54 | | Field of field spanning 55 | | FragmentSpread of fragment_spread spanning 56 | | InlineFragment of inline_fragment spanning 57 | 58 | type operation_type = Query | Mutation | Subscription 59 | 60 | type operation = { 61 | o_type: operation_type; 62 | o_name: string spanning option; 63 | o_variable_definitions: variable_definitions spanning option; 64 | o_directives: directive spanning list; 65 | o_selection_set: selection list spanning; 66 | } 67 | 68 | type fragment = { 69 | fg_name: string spanning; 70 | fg_type_condition: string spanning; 71 | fg_directives: directive spanning list; 72 | fg_selection_set: selection list spanning; 73 | } 74 | 75 | type definition = 76 | | Operation of operation spanning 77 | | Fragment of fragment spanning 78 | 79 | type document = definition list 80 | 81 | let rec innermost_name = function 82 | | Tr_named { item; _ } 83 | | Tr_non_null_named { item; _ } -> item 84 | | Tr_list { item; _ } 85 | | Tr_non_null_list { item; _ } -> innermost_name item 86 | -------------------------------------------------------------------------------- /src/base/graphql_parser.ml: -------------------------------------------------------------------------------- 1 | open Result 2 | open Source_pos 3 | 4 | type parser = { 5 | mutable tokens: Graphql_lexer.token spanning list; 6 | } 7 | 8 | type parseError = 9 | | Unexpected_token of Graphql_lexer.token 10 | | Unexpected_end_of_file 11 | | Lexer_error of Graphql_lexer.lexerError 12 | 13 | let make tokens = { tokens = tokens } 14 | 15 | let peek parser = List.hd parser.tokens 16 | 17 | exception Internal_parse_error 18 | 19 | let next parser = match parser.tokens with 20 | | [x] -> Error (replace x Unexpected_end_of_file) 21 | | x :: _ -> let () = parser.tokens <- List.tl parser.tokens in Ok x 22 | | _ -> raise Internal_parse_error 23 | 24 | let expect parser token = 25 | match next parser with 26 | | Ok span when span.item = token -> Ok span 27 | | Ok span -> Error (replace span (Unexpected_token span.item)) 28 | | x -> x 29 | 30 | let expect_name parser = 31 | match next parser with 32 | | Ok ({ item = Graphql_lexer.Name name; _ } as span) -> Ok (replace span name) 33 | | Ok ({ item = Graphql_lexer.End_of_file; _ } as span) -> Error (replace span Unexpected_end_of_file) 34 | | Ok span -> Error (map (fun t -> Unexpected_token t) span) 35 | | Error e -> Error e 36 | 37 | let expect_dotted_name parser = 38 | let rec loop start_pos _ acc = match next parser with 39 | | Ok { item = Graphql_lexer.Name name; span = _, end_pos } -> 40 | let acc = acc ^ name in 41 | begin match peek parser with 42 | | { item = Graphql_lexer.Dot; span = _, end_pos } -> let _ = next parser in loop start_pos end_pos (acc ^ ".") 43 | | _ -> Ok (start_end start_pos end_pos acc) 44 | end 45 | | Ok ({ item = Graphql_lexer.End_of_file; _ } as span) -> Error (replace span Unexpected_end_of_file) 46 | | Ok span -> Error (map (fun t -> Unexpected_token t) span) 47 | | Error e -> Error e 48 | in 49 | let { span = start_pos, end_pos; _ } = peek parser in 50 | loop start_pos end_pos "" 51 | 52 | let skip parser token = 53 | match peek parser with 54 | | span when span.item = token -> Result_ext.map (fun x -> Some x) (next parser) 55 | | span when span.item = Graphql_lexer.End_of_file -> Error (zero_width (start_pos span) Unexpected_end_of_file) 56 | | _ -> Ok None 57 | 58 | let delimited_list parser opening sub_parser closing = 59 | match expect parser opening with 60 | | Error e -> Error e 61 | | Ok { span = (start_pos, _); _ } -> 62 | let rec scanner acc = 63 | match skip parser closing with 64 | | Ok (Some { span = (_, end_pos); _ }) -> Ok (start_end start_pos end_pos (List.rev acc)) 65 | | _ -> 66 | match sub_parser parser with 67 | | Ok span -> scanner (span :: acc) 68 | | Error e -> Error e 69 | in 70 | scanner [] 71 | 72 | let delimited_nonempty_list parser opening sub_parser closing = 73 | match expect parser opening with 74 | | Error e -> Error e 75 | | Ok { span = (start_pos, _); _ } -> 76 | let rec scanner acc = 77 | match sub_parser parser with 78 | | Error e -> Error e 79 | | Ok span -> match skip parser closing with 80 | | Error e -> Error e 81 | | Ok Some { span = (_, end_pos); _ } -> Ok (start_end start_pos end_pos (List.rev (span :: acc))) 82 | | Ok None -> scanner (span :: acc) 83 | in 84 | scanner [] 85 | -------------------------------------------------------------------------------- /src/base/graphql_parser_value.ml: -------------------------------------------------------------------------------- 1 | open Result 2 | 3 | open Source_pos 4 | 5 | open Graphql_parser 6 | open Graphql_ast 7 | 8 | let map_next_token parser f = match next parser with 9 | | Ok item -> Ok (map f item) 10 | | e -> e 11 | 12 | let replace_next_token parser t = match next parser with 13 | | Ok item -> Ok (replace item t) 14 | | Error e -> Error e 15 | 16 | let map_next_token_with_error parser f = match next parser with 17 | | Ok item -> Error (map f item) 18 | | Error e -> Error e 19 | 20 | let parse_variable_literal parser = Result_ext.( 21 | expect parser Graphql_lexer.Dollar 22 | |> flat_map (fun { span = start_pos, _; _ } -> expect_name parser |> map (make_t2 start_pos)) 23 | |> map (fun (start_pos, { item; span = _, end_pos }) -> start_end start_pos end_pos (Iv_variable item)) 24 | ) 25 | 26 | let rec parse_value_literal is_const parser = match peek parser with 27 | | { item = Graphql_lexer.Bracket_open; _ } -> parse_list_literal is_const parser 28 | | { item = Graphql_lexer.Curly_open; _ } -> parse_object_literal is_const parser 29 | | { item = Graphql_lexer.Dollar; _ } when not is_const -> parse_variable_literal parser 30 | | { item = Graphql_lexer.Int i; _ } -> replace_next_token parser (Iv_int i) 31 | | { item = Graphql_lexer.Float f; _ } -> replace_next_token parser (Iv_float f) 32 | | { item = Graphql_lexer.String s; _ } -> replace_next_token parser (Iv_string s) 33 | | { item = Graphql_lexer.Name "true"; _ } -> replace_next_token parser (Iv_boolean true) 34 | | { item = Graphql_lexer.Name "false"; _ } -> replace_next_token parser (Iv_boolean false) 35 | | { item = Graphql_lexer.Name "null"; _ } -> replace_next_token parser (Iv_null) 36 | | { item = Graphql_lexer.Name name; _ } -> replace_next_token parser (Iv_enum name) 37 | | _ -> map_next_token_with_error parser (fun t -> Unexpected_token t) 38 | 39 | and parse_list_literal is_const parser = 40 | delimited_list parser Graphql_lexer.Bracket_open (parse_value_literal is_const) Graphql_lexer.Bracket_close 41 | |> Result_ext.map (fun span -> map (fun items -> Iv_list items) span) 42 | 43 | and parse_object_literal is_const parser = 44 | delimited_list parser Graphql_lexer.Curly_open (parse_object_field is_const) Graphql_lexer.Curly_close 45 | |> Result_ext.map (fun span -> map (fun items -> Iv_object items) span) 46 | 47 | and parse_object_field is_const parser = Result_ext.( 48 | expect_name parser 49 | |> flat_map (fun key -> expect parser Graphql_lexer.Colon |> replace key) 50 | |> flat_map (fun key -> parse_value_literal is_const parser |> map (make_t2 key)) 51 | ) 52 | -------------------------------------------------------------------------------- /src/base/graphql_ppx_base.ml: -------------------------------------------------------------------------------- 1 | module Generator_utils = Generator_utils 2 | module Graphql_lexer = Graphql_lexer 3 | module Graphql_parser = Graphql_parser 4 | module Graphql_parser_document = Graphql_parser_document 5 | module Log = Log 6 | module Ppx_config = Ppx_config 7 | module Read_schema = Read_schema 8 | module Result_decoder = Result_decoder 9 | module Result_structure = Result_structure 10 | module Source_pos = Source_pos 11 | module Validations = Validations 12 | module Schema = Schema 13 | module Compat = Compat 14 | module Graphql_ast = Graphql_ast 15 | module Type_utils = Type_utils 16 | module Option = Option 17 | module Traversal_utils = Traversal_utils 18 | module Graphql_printer = Graphql_printer 19 | module Ast_serializer_apollo = Ast_serializer_apollo 20 | -------------------------------------------------------------------------------- /src/base/log.ml: -------------------------------------------------------------------------------- 1 | let log msg = 2 | if Ppx_config.verbose_logging () then print_endline msg 3 | 4 | let must_log = print_endline 5 | -------------------------------------------------------------------------------- /src/base/multi_visitor.ml: -------------------------------------------------------------------------------- 1 | module NullVisitor: Traversal_utils.VisitorSig = struct 2 | type t = () 3 | include Traversal_utils.AbstractVisitor 4 | let make_self _ = () 5 | end 6 | 7 | module Visitor(H: Traversal_utils.VisitorSig)(T: Traversal_utils.VisitorSig): Traversal_utils.VisitorSig = struct 8 | type t = H.t * T.t 9 | 10 | let make_self () = (H.make_self (), T.make_self ()) 11 | 12 | let enter_document (h, t) ctx doc = 13 | let () = H.enter_document h ctx doc in 14 | T.enter_document t ctx doc 15 | let exit_document (h, t) ctx doc = 16 | let () = H.exit_document h ctx doc in 17 | T.exit_document t ctx doc 18 | 19 | let enter_operation_definition (h, t) ctx def = 20 | let () = H.enter_operation_definition h ctx def in 21 | T.enter_operation_definition t ctx def 22 | let exit_operation_definition (h, t) ctx def = 23 | let () = H.exit_operation_definition h ctx def in 24 | T.exit_operation_definition t ctx def 25 | 26 | let enter_fragment_definition (h, t) ctx def = 27 | let () = H.enter_fragment_definition h ctx def in 28 | T.enter_fragment_definition t ctx def 29 | let exit_fragment_definition (h, t) ctx def = 30 | let () = H.exit_fragment_definition h ctx def in 31 | T.exit_fragment_definition t ctx def 32 | 33 | let enter_variable_definition (h, t) ctx def = 34 | let () = H.enter_variable_definition h ctx def in 35 | T.enter_variable_definition t ctx def 36 | let exit_variable_definition (h, t) ctx def = 37 | let () = H.exit_variable_definition h ctx def in 38 | T.exit_variable_definition t ctx def 39 | 40 | let enter_directive (h, t) ctx d = 41 | let () = H.enter_directive h ctx d in 42 | T.enter_directive t ctx d 43 | let exit_directive (h, t) ctx d = 44 | let () = H.exit_directive h ctx d in 45 | T.exit_directive t ctx d 46 | 47 | let enter_argument (h, t) ctx a = 48 | let () = H.enter_argument h ctx a in 49 | T.enter_argument t ctx a 50 | let exit_argument (h, t) ctx a = 51 | let () = H.exit_argument h ctx a in 52 | T.exit_argument t ctx a 53 | 54 | let enter_selection_set (h, t) ctx s = 55 | let () = H.enter_selection_set h ctx s in 56 | T.enter_selection_set t ctx s 57 | let exit_selection_set (h, t) ctx s = 58 | let () = H.exit_selection_set h ctx s in 59 | T.exit_selection_set t ctx s 60 | 61 | let enter_field (h, t) ctx f = 62 | let () = H.enter_field h ctx f in 63 | T.enter_field t ctx f 64 | let exit_field (h, t) ctx f = 65 | let () = H.exit_field h ctx f in 66 | T.exit_field t ctx f 67 | 68 | let enter_fragment_spread (h, t) ctx fs = 69 | let () = H.enter_fragment_spread h ctx fs in 70 | T.enter_fragment_spread t ctx fs 71 | let exit_fragment_spread (h, t) ctx fs = 72 | let () = H.exit_fragment_spread h ctx fs in 73 | T.exit_fragment_spread t ctx fs 74 | 75 | let enter_inline_fragment (h, t) ctx if_ = 76 | let () = H.enter_inline_fragment h ctx if_ in 77 | T.enter_inline_fragment t ctx if_ 78 | let exit_inline_fragment (h, t) ctx if_ = 79 | let () = H.exit_inline_fragment h ctx if_ in 80 | T.exit_inline_fragment t ctx if_ 81 | 82 | let enter_null_value (h, t) ctx v = 83 | let () = H.enter_null_value h ctx v in 84 | T.enter_null_value t ctx v 85 | let exit_null_value (h, t) ctx v = 86 | let () = H.exit_null_value h ctx v in 87 | T.exit_null_value t ctx v 88 | 89 | let enter_int_value (h, t) ctx v = 90 | let () = H.enter_int_value h ctx v in 91 | T.enter_int_value t ctx v 92 | let exit_int_value (h, t) ctx v = 93 | let () = H.exit_int_value h ctx v in 94 | T.exit_int_value t ctx v 95 | 96 | let enter_float_value (h, t) ctx v = 97 | let () = H.enter_float_value h ctx v in 98 | T.enter_float_value t ctx v 99 | let exit_float_value (h, t) ctx v = 100 | let () = H.exit_float_value h ctx v in 101 | T.exit_float_value t ctx v 102 | 103 | let enter_string_value (h, t) ctx v = 104 | let () = H.enter_string_value h ctx v in 105 | T.enter_string_value t ctx v 106 | let exit_string_value (h, t) ctx v = 107 | let () = H.exit_string_value h ctx v in 108 | T.exit_string_value t ctx v 109 | 110 | let enter_bool_value (h, t) ctx v = 111 | let () = H.enter_bool_value h ctx v in 112 | T.enter_bool_value t ctx v 113 | let exit_bool_value (h, t) ctx v = 114 | let () = H.exit_bool_value h ctx v in 115 | T.exit_bool_value t ctx v 116 | 117 | let enter_enum_value (h, t) ctx v = 118 | let () = H.enter_enum_value h ctx v in 119 | T.enter_enum_value t ctx v 120 | let exit_enum_value (h, t) ctx v = 121 | let () = H.exit_enum_value h ctx v in 122 | T.exit_enum_value t ctx v 123 | 124 | let enter_variable_value (h, t) ctx v = 125 | let () = H.enter_variable_value h ctx v in 126 | T.enter_variable_value t ctx v 127 | let exit_variable_value (h, t) ctx v = 128 | let () = H.exit_variable_value h ctx v in 129 | T.exit_variable_value t ctx v 130 | 131 | let enter_list_value (h, t) ctx v = 132 | let () = H.enter_list_value h ctx v in 133 | T.enter_list_value t ctx v 134 | let exit_list_value (h, t) ctx v = 135 | let () = H.exit_list_value h ctx v in 136 | T.exit_list_value t ctx v 137 | 138 | let enter_object_value (h, t) ctx v = 139 | let () = H.enter_object_value h ctx v in 140 | T.enter_object_value t ctx v 141 | let exit_object_value (h, t) ctx v = 142 | let () = H.exit_object_value h ctx v in 143 | T.exit_object_value t ctx v 144 | 145 | let enter_object_field (h, t) ctx f = 146 | let () = H.enter_object_field h ctx f in 147 | T.enter_object_field t ctx f 148 | let exit_object_field (h, t) ctx f = 149 | let () = H.exit_object_field h ctx f in 150 | T.exit_object_field t ctx f 151 | 152 | end 153 | -------------------------------------------------------------------------------- /src/base/option.ml: -------------------------------------------------------------------------------- 1 | exception Option_unwrap_error 2 | 3 | let map f = function 4 | | None -> None 5 | | Some v -> Some (f v) 6 | 7 | let flat_map f = function 8 | | None -> None 9 | | Some v -> f v 10 | 11 | let unsafe_unwrap = function 12 | | None -> raise Option_unwrap_error 13 | | Some v -> v 14 | -------------------------------------------------------------------------------- /src/base/ppx_config.ml: -------------------------------------------------------------------------------- 1 | type output_mode = 2 | | String 3 | | Apollo_AST 4 | 5 | type config = { 6 | verbose_logging: bool; 7 | output_mode: output_mode; 8 | verbose_error_handling: bool; 9 | apollo_mode: bool; 10 | root_directory: string; 11 | schema_file: string; 12 | raise_error_with_loc: 'a. Source_pos.ast_location -> string -> 'a 13 | } 14 | 15 | let config_ref = ref None 16 | 17 | let set_config config = config_ref := Some(config) 18 | 19 | let verbose_logging () = 20 | (!config_ref |> Option.unsafe_unwrap).verbose_logging 21 | 22 | let output_mode () = 23 | (!config_ref |> Option.unsafe_unwrap).output_mode 24 | 25 | let apollo_mode () = 26 | (!config_ref |> Option.unsafe_unwrap).apollo_mode 27 | 28 | let verbose_error_handling () = 29 | (!config_ref |> Option.unsafe_unwrap).verbose_error_handling 30 | 31 | let root_directory () = 32 | (!config_ref |> Option.unsafe_unwrap).root_directory 33 | 34 | let schema_file () = 35 | (!config_ref |> Option.unsafe_unwrap).schema_file 36 | 37 | let raise_error_with_loc loc message = 38 | (!config_ref |> Option.unsafe_unwrap).raise_error_with_loc loc message 39 | -------------------------------------------------------------------------------- /src/base/result_ext.ml: -------------------------------------------------------------------------------- 1 | open Result 2 | 3 | let flat_map f = function 4 | | Error e -> Error e 5 | | Ok v -> f v 6 | 7 | let map f = function 8 | | Error e -> Error e 9 | | Ok v -> Ok (f v) 10 | 11 | let replace v = function 12 | | Error e -> Error e 13 | | Ok _ -> Ok v 14 | 15 | let make_t2 a b = (a, b) 16 | let make_t3 a b c = (a, b, c) 17 | let make_t4 a b c d = (a, b, c, d) 18 | let make_t5 a b c d e = (a, b, c, d, e) 19 | -------------------------------------------------------------------------------- /src/base/result_structure.ml: -------------------------------------------------------------------------------- 1 | type exhaustive_flag = | Exhaustive | Nonexhaustive 2 | 3 | type loc = Source_pos.ast_location 4 | 5 | type field_result = 6 | | Fr_named_field of string * loc * t 7 | | Fr_fragment_spread of string * loc * string 8 | 9 | and t = 10 | | Res_nullable of loc * t 11 | | Res_array of loc * t 12 | | Res_id of loc 13 | | Res_string of loc 14 | | Res_int of loc 15 | | Res_float of loc 16 | | Res_boolean of loc 17 | | Res_raw_scalar of loc 18 | | Res_poly_enum of loc * Schema.enum_meta 19 | | Res_custom_decoder of loc * string * t 20 | | Res_record of loc * string * field_result list 21 | | Res_object of loc * string * field_result list 22 | | Res_poly_variant_selection_set of loc * string * (string * t) list 23 | | Res_poly_variant_union of loc * string * (string * t) list * exhaustive_flag 24 | | Res_poly_variant_interface of loc * string * (string * t) * (string * t) list 25 | | Res_solo_fragment_spread of loc * string 26 | | Res_error of loc * string 27 | 28 | type mod_ = 29 | | Mod_fragment of string * (string list) * bool * Graphql_ast.fragment Source_pos.spanning * t 30 | | Mod_default_operation of Graphql_ast.variable_definitions Source_pos.spanning option * bool * Graphql_ast.operation Source_pos.spanning * t 31 | 32 | let res_loc = function 33 | | Res_nullable (loc, _) 34 | | Res_array (loc, _) 35 | | Res_id loc | Res_string loc | Res_int loc | Res_float loc | Res_boolean loc | Res_raw_scalar loc 36 | | Res_poly_enum (loc, _) 37 | | Res_custom_decoder (loc, _, _) 38 | | Res_record (loc, _, _) 39 | | Res_object (loc, _, _) 40 | | Res_poly_variant_selection_set (loc, _, _) 41 | | Res_poly_variant_union (loc, _, _, _) 42 | | Res_poly_variant_interface (loc, _,_, _) 43 | | Res_solo_fragment_spread (loc, _) 44 | | Res_error (loc, _) 45 | -> loc 46 | 47 | let can_be_absent_as_field = function 48 | | Res_nullable _ -> true 49 | | _ -> false 50 | -------------------------------------------------------------------------------- /src/base/rule_known_argument_names.ml: -------------------------------------------------------------------------------- 1 | module Visitor: Traversal_utils.VisitorSig = struct 2 | open Traversal_utils 3 | open Source_pos 4 | open Graphql_ast 5 | open Schema 6 | 7 | type arg_pos = 8 | | Directive of string 9 | | Field of string * string 10 | 11 | type known_args = (arg_pos * argument_meta list) option 12 | 13 | type t = known_args ref 14 | include Traversal_utils.AbstractVisitor 15 | 16 | let make_self () = ref None 17 | 18 | let report_error ctx span pos arg_name = 19 | let msg = match pos with 20 | | Directive dir_name -> Printf.sprintf "Unknown argument \"%s\" on directive \"%s\"" arg_name dir_name 21 | | Field (field_name, type_name) 22 | -> Printf.sprintf "Unknown argument \"%s\" on field \"%s\" of type \"%s\"" arg_name field_name type_name 23 | in 24 | Context.push_error ctx span msg 25 | 26 | let enter_argument known_args ctx (name, _) = 27 | match !known_args with 28 | | None -> () 29 | | Some (pos, known_args) -> 30 | if not @@ List.exists (fun am -> am.am_name = name.item) known_args then 31 | report_error ctx name.span pos name.item 32 | 33 | 34 | let enter_directive known_args ctx directive = 35 | known_args := Schema.lookup_directive ctx.schema directive.item.d_name.item 36 | |> Option.map (fun dm -> (Directive dm.dm_name, dm.dm_arguments)) 37 | 38 | let exit_directive known_args _ _ = 39 | known_args := None 40 | 41 | let enter_field known_args ctx field = 42 | let field_name = field.item.fd_name.item in 43 | known_args := match Context.parent_type ctx with 44 | | Some parent_type -> Schema.lookup_field parent_type field_name 45 | |> Option.map (fun f -> 46 | (Field (field_name, Schema.type_name parent_type), f.fm_arguments)) 47 | | None -> None 48 | 49 | let exit_field known_args _ _ = 50 | known_args := None 51 | 52 | end 53 | -------------------------------------------------------------------------------- /src/base/rule_no_unused_variables.ml: -------------------------------------------------------------------------------- 1 | module Visitor: Traversal_utils.VisitorSig = struct 2 | open Traversal_utils 3 | open Source_pos 4 | open Graphql_ast 5 | 6 | include AbstractVisitor 7 | 8 | type t = (string, string spanning) Hashtbl.t 9 | 10 | let make_self () = Hashtbl.create 0 11 | 12 | let enter_operation_definition self _ def = 13 | let () = Hashtbl.clear self in 14 | match def.item.o_variable_definitions with 15 | | None -> () 16 | | Some { item; _ } -> 17 | List.iter (fun (name, _) -> Hashtbl.add self name.item name) item 18 | 19 | let enter_variable_value self _ name = 20 | Hashtbl.remove self name.item 21 | 22 | let exit_operation_definition self ctx def = 23 | Hashtbl.iter 24 | (fun _ v -> 25 | let message = match def.item.o_name with 26 | | None -> Printf.sprintf "Variable \"$%s\" is never used." v.item 27 | | Some name -> Printf.sprintf "Variable \"$%s\" is never used in operation \"%s\"" v.item name.item 28 | in 29 | Context.push_error ctx v.span message) 30 | self 31 | 32 | end 33 | -------------------------------------------------------------------------------- /src/base/schema.ml: -------------------------------------------------------------------------------- 1 | type type_ref = 2 | | Named of string 3 | | NonNull of type_ref 4 | | List of type_ref 5 | 6 | type argument_meta = { 7 | am_name: string; 8 | am_description: string option; 9 | am_arg_type: type_ref; 10 | am_default_value: string option; 11 | } 12 | 13 | type field_meta = { 14 | fm_name: string; 15 | fm_description: string option; 16 | fm_arguments: argument_meta list; 17 | fm_field_type: type_ref; 18 | fm_deprecation_reason: string option; 19 | } 20 | 21 | type scalar_meta = { 22 | sm_name: string; 23 | sm_description: string option; 24 | } 25 | 26 | type object_meta = { 27 | om_name: string; 28 | om_description: string option; 29 | om_fields: field_meta list; 30 | om_interfaces: string list; 31 | } 32 | 33 | type enum_value_meta = { 34 | evm_name: string; 35 | evm_description: string option; 36 | evm_deprecation_reason: string option; 37 | } 38 | 39 | type enum_meta = { 40 | em_name: string; 41 | em_description: string option; 42 | em_values: enum_value_meta list; 43 | } 44 | 45 | type interface_meta = { 46 | im_name: string; 47 | im_description: string option; 48 | im_fields: field_meta list; 49 | } 50 | 51 | type union_meta = { 52 | um_name: string; 53 | um_description: string option; 54 | um_of_types: string list; 55 | } 56 | 57 | type input_object_meta = { 58 | iom_name: string; 59 | iom_description: string option; 60 | iom_input_fields: argument_meta list; 61 | } 62 | 63 | type type_meta = 64 | | Scalar of scalar_meta 65 | | Object of object_meta 66 | | Enum of enum_meta 67 | | Interface of interface_meta 68 | | Union of union_meta 69 | | InputObject of input_object_meta 70 | 71 | type schema_meta = { 72 | sm_query_type: string; 73 | sm_mutation_type: string option; 74 | sm_subscription_type: string option; 75 | } 76 | 77 | type directive_location = 78 | | Dl_query 79 | | Dl_mutation 80 | | Dl_subscription 81 | | Dl_field 82 | | Dl_fragment_definition 83 | | Dl_fragment_spread 84 | | Dl_inline_fragment 85 | | Dl_unknown 86 | 87 | type directive_meta = { 88 | dm_name: string; 89 | dm_locations: directive_location list; 90 | dm_arguments: argument_meta list; 91 | } 92 | 93 | type schema = { 94 | meta: schema_meta; 95 | type_map: (string, type_meta) Hashtbl.t; 96 | directive_map: (string, directive_meta) Hashtbl.t; 97 | } 98 | 99 | let query_type s = Hashtbl.find s.type_map s.meta.sm_query_type 100 | let mutation_type s = match s.meta.sm_mutation_type with 101 | | Some n -> Some (Hashtbl.find s.type_map n) 102 | | None -> None 103 | let subscription_type s = match s.meta.sm_subscription_type with 104 | | Some n -> Some (Hashtbl.find s.type_map n) 105 | | None -> None 106 | 107 | exception Invalid_type of string 108 | exception Inconsistent_schema of string 109 | 110 | 111 | let lookup_implementations schema im = 112 | let all_objects_implementing_interface _ value acc = match value with 113 | | Object { om_interfaces; _ } as o when List.exists (fun n -> n = im.im_name) om_interfaces -> o :: acc 114 | | _ -> acc 115 | in 116 | Hashtbl.fold all_objects_implementing_interface schema.type_map [] 117 | 118 | let lookup_field ty name = 119 | let find_field fs = 120 | match List.find_all (fun f -> f.fm_name = name) fs with 121 | | [] -> None 122 | | [x] -> Some x 123 | | _ -> raise @@ Inconsistent_schema ("Multiple fields named " ^ name) 124 | in 125 | match ty with 126 | | Object { om_fields; _ } -> find_field om_fields 127 | | Interface { im_fields; _ } -> find_field im_fields 128 | | Scalar { sm_name; _ } -> raise @@ Invalid_type ("Type " ^ sm_name ^ " doesn't have any fields") 129 | | Enum { em_name; _ } -> raise @@ Invalid_type ("Type " ^ em_name ^ " doesn't have any fields") 130 | | Union { um_name; _ } -> raise @@ Invalid_type ("Type " ^ um_name ^ " doesn't have any fields") 131 | | InputObject { iom_name; _ } -> raise @@ Invalid_type ("Type " ^ iom_name ^ " doesn't have any fields") 132 | 133 | let lookup_input_field ty name = 134 | let find_field fs = 135 | match List.find_all (fun am -> am.am_name = name) fs with 136 | | [] -> None 137 | | [x] -> Some x 138 | | _ -> raise @@ Inconsistent_schema ("Multiple input fields named " ^ name) 139 | in 140 | match ty with 141 | | Object { om_name = name; _ } 142 | | Interface { im_name = name; _ } 143 | | Scalar { sm_name = name; _ } 144 | | Enum { em_name = name; _ } 145 | | Union { um_name = name; _ } -> raise @@ Invalid_type ("Type " ^ name ^ " doesn't have any input fields") 146 | | InputObject { iom_input_fields; _ } -> find_field iom_input_fields 147 | 148 | let type_name ty = match ty with 149 | | Scalar { sm_name; _ } -> sm_name 150 | | Object { om_name; _ } -> om_name 151 | | Enum { em_name; _ } -> em_name 152 | | Interface { im_name; _ } -> im_name 153 | | Union { um_name; _ } -> um_name 154 | | InputObject { iom_name; _ } -> iom_name 155 | 156 | let lookup_type schema name = 157 | match (Hashtbl.find_all schema.type_map name) with 158 | | [] -> None 159 | | [x] -> Some x 160 | | _ -> raise @@ Inconsistent_schema ("Multiple types named " ^ name) 161 | 162 | let lookup_directive schema name = 163 | match (Hashtbl.find_all schema.directive_map name) with 164 | | [] -> None 165 | | [x] -> Some x 166 | | _ -> raise @@ Inconsistent_schema ("Multiple directives named " ^ name) 167 | 168 | let all_enums schema = Hashtbl.fold (fun _ v acc -> 169 | match v with 170 | | Enum e -> e :: acc 171 | | _ -> acc) schema.type_map [] 172 | 173 | let extract_name_from_type_meta = function 174 | | Scalar { sm_name = x; _ } 175 | | Object { om_name = x; _ } 176 | | Enum { em_name = x; _ } 177 | | Interface { im_name = x; _ } 178 | | Union { um_name = x; _ } 179 | | InputObject {iom_name = x; _ } -> x 180 | 181 | let compare_type_meta x y = 182 | String.compare (extract_name_from_type_meta x) (extract_name_from_type_meta y) 183 | 184 | let rec innermost_name = function 185 | | Named name -> name 186 | | NonNull inner | List inner -> innermost_name inner 187 | -------------------------------------------------------------------------------- /src/base/source_pos.ml: -------------------------------------------------------------------------------- 1 | type source_position = { 2 | index: int; 3 | line: int; 4 | col: int; 5 | } 6 | 7 | type 'a spanning = { 8 | item: 'a; 9 | span: source_position * source_position; 10 | } 11 | 12 | let origin = { index = 0; line = 0; col = 0 } 13 | 14 | let advance_line { index; line; col = _ } = 15 | { index = index + 1; line = line + 1; col = 0 } 16 | 17 | let advance_col { index; line; col } = 18 | { index = index + 1; line = line; col = col + 1 } 19 | 20 | let zero_width pos item = 21 | { span = (pos, pos); item = item } 22 | 23 | let single_width pos item = 24 | { span = (pos, advance_col pos); item = item } 25 | 26 | let start_end startPos endPos item = 27 | { span = (startPos, endPos); item = item } 28 | 29 | let replace span item = 30 | { span = span.span; item = item } 31 | 32 | let map f span = 33 | { span = span.span; item = f span.item } 34 | 35 | let start_pos span = fst span.span 36 | let end_pos span = snd span.span 37 | 38 | type ast_position = { 39 | pos_fname : string; 40 | pos_lnum : int; 41 | pos_bol : int; 42 | pos_cnum : int; 43 | } 44 | 45 | type ast_location = { 46 | loc_start: ast_position; 47 | loc_end: ast_position; 48 | loc_ghost: bool; 49 | } 50 | -------------------------------------------------------------------------------- /src/base/type_utils.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ast 2 | open Source_pos 3 | open Schema 4 | 5 | type native_type_ref = 6 | | Ntr_named of string 7 | | Ntr_nullable of native_type_ref 8 | | Ntr_list of native_type_ref 9 | 10 | let rec to_native_type_ref tr = match tr with 11 | | NonNull (Named n) -> Ntr_named n 12 | | NonNull (List l) -> Ntr_list (to_native_type_ref l) 13 | | NonNull i -> to_native_type_ref i 14 | | List l -> Ntr_nullable (Ntr_list (to_native_type_ref l)) 15 | | Named n -> Ntr_nullable (Ntr_named n) 16 | 17 | let rec to_schema_type_ref tr = match tr with 18 | | Tr_list l -> List (to_schema_type_ref l.item) 19 | | Tr_named n -> Named n.item 20 | | Tr_non_null_list l -> NonNull (List (to_schema_type_ref l.item)) 21 | | Tr_non_null_named n -> NonNull (Named n.item) 22 | 23 | let is_nullable = function 24 | | Ntr_named _ | Ntr_list _ -> false 25 | | Ntr_nullable _ -> true -------------------------------------------------------------------------------- /src/base/validations.ml: -------------------------------------------------------------------------------- 1 | open Traversal_utils 2 | 3 | module AllRulesImpl = 4 | Multi_visitor.Visitor(Rule_known_argument_names.Visitor)( 5 | Multi_visitor.Visitor(Rule_no_unused_variables.Visitor)( 6 | Multi_visitor.NullVisitor)) 7 | 8 | module AllRules = Visitor(AllRulesImpl) 9 | 10 | let run_validators config document = 11 | let ctx = make_context config document in 12 | let _ = AllRules.visit_document ctx document in 13 | match ! (ctx.errors) with 14 | | [] -> None 15 | | errs -> Some errs 16 | -------------------------------------------------------------------------------- /src/bucklescript/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name graphql_ppx) 3 | (libraries ocaml-migrate-parsetree 4 | graphql_ppx_base) 5 | (flags (:include ../../discover/dune.flags)) 6 | (preprocess (pps ppx_tools_versioned.metaquot_402))) 7 | -------------------------------------------------------------------------------- /src/bucklescript/graphql_ppx.ml: -------------------------------------------------------------------------------- 1 | module From_current = Migrate_parsetree.Convert(Migrate_parsetree.OCaml_current)(Migrate_parsetree.OCaml_402) 2 | module To_current = Migrate_parsetree.Convert(Migrate_parsetree.OCaml_402)(Migrate_parsetree.OCaml_current) 3 | 4 | open Graphql_ppx_base 5 | open Source_pos 6 | 7 | open Output_bucklescript_utils 8 | 9 | let add_pos delimLength base pos = 10 | let (_, _, col) = Location.get_pos_info (conv_pos base) in 11 | { 12 | pos_fname = base.pos_fname; 13 | pos_lnum = base.pos_lnum + pos.line; 14 | pos_bol = 0; 15 | pos_cnum = if pos.line = 0 then delimLength + col + pos.col else pos.col; 16 | } 17 | 18 | let add_loc delimLength base span = 19 | { 20 | loc_start = add_pos delimLength base.loc_start (fst span); 21 | loc_end = add_pos delimLength base.loc_start (snd span); 22 | loc_ghost = false; 23 | } 24 | 25 | let fmt_lex_err err = 26 | let open Graphql_lexer in 27 | match err with 28 | | Unknown_character ch -> Printf.sprintf "Unknown character %c" ch 29 | | Unexpected_character ch -> Printf.sprintf "Unexpected character %c" ch 30 | | Unterminated_string -> Printf.sprintf "Unterminated string literal" 31 | | Unknown_character_in_string ch -> Printf.sprintf "Unknown character in string literal: %c" ch 32 | | Unknown_escape_sequence s -> Printf.sprintf "Unknown escape sequence in string literal: %s" s 33 | | Unexpected_end_of_file -> Printf.sprintf "Unexpected end of query" 34 | | Invalid_number -> Printf.sprintf "Invalid number" 35 | 36 | let fmt_parse_err err = 37 | let open Graphql_parser in 38 | match err with 39 | | Unexpected_token t -> Printf.sprintf "Unexpected token %s" (Graphql_lexer.string_of_token t) 40 | | Unexpected_end_of_file -> "Unexpected end of query" 41 | | Lexer_error err -> fmt_lex_err err 42 | 43 | let make_error_expr loc message = 44 | let open Ast_402 in 45 | let ext = Ast_mapper.extension_of_error (Location.error ~loc message) in 46 | Ast_helper.Exp.extension ~loc ext 47 | 48 | let is_prefixed prefix str = 49 | let i = 0 in 50 | let len = String.length prefix in 51 | let j = ref 0 in 52 | while !j < len && String.unsafe_get prefix !j = 53 | String.unsafe_get str (i + !j) do 54 | incr j 55 | done; 56 | (!j = len) 57 | 58 | let drop_prefix prefix str = 59 | let len = String.length prefix in 60 | let rest = (String.length str) - len in 61 | String.sub str len rest 62 | 63 | let rewrite_query loc delim query = 64 | let open Ast_402 in 65 | let open Ast_helper in 66 | let open Parsetree in 67 | let lexer = Graphql_lexer.make query in 68 | let delimLength = match delim with | Some s -> 2 + String.length s | None -> 1 in 69 | match Graphql_lexer.consume lexer with 70 | | Result.Error e -> raise (Location.Error ( 71 | Location.error ~loc:(add_loc delimLength loc e.span |> conv_loc) (fmt_lex_err e.item) 72 | )) 73 | | Result.Ok tokens -> 74 | let parser = Graphql_parser.make tokens in 75 | match Graphql_parser_document.parse_document parser with 76 | | Result.Error e -> raise (Location.Error ( 77 | Location.error ~loc:(add_loc delimLength loc e.span |> conv_loc) (fmt_parse_err e.item) 78 | )) 79 | | Result.Ok document -> 80 | let config = { 81 | Generator_utils.map_loc = add_loc delimLength loc; 82 | delimiter = delim; 83 | full_document = document; 84 | (* the only call site of schema, make it lazy! *) 85 | schema = Lazy.force (Read_schema.get_schema ()); 86 | } in 87 | match Validations.run_validators config document with 88 | | Some errs -> 89 | Mod.mk 90 | (Pmod_structure (List.map (fun (loc, msg) -> 91 | let loc = conv_loc loc in 92 | [%stri let _ = [%e make_error_expr loc msg]]) errs)) 93 | | None -> 94 | let parts = Result_decoder.unify_document_schema config document in 95 | Output_bucklescript_module.generate_modules config parts 96 | 97 | let mapper argv = 98 | let open Ast_402 in 99 | let open Ast_mapper in 100 | let open Parsetree in 101 | let open Asttypes in 102 | 103 | let () = Ppx_config.(set_config { 104 | verbose_logging = (match List.find ((=) "-verbose") argv with 105 | | _ -> true 106 | | exception Not_found -> false); 107 | output_mode = (match List.find ((=) "-ast-out") argv with 108 | | _ -> Ppx_config.Apollo_AST 109 | | exception Not_found -> Ppx_config.String); 110 | verbose_error_handling = (match List.find ((=) "-o") argv with 111 | | _ -> false 112 | | exception Not_found -> begin match Sys.getenv "NODE_ENV" with 113 | | "production" -> false 114 | | _ -> true 115 | | exception Not_found -> true 116 | end); 117 | apollo_mode = (match List.find ((=) "-apollo-mode") argv with 118 | | _ -> true 119 | | exception Not_found -> false); 120 | root_directory = Sys.getcwd (); 121 | schema_file = (match List.find (is_prefixed "-schema=") argv with 122 | | arg -> drop_prefix "-schema=" arg 123 | | exception Not_found -> "graphql_schema.json"); 124 | raise_error_with_loc = fun loc message -> 125 | let loc = conv_loc loc in 126 | raise (Location.Error (Location.error ~loc message)) 127 | }) in 128 | 129 | let module_expr mapper mexpr = begin 130 | match mexpr with 131 | | {pmod_desc = Pmod_extension ({txt = "graphql"; loc}, pstr); _} -> begin 132 | match pstr with 133 | | PStr [{ pstr_desc = Pstr_eval ({ 134 | pexp_loc = loc; 135 | pexp_desc = Pexp_constant (Const_string (query, delim)); _}, _); _}] -> begin 136 | rewrite_query 137 | (conv_loc_from_ast loc) 138 | delim 139 | query 140 | end 141 | | _ -> raise (Location.Error ( 142 | Location.error ~loc "[%graphql] accepts a string, e.g. [%graphql {| { query |}]" 143 | )) 144 | end 145 | | other -> default_mapper.module_expr mapper other 146 | end in 147 | 148 | To_current.copy_mapper { default_mapper with module_expr } 149 | 150 | 151 | let () = 152 | Migrate_parsetree.Compiler_libs.Ast_mapper.register 153 | "graphql" (fun argv -> mapper argv) 154 | -------------------------------------------------------------------------------- /src/bucklescript/output_bucklescript_encoder.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | open Graphql_ast 3 | open Source_pos 4 | open Schema 5 | 6 | open Ast_402 7 | open Asttypes 8 | 9 | open Type_utils 10 | open Generator_utils 11 | open Output_bucklescript_utils 12 | 13 | let mangle_enum_name = Generator_utils.uncapitalize_ascii 14 | 15 | let ident_from_string loc func_name = 16 | Ast_helper.(Exp.ident ~loc { txt = Longident.parse func_name; loc }) 17 | 18 | module StringSet = Set.Make(String) 19 | 20 | let sort_variable_types schema variables = 21 | let recursive_flag = ref false in 22 | let ordered_nodes = Queue.create () in 23 | let has_added_to_queue name = Queue.fold (fun acc (_, v) -> acc || name = v) false ordered_nodes in 24 | let rec loop visit_stack = function 25 | | [] -> () 26 | | (span, type_ref) :: tail -> 27 | let type_name = innermost_name type_ref in 28 | let () = match lookup_type schema type_name with 29 | | None -> () 30 | | Some _ when StringSet.mem type_name visit_stack -> recursive_flag := true 31 | | Some _ when has_added_to_queue type_name -> () 32 | | Some (Enum _) -> Queue.push (span, type_name) ordered_nodes 33 | | Some (InputObject io) -> 34 | let () = loop 35 | (StringSet.add type_name visit_stack) 36 | (List.map (fun { am_arg_type; _ } -> (span, am_arg_type)) io.iom_input_fields) in 37 | Queue.push (span, type_name) ordered_nodes 38 | | Some _ -> () 39 | in loop visit_stack tail 40 | in 41 | let () = loop StringSet.empty variables in 42 | let ordered_nodes = Array.init 43 | (Queue.length ordered_nodes) 44 | (fun _ -> 45 | let span, name = Queue.take ordered_nodes in 46 | (span, name |> lookup_type schema |> Option.unsafe_unwrap)) in 47 | (! recursive_flag, ordered_nodes) 48 | 49 | let function_name_string x = "json_of_" ^ Schema.extract_name_from_type_meta x 50 | 51 | let rec parser_for_type schema loc type_ref = 52 | let raise_inconsistent_schema type_name = raise_error_with_loc loc ("Inconsistent schema, type named " ^ type_name ^ " cannot be found") in 53 | match type_ref with 54 | | Ntr_list x -> 55 | let child_parser = parser_for_type schema loc x in 56 | [%expr fun v -> Js.Json.array (Js.Array.map [%e child_parser] v)] [@metaloc conv_loc loc] 57 | | Ntr_nullable x -> 58 | let child_parser = parser_for_type schema loc x in 59 | [%expr fun v -> match v with None -> Js.Json.null | Some v -> [%e child_parser] v] [@metaloc conv_loc loc] 60 | | Ntr_named type_name -> 61 | match lookup_type schema type_name with 62 | | None -> raise_inconsistent_schema type_name 63 | | Some (Scalar { sm_name = "String"; _ }) 64 | | Some (Scalar { sm_name = "ID"; _ }) -> [%expr Js.Json.string] 65 | | Some (Scalar { sm_name = "Int"; _ }) -> [%expr fun v -> Js.Json.number (float_of_int v)] 66 | | Some (Scalar { sm_name = "Float"; _ }) -> [%expr Js.Json.number] 67 | | Some (Scalar { sm_name = "Boolean"; _ }) -> [%expr Js.Json.boolean] 68 | | Some (Scalar _) -> [%expr fun v -> v] 69 | | Some ty -> 70 | function_name_string ty |> ident_from_string (conv_loc loc) 71 | 72 | let json_of_fields schema loc expr fields = 73 | let field_array_exprs = fields |> List.map 74 | (fun {am_name; am_arg_type; _} -> 75 | let type_ref = to_native_type_ref am_arg_type in 76 | let parser = parser_for_type schema loc type_ref in 77 | [%expr ( 78 | [%e Ast_helper.Exp.constant (Const_string (am_name, None)) ], 79 | [%e parser] ([%e expr] ## [%e ident_from_string (conv_loc loc) am_name]) 80 | )] [@metaloc conv_loc loc]) in 81 | let field_array = Ast_helper.Exp.array field_array_exprs in 82 | [%expr Js.Json.object_ (Js.Dict.fromArray [%e field_array])] [@metaloc conv_loc loc] 83 | 84 | let generate_encoder config (spanning, x) = 85 | let loc = config.map_loc spanning.span in 86 | let body = match x with 87 | | Scalar _ -> raise_error_with_loc loc "Can not build variable encoder for scalar type" 88 | | Object _ -> raise @@ Invalid_argument "Unsupported variable type: Object" 89 | | Interface _ -> raise @@ Invalid_argument "Unsupported variable type: Interface" 90 | | Union _ -> raise @@ Invalid_argument "Unsupported variable type: Union" 91 | | Enum { em_values; _ } -> 92 | let match_arms = em_values |> List.map 93 | (fun { evm_name; _ } -> 94 | let pattern = Ast_helper.Pat.variant evm_name None in 95 | let expr = Ast_helper.Exp.constant (Const_string (evm_name, None)) in 96 | Ast_helper.Exp.case pattern [%expr Js.Json.string [%e expr]]) in 97 | Ast_helper.Exp.match_ [%expr value] match_arms 98 | | InputObject { iom_input_fields; _ } -> 99 | json_of_fields config.schema loc [%expr value] iom_input_fields 100 | in 101 | let loc = conv_loc loc in 102 | Ast_helper.Vb.mk ~loc (Ast_helper.Pat.var { txt = function_name_string x; loc }) [%expr fun value -> [%e body]] 103 | 104 | let generate_encoders config _loc = function 105 | | Some { item; _ } -> 106 | item 107 | |> List.map (fun (span, {vd_type = variable_type; _}) -> span, to_schema_type_ref variable_type.item) 108 | |> sort_variable_types config.schema 109 | |> (fun (is_recursive, types) -> (if is_recursive then Recursive else Nonrecursive), Array.map (generate_encoder config) types) 110 | | None -> (Nonrecursive, [||]) 111 | -------------------------------------------------------------------------------- /src/bucklescript/output_bucklescript_module.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | open Result_structure 3 | open Generator_utils 4 | 5 | open Ast_402 6 | open Asttypes 7 | open Parsetree 8 | open Ast_helper 9 | 10 | module StringSet = Set.Make(String) 11 | module VariableFinderImpl = struct 12 | type t = StringSet.t ref 13 | let make_self _ = ref StringSet.empty 14 | 15 | include Traversal_utils.AbstractVisitor 16 | 17 | let enter_variable_value self _ v = 18 | self := StringSet.add v.Source_pos.item !self 19 | 20 | let from_self (self: t) : StringSet.t = !self 21 | end 22 | 23 | module VariableFinder = Traversal_utils.Visitor(VariableFinderImpl) 24 | 25 | let find_variables config document = 26 | let ctx = Traversal_utils.make_context config document in 27 | VariableFinderImpl.from_self (VariableFinder.visit_document ctx document) 28 | 29 | let ret_type_magic = [ 30 | (* Some functor magic to determine the return type of parse *) 31 | [%stri module type mt_ret = sig type t end]; 32 | [%stri type 'a typed_ret = (module mt_ret with type t = 'a)]; 33 | [%stri let ret_type (type a) (f: _ -> a) = (let module MT_Ret = struct type t = a end in (module MT_Ret): a typed_ret)]; 34 | [%stri module MT_Ret = (val ret_type parse)]; 35 | [%stri type t = MT_Ret.t]; 36 | ] 37 | 38 | let emit_printed_query parts = 39 | let open Ast_402 in 40 | let open Graphql_printer in 41 | let generate_expr acc = function 42 | | Empty -> acc 43 | | String s -> Ast_helper.(Exp.apply 44 | (Exp.ident { Location.txt = Longident.parse "^"; loc = Location.none }) 45 | [ "", acc; "", Exp.constant (Asttypes.Const_string (s, None)) ]) 46 | | FragmentNameRef f -> Ast_helper.(Exp.apply 47 | (Exp.ident { Location.txt = Longident.parse "^"; loc = Location.none }) 48 | [ "", acc; "", Exp.ident { Location.txt = Longident.parse (f ^ ".name"); loc = Location.none }]) 49 | | FragmentQueryRef f -> Ast_helper.(Exp.apply 50 | (Exp.ident { Location.txt = Longident.parse "^"; loc = Location.none }) 51 | [ "", acc; "", Exp.ident { Location.txt = Longident.parse (f ^ ".query"); loc = Location.none }]) 52 | in 53 | Array.fold_left generate_expr Ast_402.(Ast_helper.Exp.constant (Asttypes.Const_string ("", None))) parts 54 | 55 | let rec emit_json = Ast_402.(function 56 | | `Assoc vs -> 57 | let pairs = Ast_helper.(Exp.array (vs |> List.map (fun (key, value) -> Exp.tuple [ 58 | Exp.constant (Const_string (key, None)); 59 | emit_json value 60 | ]))) in 61 | [%expr Js.Json.object_(Js.Dict.fromArray([%e pairs]))] 62 | | `List ls -> 63 | let values = Ast_helper.Exp.array (List.map emit_json ls) in 64 | [%expr Js.Json.array([%e values])] 65 | | `Bool b -> if b then [%expr Js.Json.boolean(true)] else [%expr Js.Json.boolean(false)] 66 | | `Null -> [%expr Obj.magic(Js.Undefined.empty)] 67 | | `String s -> [%expr Js.Json.string([%e Ast_helper.Exp.constant (Const_string (s, None))])] 68 | | `Int i -> [%expr Js.Json.number([%e Ast_helper.Exp.constant (Const_float (string_of_int i))])] 69 | | `StringExpr parts -> [%expr Js.Json.string([%e emit_printed_query parts])] 70 | ) 71 | 72 | let make_printed_query config document = 73 | let source = Graphql_printer.print_document config.schema document in 74 | let reprinted = match Ppx_config.output_mode () with 75 | | Ppx_config.Apollo_AST -> Ast_serializer_apollo.serialize_document source document |> emit_json 76 | | Ppx_config.String -> emit_printed_query source 77 | in 78 | [ 79 | [%stri let ppx_printed_query = [%e reprinted]]; 80 | [%stri let query = ppx_printed_query]; 81 | ] 82 | 83 | let generate_default_operation config variable_defs has_error operation res_structure = 84 | let parse_fn = Output_bucklescript_decoder.generate_decoder config res_structure in 85 | if has_error then 86 | [ [%stri let parse = fun value -> [%e parse_fn]] ] 87 | else 88 | let (rec_flag, encoders) = 89 | Output_bucklescript_encoder.generate_encoders config (Result_structure.res_loc res_structure) variable_defs in 90 | let make_fn, make_with_variables_fn = Output_bucklescript_unifier.make_make_fun config variable_defs in 91 | List.concat [ 92 | make_printed_query config [Graphql_ast.Operation operation]; 93 | List.concat [ 94 | [ [%stri let parse = fun value -> [%e parse_fn]] ]; 95 | (if rec_flag = Recursive then 96 | [{ 97 | pstr_desc = (Pstr_value (rec_flag, encoders |> Array.to_list)); 98 | pstr_loc = Location.none; 99 | }] 100 | else 101 | encoders 102 | |> Array.map (fun encoder -> { pstr_desc = (Pstr_value (Nonrecursive, [encoder])); pstr_loc = Location.none }) 103 | |> Array.to_list 104 | ); 105 | [ 106 | [%stri let make = [%e make_fn]]; 107 | [%stri let makeWithVariables = [%e make_with_variables_fn]]; 108 | ]; 109 | ]; 110 | ret_type_magic 111 | ] 112 | 113 | let generate_fragment_module config name _required_variables has_error fragment res_structure = 114 | let parse_fn = Output_bucklescript_decoder.generate_decoder config res_structure in 115 | let variable_names = find_variables config [Graphql_ast.Fragment fragment] |> StringSet.elements in 116 | let variable_fields = variable_names |> List.map (fun name -> 117 | (name, [], Ast_helper.Typ.constr { txt = Longident.Lident "unit"; loc = Location.none} [])) in 118 | let variable_obj_type = (Ast_helper.Typ.constr { txt = Longident.parse "Js.t"; loc = Location.none} [ 119 | (Ast_helper.Typ.object_ variable_fields Open) ]) in 120 | let contents = if has_error then 121 | [ [%stri let make = fun (_vars: [%t variable_obj_type]) value -> [%e parse_fn]] ] 122 | else 123 | List.concat [ 124 | make_printed_query config [Graphql_ast.Fragment fragment]; 125 | [ 126 | [%stri let parse = fun value -> [%e parse_fn]]; 127 | [%stri let name = [%e Ast_helper.Exp.constant (Const_string (name, None))]]; 128 | ]; 129 | ret_type_magic 130 | ] 131 | in 132 | let m = Pstr_module { 133 | pmb_name = { txt = Generator_utils.capitalize_ascii name; loc = Location.none }; 134 | pmb_expr = Mod.structure contents; 135 | pmb_attributes = []; 136 | pmb_loc = Location.none; 137 | } in 138 | [ { pstr_desc = m; pstr_loc = Location.none; } ] 139 | 140 | let generate_operation config = function 141 | | Mod_default_operation (vdefs, has_error, operation, structure) -> generate_default_operation config vdefs has_error operation structure 142 | | Mod_fragment (name, req_vars, has_error, fragment, structure) -> generate_fragment_module config name req_vars has_error fragment structure 143 | 144 | let generate_modules config operations = 145 | let generated = List.map (generate_operation config) operations in 146 | Mod.mk (Pmod_structure (List.concat generated)) 147 | -------------------------------------------------------------------------------- /src/bucklescript/output_bucklescript_unifier.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | open Graphql_ast 3 | open Source_pos 4 | open Generator_utils 5 | 6 | open Ast_402 7 | open Parsetree 8 | open Asttypes 9 | 10 | open Type_utils 11 | open Output_bucklescript_utils 12 | 13 | exception Unimplemented of string 14 | 15 | 16 | let make_make_fun config variable_defs = 17 | let make_make_triple loc variables = 18 | Ast_helper.Exp.extension ~loc:loc 19 | ({txt = "bs.obj"; loc = loc}, 20 | PStr [ 21 | [%stri { 22 | query = ppx_printed_query; 23 | variables = [%e variables]; 24 | parse = parse; }] [@metaloc loc] 25 | ]) in 26 | match variable_defs with 27 | | Some { item; span } -> begin 28 | let rec make_labelled_function defs body = match defs with 29 | | [] -> [%expr fun () -> [%e body]] [@metaloc config.map_loc span |> conv_loc] 30 | | (name, def) :: tl -> let name_loc = config.map_loc name.span |> conv_loc in 31 | Ast_helper.( 32 | Exp.fun_ 33 | ~loc:name_loc 34 | (match def.vd_type.item with 35 | | Tr_non_null_list _ | Tr_non_null_named _ -> name.item 36 | | Tr_list _ | Tr_named _ -> "?" ^ name.item) 37 | None 38 | (Pat.var ~loc:name_loc {txt=name.item; loc=name_loc}) 39 | (make_labelled_function tl body)) 40 | in 41 | let make_object_function defs body = 42 | let rec generate_bindings defs = match defs with 43 | | [] -> body 44 | | (name, _) :: tl -> let name_loc = config.map_loc name.span |> conv_loc in 45 | Ast_helper.Exp.let_ ~loc:name_loc Nonrecursive [ 46 | Ast_helper.(Vb.mk 47 | ~loc:name_loc 48 | (Pat.var ~loc:name_loc {txt=name.item; loc=name_loc}) 49 | [%expr variables##[%e Exp.ident {txt=Longident.Lident name.item; loc=name_loc}]]) 50 | ] (generate_bindings tl) 51 | in 52 | [%expr fun variables -> [%e generate_bindings defs]] 53 | in 54 | let make_var_ctor defs = 55 | defs 56 | |> List.map (fun (name, def) -> 57 | let parser_ = ( 58 | Output_bucklescript_encoder.parser_for_type config.schema (config.map_loc name.span) ( 59 | to_native_type_ref (to_schema_type_ref def.vd_type.item) 60 | ) 61 | ) in 62 | let loc = config.map_loc name.span |> conv_loc in 63 | [%expr 64 | ( 65 | [%e Ast_helper.Exp.constant ~loc (Const_string (name.item, None))], 66 | [%e parser_] [%e Ast_helper.Exp.ident ~loc {txt=Longident.parse name.item; loc}] 67 | )] [@metaloc loc] 68 | ) 69 | |> Ast_helper.Exp.array in 70 | let loc = config.map_loc span |> conv_loc in 71 | let variable_ctor_body = 72 | [%expr Js.Json.object_ (Js.Dict.fromArray [%e make_var_ctor item])] [@metaloc loc] 73 | in 74 | ( 75 | make_labelled_function item (make_make_triple loc variable_ctor_body), 76 | make_object_function item (make_make_triple loc variable_ctor_body) 77 | ) 78 | end 79 | | None -> begin 80 | ( 81 | [%expr fun () -> [%e make_make_triple Location.none [%expr Js.Json.null]]], 82 | [%expr fun (_: < > Js.t) -> [%e make_make_triple Location.none [%expr Js.Json.null]]] 83 | ) 84 | end 85 | -------------------------------------------------------------------------------- /src/bucklescript/output_bucklescript_utils.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | 3 | let conv_pos pos = 4 | { 5 | Lexing.pos_fname = pos.Source_pos.pos_fname; 6 | Lexing.pos_lnum = pos.Source_pos.pos_lnum; 7 | Lexing.pos_bol = pos.Source_pos.pos_bol; 8 | Lexing.pos_cnum = pos.Source_pos.pos_cnum; 9 | } 10 | 11 | let conv_loc loc = 12 | { 13 | Location.loc_start = conv_pos loc.Source_pos.loc_start; 14 | Location.loc_end = conv_pos loc.Source_pos.loc_end; 15 | Location.loc_ghost = loc.Source_pos.loc_ghost; 16 | } 17 | 18 | let conv_pos_from_ast pos = 19 | { 20 | Source_pos.pos_fname = pos.Lexing.pos_fname; 21 | Source_pos.pos_lnum = pos.Lexing.pos_lnum; 22 | Source_pos.pos_bol = pos.Lexing.pos_bol; 23 | Source_pos.pos_cnum = pos.Lexing.pos_cnum; 24 | } 25 | 26 | let conv_loc_from_ast loc = 27 | { 28 | Source_pos.loc_start = conv_pos_from_ast loc.Location.loc_start; 29 | Source_pos.loc_end = conv_pos_from_ast loc.Location.loc_end; 30 | Source_pos.loc_ghost = loc.Location.loc_ghost; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhallin/graphql_ppx/5796b3759bdf0d29112f48e43a2f0623f7466e8a/src/dune -------------------------------------------------------------------------------- /src/native/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name graphql_ppx) 3 | (public_name graphql_ppx) 4 | (wrapped false) 5 | (kind ppx_rewriter) 6 | (libraries graphql_ppx_base 7 | ppxlib) 8 | (preprocess (pps ppxlib.metaquot))) 9 | -------------------------------------------------------------------------------- /src/native/graphql_ppx.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | open Ppxlib 3 | 4 | open Source_pos 5 | 6 | open Output_native_utils 7 | 8 | let argv = Sys.argv |> Array.to_list 9 | 10 | let add_pos delimLength base pos = 11 | { 12 | pos_fname = base.pos_fname; 13 | pos_lnum = base.pos_lnum + pos.line; 14 | pos_bol = 0; 15 | pos_cnum = if pos.line = 0 then delimLength + pos.col else pos.col; 16 | } 17 | 18 | let add_loc delimLength base span = 19 | { 20 | loc_start = add_pos delimLength base.loc_start (fst span); 21 | loc_end = add_pos delimLength base.loc_start (snd span); 22 | loc_ghost = false; 23 | } 24 | 25 | let fmt_lex_err err = 26 | let open Graphql_lexer in 27 | match err with 28 | | Unknown_character ch -> Printf.sprintf "Unknown character %c" ch 29 | | Unexpected_character ch -> Printf.sprintf "Unexpected character %c" ch 30 | | Unterminated_string -> Printf.sprintf "Unterminated string literal" 31 | | Unknown_character_in_string ch -> Printf.sprintf "Unknown character in string literal: %c" ch 32 | | Unknown_escape_sequence s -> Printf.sprintf "Unknown escape sequence in string literal: %s" s 33 | | Unexpected_end_of_file -> Printf.sprintf "Unexpected end of query" 34 | | Invalid_number -> Printf.sprintf "Invalid number" 35 | 36 | let fmt_parse_err err = 37 | let open Graphql_parser in 38 | match err with 39 | | Unexpected_token t -> Printf.sprintf "Unexpected token %s" (Graphql_lexer.string_of_token t) 40 | | Unexpected_end_of_file -> "Unexpected end of query" 41 | | Lexer_error err -> fmt_lex_err err 42 | 43 | let is_prefixed prefix str = 44 | let i = 0 in 45 | let len = String.length prefix in 46 | let j = ref 0 in 47 | while !j < len && String.unsafe_get prefix !j = 48 | String.unsafe_get str (i + !j) do 49 | incr j 50 | done; 51 | (!j = len) 52 | 53 | let make_error_expr loc message = 54 | let ext = Location.Error.to_extension (Location.Error.createf ~loc "%s" message) in 55 | Ast_helper.Exp.extension ~loc ext 56 | 57 | let drop_prefix prefix str = 58 | let len = String.length prefix in 59 | let rest = (String.length str) - len in 60 | String.sub str len rest 61 | 62 | let () = Ppx_config.(set_config { 63 | verbose_logging = (match List.find ((=) "-verbose") argv with 64 | | _ -> true 65 | | exception Not_found -> false); 66 | output_mode = (match List.find ((=) "-ast-out") argv with 67 | | _ -> Ppx_config.Apollo_AST 68 | | exception Not_found -> Ppx_config.String); 69 | verbose_error_handling = (match List.find ((=) "-o") argv with 70 | | _ -> false 71 | | exception Not_found -> begin match Sys.getenv "NODE_ENV" with 72 | | "production" -> false 73 | | _ -> true 74 | | exception Not_found -> true 75 | end); 76 | apollo_mode = (match List.find ((=) "-apollo-mode") argv with 77 | | _ -> true 78 | | exception Not_found -> false); 79 | root_directory = Sys.getcwd (); 80 | schema_file = (match List.find (is_prefixed "-schema=") argv with 81 | | arg -> drop_prefix "-schema=" arg 82 | | exception Not_found -> "graphql_schema.json"); 83 | raise_error_with_loc = fun loc message -> 84 | let loc = conv_loc loc in 85 | raise (Location.Error (Location.Error.createf ~loc "%s" message)) 86 | }) 87 | 88 | let rewrite_query loc delim query = 89 | let lexer = Graphql_lexer.make query in 90 | let delimLength = match delim with | Some s -> 2 + String.length s | None -> 1 in 91 | match Graphql_lexer.consume lexer with 92 | | Result.Error e -> raise (Location.Error ( 93 | Location.Error.createf ~loc:(add_loc delimLength loc e.span |> conv_loc) "%s" (fmt_lex_err e.item) 94 | )) 95 | | Result.Ok tokens -> 96 | let parser = Graphql_parser.make tokens in 97 | match Graphql_parser_document.parse_document parser with 98 | | Result.Error e -> raise (Location.Error ( 99 | Location.Error.createf ~loc:(add_loc delimLength loc e.span |> conv_loc) "%s" (fmt_parse_err e.item) 100 | )) 101 | | Result.Ok document -> 102 | let config = { 103 | Generator_utils.map_loc = add_loc delimLength loc; 104 | delimiter = delim; 105 | full_document = document; 106 | (* the only call site of schema, make it lazy! *) 107 | schema = Lazy.force (Read_schema.get_schema ()); 108 | } in 109 | match Validations.run_validators config document with 110 | | Some errs -> 111 | Ast_helper.Mod.mk 112 | (Pmod_structure (List.map (fun (loc, msg) -> 113 | let loc = conv_loc loc in 114 | [%stri let _ = [%e make_error_expr loc msg]]) errs)) 115 | | None -> 116 | let parts = Result_decoder.unify_document_schema config document in 117 | Output_native_module.generate_modules config parts 118 | 119 | let rewrite ~loc ~path:_ expr = 120 | let open Parsetree in 121 | match expr with 122 | | PStr [{ pstr_desc = Pstr_eval ({ 123 | pexp_loc = loc; 124 | pexp_desc = Pexp_constant (Pconst_string (query, delim)); _ }, _); _ }] -> 125 | rewrite_query 126 | (conv_loc_from_ast loc) 127 | delim 128 | query 129 | | _ -> raise (Location.Error ( 130 | Location.Error.createf ~loc "[%%graphql] accepts a string, e.g. [%%graphql {| { query |}]" 131 | )) 132 | 133 | let ext = Extension.declare 134 | "graphql" 135 | Extension.Context.module_expr 136 | Ast_pattern.__ 137 | rewrite 138 | 139 | let () = Ppxlib.Driver.register_transformation ~extensions:[ext] "graphql" 140 | -------------------------------------------------------------------------------- /src/native/output_native_encoder.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | open Graphql_ast 3 | open Source_pos 4 | open Schema 5 | 6 | open Ppxlib 7 | open Asttypes 8 | 9 | open Type_utils 10 | open Generator_utils 11 | open Output_native_utils 12 | 13 | let mangle_enum_name = Generator_utils.uncapitalize_ascii 14 | 15 | let ident_from_string loc func_name = 16 | Ast_helper.(Exp.ident ~loc { txt = Longident.parse func_name; loc }) 17 | 18 | module StringSet = Set.Make(String) 19 | 20 | let sort_variable_types schema variables = 21 | let recursive_flag = ref false in 22 | let ordered_nodes = Queue.create () in 23 | let has_added_to_queue name = Queue.fold (fun acc (_, v) -> acc || name = v) false ordered_nodes in 24 | let rec loop visit_stack = function 25 | | [] -> () 26 | | (span, type_ref) :: tail -> 27 | let type_name = innermost_name type_ref in 28 | let () = match lookup_type schema type_name with 29 | | None -> () 30 | | Some _ when StringSet.mem type_name visit_stack -> recursive_flag := true 31 | | Some _ when has_added_to_queue type_name -> () 32 | | Some (Enum _) -> Queue.push (span, type_name) ordered_nodes 33 | | Some (InputObject io) -> 34 | let () = loop 35 | (StringSet.add type_name visit_stack) 36 | (List.map (fun { am_arg_type; _ } -> (span, am_arg_type)) io.iom_input_fields) in 37 | Queue.push (span, type_name) ordered_nodes 38 | | Some _ -> () 39 | in loop visit_stack tail 40 | in 41 | let () = loop StringSet.empty variables in 42 | let ordered_nodes = Array.init 43 | (Queue.length ordered_nodes) 44 | (fun _ -> 45 | let span, name = Queue.take ordered_nodes in 46 | (span, name |> lookup_type schema |> Option.unsafe_unwrap)) in 47 | (! recursive_flag, ordered_nodes) 48 | 49 | let function_name_string x = "json_of_" ^ Schema.extract_name_from_type_meta x 50 | 51 | let rec parser_for_type schema loc type_ref = 52 | let raise_inconsistent_schema type_name = raise_error_with_loc loc ("Inconsistent schema, type named " ^ type_name ^ " cannot be found") in 53 | match type_ref with 54 | | Ntr_list x -> 55 | let child_parser = parser_for_type schema loc x in 56 | let loc = conv_loc loc in 57 | [%expr fun v -> `List (Array.map [%e child_parser] v |> Array.to_list)] [@metaloc loc] 58 | | Ntr_nullable x -> 59 | let child_parser = parser_for_type schema loc x in 60 | let loc = conv_loc loc in 61 | [%expr fun v -> match v with None -> `Null | Some v -> [%e child_parser] v] [@metaloc loc] 62 | | Ntr_named type_name -> 63 | let loc = conv_loc loc in 64 | match lookup_type schema type_name with 65 | | None -> raise_inconsistent_schema type_name 66 | | Some (Scalar { sm_name = "String"; _ }) 67 | | Some (Scalar { sm_name = "ID"; _ }) -> [%expr fun v -> `String v] 68 | | Some (Scalar { sm_name = "Int"; _ }) -> [%expr fun v -> `Int v] 69 | | Some (Scalar { sm_name = "Float"; _ }) -> [%expr fun v -> `Float v] 70 | | Some (Scalar { sm_name = "Boolean"; _ }) -> [%expr fun v -> `Bool v] 71 | | Some (Scalar _) -> [%expr fun v -> v] 72 | | Some ty -> 73 | function_name_string ty |> ident_from_string loc 74 | 75 | let json_of_fields schema loc expr fields = 76 | let field_array_exprs = fields |> List.map 77 | (fun {am_name; am_arg_type; _} -> 78 | let type_ref = to_native_type_ref am_arg_type in 79 | let parser = parser_for_type schema loc type_ref in 80 | let loc = conv_loc loc in 81 | [%expr ( 82 | [%e Ast_helper.Exp.constant (Pconst_string (am_name, None)) ], 83 | [%e parser] ([%e Ast_helper.Exp.send expr { txt = am_name; loc }]) 84 | )] [@metaloc conv_loc loc]) in 85 | let field_array = Ast_helper.Exp.array field_array_exprs in 86 | let loc = conv_loc loc in 87 | [%expr `Assoc (Array.to_list [%e field_array])] [@metaloc loc] 88 | 89 | let generate_encoder config (spanning, x) = 90 | let loc = config.map_loc spanning.span in 91 | let body = match x with 92 | | Scalar _ -> raise_error_with_loc loc "Can not build variable encoder for scalar type" 93 | | Object _ -> raise @@ Invalid_argument "Unsupported variable type: Object" 94 | | Interface _ -> raise @@ Invalid_argument "Unsupported variable type: Interface" 95 | | Union _ -> raise @@ Invalid_argument "Unsupported variable type: Union" 96 | | Enum { em_values; _ } -> 97 | let loc = conv_loc loc in 98 | let match_arms = em_values |> List.map 99 | (fun { evm_name; _ } -> 100 | let pattern = Ast_helper.Pat.variant evm_name None in 101 | let expr = Ast_helper.Exp.constant (Pconst_string (evm_name, None)) in 102 | Ast_helper.Exp.case pattern [%expr `String [%e expr]]) in 103 | Ast_helper.Exp.match_ [%expr value] match_arms 104 | | InputObject { iom_input_fields; _ } -> 105 | let sub_value = let loc = conv_loc loc in [%expr value] in 106 | json_of_fields config.schema loc sub_value iom_input_fields 107 | in 108 | let loc = conv_loc loc in 109 | Ast_helper.Vb.mk ~loc (Ast_helper.Pat.var { txt = function_name_string x; loc }) [%expr fun value -> [%e body]] 110 | 111 | let generate_encoders config _loc = function 112 | | Some { item; _ } -> 113 | item 114 | |> List.map (fun (span, {vd_type = variable_type; _}) -> span, to_schema_type_ref variable_type.item) 115 | |> sort_variable_types config.schema 116 | |> (fun (is_recursive, types) -> (if is_recursive then Recursive else Nonrecursive), Array.map (generate_encoder config) types) 117 | | None -> (Nonrecursive, [||]) 118 | -------------------------------------------------------------------------------- /src/native/output_native_unifier.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | open Graphql_ast 3 | open Source_pos 4 | open Generator_utils 5 | 6 | open Ppxlib 7 | open Parsetree 8 | open Asttypes 9 | 10 | open Type_utils 11 | open Output_native_utils 12 | 13 | exception Unimplemented of string 14 | 15 | 16 | let make_make_fun config variable_defs = 17 | let make_make_triple loc variables = 18 | [%expr object 19 | method query = ppx_printed_query 20 | method variables = [%e variables] 21 | method parse = parse 22 | end] in 23 | let loc = Location.none in 24 | match variable_defs with 25 | | Some { item; span } -> begin 26 | let rec make_labelled_function defs body = match defs with 27 | | [] -> [%expr fun () -> [%e body]] [@metaloc config.map_loc span |> conv_loc] 28 | | (name, def) :: tl -> let name_loc = config.map_loc name.span |> conv_loc in 29 | Ast_helper.( 30 | Exp.fun_ 31 | ~loc:name_loc 32 | (match def.vd_type.item with 33 | | Tr_non_null_list _ | Tr_non_null_named _ -> Labelled name.item 34 | | Tr_list _ | Tr_named _ -> Optional name.item) 35 | None 36 | (Pat.var ~loc:name_loc {txt=name.item; loc=name_loc}) 37 | (make_labelled_function tl body)) 38 | in 39 | let make_object_function defs body = 40 | let rec generate_bindings defs = match defs with 41 | | [] -> body 42 | | (name, _) :: tl -> let name_loc = config.map_loc name.span |> conv_loc in 43 | Ast_helper.Exp.let_ ~loc:name_loc Nonrecursive [ 44 | Ast_helper.(Vb.mk 45 | ~loc:name_loc 46 | (Pat.var ~loc:name_loc {txt=name.item; loc=name_loc}) 47 | (Exp.send [%expr variables] {txt=name.item; loc=name_loc})) 48 | ] (generate_bindings tl) 49 | in 50 | [%expr fun variables -> [%e generate_bindings defs]] 51 | in 52 | let make_var_ctor defs = 53 | defs 54 | |> List.map (fun (name, def) -> 55 | let parser_ = ( 56 | Output_native_encoder.parser_for_type config.schema (config.map_loc name.span) ( 57 | to_native_type_ref (to_schema_type_ref def.vd_type.item) 58 | ) 59 | ) in 60 | let loc = config.map_loc name.span |> conv_loc in 61 | [%expr 62 | ( 63 | [%e Ast_helper.Exp.constant ~loc (Pconst_string (name.item, None))], 64 | [%e parser_] [%e Ast_helper.Exp.ident ~loc {txt=Longident.parse name.item; loc}] 65 | )] [@metaloc loc] 66 | ) 67 | |> Ast_helper.Exp.array in 68 | let loc = config.map_loc span |> conv_loc in 69 | let variable_ctor_body = 70 | [%expr `Assoc ([%e make_var_ctor item] |> Array.to_list)] [@metaloc loc] 71 | in 72 | ( 73 | make_labelled_function item (make_make_triple loc variable_ctor_body), 74 | make_object_function item (make_make_triple loc variable_ctor_body) 75 | ) 76 | end 77 | | None -> begin 78 | ( 79 | [%expr fun () -> [%e make_make_triple Location.none [%expr `Null]]], 80 | [%expr fun (_: < >) -> [%e make_make_triple Location.none [%expr `Null]]] 81 | ) 82 | end 83 | -------------------------------------------------------------------------------- /src/native/output_native_utils.ml: -------------------------------------------------------------------------------- 1 | open Graphql_ppx_base 2 | 3 | let conv_pos pos = 4 | { 5 | Lexing.pos_fname = pos.Source_pos.pos_fname; 6 | Lexing.pos_lnum = pos.Source_pos.pos_lnum; 7 | Lexing.pos_bol = pos.Source_pos.pos_bol; 8 | Lexing.pos_cnum = pos.Source_pos.pos_cnum; 9 | } 10 | 11 | let conv_loc loc = 12 | { 13 | Location.loc_start = conv_pos loc.Source_pos.loc_start; 14 | Location.loc_end = conv_pos loc.Source_pos.loc_end; 15 | Location.loc_ghost = loc.Source_pos.loc_ghost; 16 | } 17 | 18 | let conv_pos_from_ast pos = 19 | { 20 | Source_pos.pos_fname = pos.Lexing.pos_fname; 21 | Source_pos.pos_lnum = pos.Lexing.pos_lnum; 22 | Source_pos.pos_bol = pos.Lexing.pos_bol; 23 | Source_pos.pos_cnum = pos.Lexing.pos_cnum; 24 | } 25 | 26 | let conv_loc_from_ast loc = 27 | { 28 | Source_pos.loc_start = conv_pos_from_ast loc.Location.loc_start; 29 | Source_pos.loc_end = conv_pos_from_ast loc.Location.loc_end; 30 | Source_pos.loc_ghost = loc.Location.loc_ghost; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /tests_apollo/__tests__/astOutput.re: -------------------------------------------------------------------------------- 1 | open Jest; 2 | 3 | [@bs.val] [@bs.module "graphql-tag"] external graphqlTag: string => Js.Json.t = "default"; 4 | 5 | module Query1 = [%graphql {| 6 | { 7 | lists { 8 | nullableOfNullable 9 | nullableOfNonNullable 10 | nonNullableOfNullable 11 | nonNullableOfNonNullable 12 | } 13 | }|}]; 14 | 15 | let referenceQuery1 = graphqlTag({| 16 | { 17 | lists { 18 | nullableOfNullable 19 | nullableOfNonNullable 20 | nonNullableOfNullable 21 | nonNullableOfNonNullable 22 | } 23 | }|}); 24 | 25 | module Query2 = [%graphql {| 26 | query ( 27 | $nullableString: String, 28 | $string: String!, 29 | $nullableInt: Int, 30 | $int: Int!, 31 | $nullableFloat: Float, 32 | $float: Float!, 33 | $nullableBoolean: Boolean, 34 | $boolean: Boolean!, 35 | $nullableID: ID, 36 | $id: ID!, 37 | ) { 38 | scalarsInput(arg: { 39 | nullableString: $nullableString, 40 | string: $string, 41 | nullableInt: $nullableInt, 42 | int: $int, 43 | nullableFloat: $nullableFloat, 44 | float: $float, 45 | nullableBoolean: $nullableBoolean, 46 | boolean: $boolean, 47 | nullableID: $nullableID, 48 | id: $id, 49 | }) 50 | } 51 | |}]; 52 | 53 | let referenceQuery2 = graphqlTag({| 54 | query ( 55 | $nullableString: String, 56 | $string: String!, 57 | $nullableInt: Int, 58 | $int: Int!, 59 | $nullableFloat: Float, 60 | $float: Float!, 61 | $nullableBoolean: Boolean, 62 | $boolean: Boolean!, 63 | $nullableID: ID, 64 | $id: ID!, 65 | ) { 66 | scalarsInput(arg: { 67 | nullableString: $nullableString, 68 | string: $string, 69 | nullableInt: $nullableInt, 70 | int: $int, 71 | nullableFloat: $nullableFloat, 72 | float: $float, 73 | nullableBoolean: $nullableBoolean, 74 | boolean: $boolean, 75 | nullableID: $nullableID, 76 | id: $id, 77 | }) 78 | }|}); 79 | 80 | describe("AST Parsing", () => { 81 | open Expect; 82 | open! Expect.Operators; 83 | 84 | let defs = x => x 85 | |. Js.Json.decodeObject 86 | |. Js.Option.getExn 87 | |. Js.Dict.get("definitions") 88 | |. Js.Option.getExn; 89 | 90 | test("Simple query's definitions match Apollo's", () => 91 | expect(defs(Query1.query)) == defs(referenceQuery1)); 92 | 93 | test("Complex query's definitions match Apollo's", () => 94 | expect(defs(Query2.query)) == defs(referenceQuery2)); 95 | }); -------------------------------------------------------------------------------- /tests_apollo/bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql_ppx_test", 3 | "sources": [ 4 | "__tests__" 5 | ], 6 | "ppx-flags": [ 7 | "../graphql_ppx.exe -ast-out" 8 | ], 9 | "bs-dependencies": [ 10 | "@glennsl/bs-jest" 11 | ], 12 | "bsc-flags": [ "-bs-super-errors" ], 13 | "refmt": 3 14 | } 15 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/apolloMode.re: -------------------------------------------------------------------------------- 1 | 2 | module MyQuery = [%graphql {| 3 | { 4 | first: nestedObject { 5 | inner { 6 | inner { 7 | field 8 | } 9 | } 10 | } 11 | 12 | second: nestedObject { 13 | inner { 14 | inner { 15 | f1: field 16 | f2: field 17 | } 18 | } 19 | } 20 | } 21 | |}]; 22 | 23 | Jest.(describe("Apollo mode", () => { 24 | open Expect; 25 | open! Expect.Operators; 26 | 27 | test("Adds __typename to objects", () => { 28 | let typenameRegex = [%bs.re {|/__typename/g|}]; 29 | Js.log(MyQuery.query) 30 | MyQuery.query 31 | |> Js.String.match(typenameRegex) 32 | |> Belt.Option.map(_, Array.length) 33 | |> expect == Some(7); 34 | }); 35 | })); 36 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/apolloMode.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | first: Js.t({. inner: option(Js.t({. inner : option(Js.t({. field : string})) })) }), 5 | second: Js.t({. inner : option(Js.t({. inner : option(Js.t({. f1: string, f2: string})) })) }), 6 | }); 7 | 8 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 9 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 10 | 11 | let query: string; 12 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/argNamedQuery.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($query: String!) { 3 | argNamedQuery(query: $query) 4 | } 5 | |}]; 6 | 7 | Jest.(describe("Argument named 'query'", () => { 8 | open Expect; 9 | open! Expect.Operators; 10 | 11 | test("Serializes variables", () => 12 | expect(MyQuery.make(~query="a query", ())##variables) 13 | == Js.Json.parseExn({|{"query": "a query"}|})); 14 | 15 | test("No name clash with the query field", () => 16 | expect(MyQuery.make(~query="a query", ())##query) != "a query") 17 | })) 18 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/argNamedQuery.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. argNamedQuery: int }); 3 | 4 | let make: (~query: string, unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 5 | let makeWithVariables: Js.t({. query: string}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 6 | let query: string; 7 | }; 8 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/comment.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($arg: NonrecursiveInput!) { 3 | nonrecursiveInput(arg: $arg) # comment to test 4 | } 5 | |}]; 6 | 7 | Jest.(describe("Comment in query", () => { 8 | open Expect; 9 | open! Expect.Operators; 10 | 11 | test("Constructs with comment in query", () => 12 | expect(MyQuery.make( 13 | ~arg={ 14 | "field": Some("test"), 15 | "enum": Some(`SECOND), 16 | }, 17 | ())##variables) 18 | == Js.Json.parseExn({| { 19 | "arg": { 20 | "field": "test", 21 | "enum": "SECOND" 22 | } 23 | } |})); 24 | })); 25 | 26 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/customDecoder.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | variousScalars { 4 | string @bsDecoder(fn: "int_of_string") 5 | int @bsDecoder(fn: "string_of_int") 6 | } 7 | } 8 | |}]; 9 | 10 | Jest.(describe("Custom decoders", () => { 11 | open Expect; 12 | open! Expect.Operators; 13 | 14 | test("Runs the decoder", () => 15 | expect(MyQuery.parse(Js.Json.parseExn({|{"variousScalars": {"string": "123", "int": 456}}|}))) 16 | == {"variousScalars": {"string": 123, "int": "456"}}) 17 | })); 18 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/customDecoder.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. variousScalars: Js.t({. string: int, int: string})}); 3 | 4 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 5 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 6 | let query: string; 7 | }; 8 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/customScalars.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($opt: CustomScalar, $req: CustomScalar!) { 3 | customScalarField(argOptional: $opt, argRequired: $req) { 4 | nullable 5 | nonNullable 6 | } 7 | } 8 | |}]; 9 | 10 | Jest.(describe("Custom scalars", () => { 11 | open Expect; 12 | open! Expect.Operators; 13 | 14 | test("Encodes custom scalar variables as Json objects", () => 15 | expect(MyQuery.make( 16 | ~opt=Js.Json.number(123.), 17 | ~req=Js.Json.number(456.), 18 | ())##variables) 19 | == Js.Json.parseExn({| 20 | { 21 | "opt": 123, 22 | "req": 456 23 | } 24 | |})); 25 | 26 | test("Encodes nullable scalar variables as optional Json objects", () => 27 | expect(MyQuery.makeWithVariables({ 28 | "opt": Some(Js.Json.number(123.)), 29 | "req": Js.Json.number(456.), 30 | })##variables) 31 | == Js.Json.parseExn({| 32 | { 33 | "opt": 123, 34 | "req": 456 35 | } 36 | |})); 37 | 38 | test("Decodes results to JSON", () => 39 | expect(MyQuery.parse(Js.Json.parseExn({|{"customScalarField": { "nullable": 123, "nonNullable": 456 }}|}))) 40 | == {"customScalarField": { 41 | "nullable": Some(Js.Json.number(123.)), 42 | "nonNullable": Js.Json.number(456.), 43 | }}) 44 | })); 45 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/enumInput.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($arg: SampleField!) { 3 | enumInput(arg: $arg) 4 | } 5 | |}]; 6 | 7 | Jest.(describe("Enum arguments", () => { 8 | open Expect; 9 | open! Expect.Operators; 10 | 11 | test("Encodes enum arguments to strings", () => 12 | expect(MyQuery.make(~arg=`FIRST, ())##variables) 13 | == Js.Json.parseExn({| { "arg": "FIRST" } |})) 14 | })); 15 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/enumInput.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | enumInput: string, 5 | }); 6 | 7 | let make: ( 8 | ~arg: [ | `FIRST | `SECOND | `THIRD], 9 | unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 10 | let makeWithVariables: Js.t({ 11 | . 12 | arg: [ | `FIRST | `SECOND | `THIRD], 13 | }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 14 | 15 | let query: string; 16 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/fragmentDefinition.re: -------------------------------------------------------------------------------- 1 | module Fragments = [%graphql {| 2 | fragment listFragment on Lists { 3 | nullableOfNullable 4 | nullableOfNonNullable 5 | } 6 | |}]; 7 | 8 | module MyQuery = [%graphql {| 9 | query { 10 | l1: lists { 11 | ...Fragments.ListFragment 12 | } 13 | 14 | l2: lists { 15 | ...Fragments.ListFragment @bsField(name: "frag1") 16 | ...Fragments.ListFragment @bsField(name: "frag2") 17 | } 18 | } 19 | |}]; 20 | 21 | Jest.(describe("Fragment definition", () => { 22 | open Expect; 23 | open! Expect.Operators; 24 | 25 | test("Decodes the fragment", () => 26 | expect(MyQuery.parse(Js.Json.parseExn({| 27 | { 28 | "l1": {"nullableOfNullable": ["a", null, "b"]}, 29 | "l2": {"nullableOfNullable": ["a", null, "b"]} 30 | }|}))) 31 | == { 32 | "l1": { 33 | "nullableOfNullable": Some([|Some("a"), None, Some("b")|]), 34 | "nullableOfNonNullable": None, 35 | }, 36 | "l2": { 37 | "frag1": { 38 | "nullableOfNullable": Some([|Some("a"), None, Some("b")|]), 39 | "nullableOfNonNullable": None, 40 | }, 41 | "frag2": { 42 | "nullableOfNullable": Some([|Some("a"), None, Some("b")|]), 43 | "nullableOfNonNullable": None, 44 | }, 45 | }, 46 | }); 47 | })); 48 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/fragmentDefinition.rei: -------------------------------------------------------------------------------- 1 | module Fragments: { 2 | module ListFragment: { 3 | type t = Js.t({ 4 | . 5 | nullableOfNullable : option(array(option(string))), 6 | nullableOfNonNullable : option(array(string)), 7 | }); 8 | 9 | let query: string; 10 | let name: string; 11 | let parse: Js.Json.t => t; 12 | }; 13 | }; 14 | 15 | module MyQuery: { 16 | type t = Js.t({. 17 | l1: Js.t({ 18 | . 19 | nullableOfNullable : option(array(option(string))), 20 | nullableOfNonNullable : option(array(string)), 21 | }), 22 | l2: Js.t({ 23 | . 24 | frag1: Js.t({ 25 | . 26 | nullableOfNullable : option(array(option(string))), 27 | nullableOfNonNullable : option(array(string)), 28 | }), 29 | frag2: Js.t({ 30 | . 31 | nullableOfNullable : option(array(option(string))), 32 | nullableOfNonNullable : option(array(string)), 33 | }), 34 | }), 35 | }); 36 | 37 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 38 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 39 | } -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/interface.re: -------------------------------------------------------------------------------- 1 | module QueryWithFragments = [%graphql 2 | {| 3 | query { 4 | users { 5 | id 6 | ... on AdminUser { 7 | name 8 | } 9 | ... on AnonymousUser { 10 | anonymousId 11 | } 12 | } 13 | } 14 | |} 15 | ]; 16 | 17 | module QueryWithoutFragments = [%graphql 18 | {| 19 | query { 20 | users { 21 | id 22 | } 23 | } 24 | |} 25 | ]; 26 | 27 | let json = {|{ 28 | "users": [ 29 | { "__typename": "AdminUser", "id": "1", "name": "bob" }, 30 | { "__typename": "AnonymousUser", "id": "2", "anonymousId": 1}, 31 | { "__typename": "OtherUser", "id": "3"} 32 | ]}|}; 33 | 34 | Jest.( 35 | describe("Interface definition", () => { 36 | open Expect; 37 | open! Expect.Operators; 38 | 39 | test("Decodes the interface with fragments ", () => 40 | expect(QueryWithFragments.parse(Js.Json.parseExn(json))) 41 | == { 42 | "users": [| 43 | `AdminUser({"id": "1", "name": "bob"}), 44 | `AnonymousUser({"id": "2", "anonymousId": 1}), 45 | `User({"id": "3"}), 46 | |], 47 | } 48 | ); 49 | 50 | test("Decodes the interface without fragments ", () => 51 | expect(QueryWithoutFragments.parse(Js.Json.parseExn(json))) 52 | == { 53 | "users": [| 54 | `User({"id": "1"}), 55 | `User({"id": "2"}), 56 | `User({"id": "3"}), 57 | |], 58 | } 59 | ); 60 | }) 61 | ); -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/interface.rei: -------------------------------------------------------------------------------- 1 | module QueryWithFragments: { 2 | type t = Js.t({ 3 | . 4 | users: array([ 5 | | `User(Js.t({ . id: string })) 6 | | `AdminUser(Js.t({ . id: string, name: string })) 7 | | `AnonymousUser(Js.t({ . id: string, anonymousId: int })) 8 | ]) 9 | }); 10 | 11 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 12 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 13 | let query: string; 14 | }; 15 | 16 | module QueryWithoutFragments: { 17 | type t = Js.t({ 18 | . 19 | users: array([ 20 | | `User(Js.t({ . id: string })) 21 | ]) 22 | }); 23 | 24 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 25 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 26 | let query: string; 27 | }; 28 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/lists.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | lists { 4 | nullableOfNullable 5 | nullableOfNonNullable 6 | nonNullableOfNullable 7 | nonNullableOfNonNullable 8 | } 9 | } 10 | |}]; 11 | 12 | Jest.(describe("Lists", () => { 13 | open Expect; 14 | open! Expect.Operators; 15 | 16 | test("Null in nullable lists", () => 17 | expect(MyQuery.parse(Js.Json.parseExn({|{"lists": {"nullableOfNullable": [null, "123"], "nonNullableOfNullable": [null, "123"], "nonNullableOfNonNullable": ["a", "b"]}}|}))) 18 | == {"lists": { 19 | "nullableOfNullable": Some([|None, Some("123")|]), 20 | "nullableOfNonNullable": None, 21 | "nonNullableOfNullable": [|None, Some("123")|], 22 | "nonNullableOfNonNullable": [|"a", "b"|] 23 | }}) 24 | })); 25 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/lists.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | lists : Js.t({ 5 | . 6 | nullableOfNullable : option(array(option(string))), 7 | nullableOfNonNullable : option(array(string)), 8 | nonNullableOfNullable : array(option(string)), 9 | nonNullableOfNonNullable : array(string), 10 | }) 11 | }); 12 | 13 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 14 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 15 | let query: string; 16 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/listsArgs.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql 2 | {| 3 | query ( 4 | $nullableOfNullable: [String], 5 | $nullableOfNonNullable: [String!], 6 | $nonNullableOfNullable: [String]!, 7 | $nonNullableOfNonNullable: [String!]!, 8 | ) { 9 | listsInput(arg: { 10 | nullableOfNullable: $nullableOfNullable, 11 | nullableOfNonNullable: $nullableOfNonNullable, 12 | nonNullableOfNullable: $nonNullableOfNullable, 13 | nonNullableOfNonNullable: $nonNullableOfNonNullable, 14 | }) 15 | } 16 | |} 17 | ]; 18 | 19 | Jest.(describe("Lists as query arguments", () => { 20 | open Expect; 21 | open! Expect.Operators; 22 | 23 | test("Allows you to omit nullable arguments", () => 24 | expect(MyQuery.make( 25 | ~nonNullableOfNullable=[||], 26 | ~nonNullableOfNonNullable=[||], 27 | ())##variables) 28 | == Js.Json.parseExn({| 29 | { 30 | "nullableOfNullable": null, 31 | "nullableOfNonNullable": null, 32 | "nonNullableOfNullable": [], 33 | "nonNullableOfNonNullable": [] 34 | } 35 | |})); 36 | 37 | test("Allows None in lists of nullable types", () => 38 | expect(MyQuery.make( 39 | ~nullableOfNullable=[|Some("x"), None, Some("y")|], 40 | ~nonNullableOfNullable=[|Some("a"), None, Some("b")|], 41 | ~nonNullableOfNonNullable=[|"1", "2", "3"|], 42 | ())##variables) 43 | == Js.Json.parseExn({| 44 | { 45 | "nullableOfNullable": ["x", null, "y"], 46 | "nullableOfNonNullable": null, 47 | "nonNullableOfNullable": ["a", null, "b"], 48 | "nonNullableOfNonNullable": ["1", "2", "3"] 49 | } 50 | |})); 51 | })); 52 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/listsArgs.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. listsInput : string}); 3 | 4 | let make: ( 5 | ~nullableOfNullable: array(option(string))=?, 6 | ~nullableOfNonNullable: array(string)=?, 7 | ~nonNullableOfNullable: array(option(string)), 8 | ~nonNullableOfNonNullable: array(string), 9 | unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 10 | let makeWithVariables: 11 | Js.t({ 12 | . 13 | nullableOfNullable: option(array(option(string))), 14 | nullableOfNonNullable: option(array(string)), 15 | nonNullableOfNullable: array(option(string)), 16 | nonNullableOfNonNullable: array(string) 17 | }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 18 | let query: string; 19 | }; 20 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/listsInput.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($arg: ListsInput!) { 3 | listsInput(arg: $arg) 4 | } 5 | |}]; 6 | 7 | Jest.(describe("Lists as query arguments through input object", () => { 8 | open Expect; 9 | open! Expect.Operators; 10 | 11 | test("Allows None in lists of nullable types", () => 12 | expect(MyQuery.make( 13 | ~arg={ 14 | "nullableOfNullable": Some([|Some("x"), None, Some("y")|]), 15 | "nullableOfNonNullable": None, 16 | "nonNullableOfNullable": [|Some("a"), None, Some("b")|], 17 | "nonNullableOfNonNullable": [|"1", "2", "3"|], 18 | }, 19 | ())##variables) 20 | == Js.Json.parseExn({| 21 | { 22 | "arg": { 23 | "nullableOfNullable": ["x", null, "y"], 24 | "nullableOfNonNullable": null, 25 | "nonNullableOfNullable": ["a", null, "b"], 26 | "nonNullableOfNonNullable": ["1", "2", "3"] 27 | } 28 | } 29 | |})); 30 | })); 31 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/listsInput.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. listsInput: string}); 3 | 4 | let make: ( 5 | ~arg: Js.t ({ 6 | .. 7 | nullableOfNullable: option(array(option(string))), 8 | nullableOfNonNullable: option(array(string)), 9 | nonNullableOfNullable: array(option(string)), 10 | nonNullableOfNonNullable: array(string), 11 | }), 12 | unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 13 | 14 | let makeWithVariables: 15 | Js.t({ 16 | . 17 | arg: Js.t({ 18 | . 19 | nullableOfNullable: option(array(option(string))), 20 | nullableOfNonNullable: option(array(string)), 21 | nonNullableOfNullable: array(option(string)), 22 | nonNullableOfNonNullable: array(string), 23 | }), 24 | }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 25 | 26 | let query: string; 27 | }; 28 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/mutation.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | mutation { 3 | mutationWithError { 4 | value { 5 | stringField 6 | } 7 | 8 | errors { 9 | field 10 | message 11 | } 12 | } 13 | } 14 | |}]; 15 | 16 | Jest.(describe("Mutation", () => { 17 | open Expect; 18 | open! Expect.Operators; 19 | 20 | test("Printed query is a mutation", () => 21 | expect(MyQuery.query |> Js.String.indexOf("mutation")) == 0); 22 | })); 23 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/mutation.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | mutationWithError: Js.t({ . 5 | value: option(Js.t({. stringField: string})), 6 | errors: option(array(Js.t({. field: [ | `FIRST | `SECOND | `THIRD], message: string}))), 7 | }), 8 | }); 9 | 10 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 11 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 12 | 13 | let query: string; 14 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/nested.re: -------------------------------------------------------------------------------- 1 | type record = {f1: string, f2: string}; 2 | 3 | module MyQuery = [%graphql {| 4 | { 5 | first: nestedObject { 6 | inner { 7 | inner { 8 | field 9 | } 10 | } 11 | } 12 | 13 | second: nestedObject { 14 | inner { 15 | inner @bsRecord { 16 | f1: field 17 | f2: field 18 | } 19 | } 20 | } 21 | } 22 | |}]; 23 | 24 | Jest.(describe("Nested", () => { 25 | open Expect; 26 | open! Expect.Operators; 27 | 28 | test("Decodes recursively", () => 29 | expect(MyQuery.parse(Js.Json.parseExn({| 30 | {"first": {"inner": {"inner": {"field": "second"}}}, 31 | "second": {"inner": null}} 32 | |}))) == { 33 | "first": {"inner": Some({"inner": Some({"field": "second"})})}, 34 | "second": {"inner": None} 35 | }); 36 | })); 37 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/nested.rei: -------------------------------------------------------------------------------- 1 | type record = { 2 | f1: string, 3 | f2: string 4 | }; 5 | 6 | module MyQuery: { 7 | type t = Js.t({ 8 | . 9 | first: Js.t({. inner: option(Js.t({. inner : option(Js.t({. field : string})) })) }), 10 | second: Js.t({. inner : option(Js.t({. inner : option(record) })) }), 11 | }); 12 | 13 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 14 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 15 | 16 | let query: string; 17 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/nonrecursiveInput.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($arg: NonrecursiveInput!) { 3 | nonrecursiveInput(arg: $arg) 4 | } 5 | |}]; 6 | 7 | Jest.(describe("Recursive input types", () => { 8 | open Expect; 9 | open! Expect.Operators; 10 | 11 | test("Constructing a recursive input type", () => 12 | expect(MyQuery.make( 13 | ~arg={ 14 | "field": Some("test"), 15 | "enum": Some(`SECOND), 16 | }, 17 | ())##variables) 18 | == Js.Json.parseExn({| { 19 | "arg": { 20 | "field": "test", 21 | "enum": "SECOND" 22 | } 23 | } |})); 24 | })); 25 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/nonrecursiveInput.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. nonrecursiveInput: string}); 3 | 4 | let make: ( 5 | ~arg: Js.t({ . enum: option([ | `FIRST | `SECOND | `THIRD ]), field: option(string) }), 6 | unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 7 | let makeWithVariables: Js.t({ 8 | . 9 | arg: Js.t({ . enum: option([ | `FIRST | `SECOND | `THIRD ]), field: option(string) }) as 'a, 10 | }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 11 | 12 | let query: string; 13 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/record.re: -------------------------------------------------------------------------------- 1 | type scalars = { 2 | string: string, 3 | int: int 4 | }; 5 | 6 | type dog = { 7 | name: string, 8 | barkVolume: float 9 | }; 10 | 11 | module MyQuery = [%graphql {| 12 | { 13 | variousScalars @bsRecord { 14 | string 15 | int 16 | } 17 | } 18 | |}]; 19 | 20 | module ExternalFragmentQuery = [%graphql {| 21 | fragment Fragment on VariousScalars @bsRecord { 22 | string 23 | int 24 | } 25 | 26 | { 27 | variousScalars { 28 | ...Fragment 29 | } 30 | } 31 | |}]; 32 | 33 | module InlineFragmentQuery = [%graphql {| 34 | { 35 | dogOrHuman { 36 | ...on Dog @bsRecord { 37 | name 38 | barkVolume 39 | } 40 | } 41 | } 42 | |}]; 43 | 44 | module UnionExternalFragmentQuery = [%graphql {| 45 | fragment DogFragment on Dog @bsRecord { 46 | name 47 | barkVolume 48 | } 49 | 50 | { 51 | dogOrHuman { 52 | ...on Dog { 53 | ...DogFragment 54 | } 55 | } 56 | } 57 | |}]; 58 | 59 | Jest.(describe("Record", () => { 60 | open Expect; 61 | open! Expect.Operators; 62 | 63 | test("Decodes a record in a selection", () => { 64 | let expected = {string: "a string", int: 123}; 65 | expect(MyQuery.parse(Js.Json.parseExn({|{"variousScalars": {"string": "a string", "int": 123}}|}))) 66 | == {"variousScalars": expected}; 67 | }); 68 | 69 | test("Decodes a record in an external fragment", () => { 70 | let expected = {string: "a string", int: 123}; 71 | expect(ExternalFragmentQuery.parse(Js.Json.parseExn({|{"variousScalars": {"string": "a string", "int": 123}}|}))) 72 | == {"variousScalars": expected}; 73 | }); 74 | 75 | test("Decodes a record in an inline fragment", () => { 76 | let expected = `Dog({name: "name", barkVolume: 123.0}); 77 | expect(InlineFragmentQuery.parse(Js.Json.parseExn({|{"dogOrHuman": {"__typename": "Dog", "name": "name", "barkVolume": 123}}|}))) 78 | == {"dogOrHuman": expected}; 79 | }); 80 | 81 | test("Decodes a record in an external fragment on union selections", () => { 82 | let expected = `Dog({name: "name", barkVolume: 123.0}); 83 | expect(UnionExternalFragmentQuery.parse(Js.Json.parseExn({|{"dogOrHuman": {"__typename": "Dog", "name": "name", "barkVolume": 123}}|}))) 84 | == {"dogOrHuman": expected}; 85 | }); 86 | })); 87 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/record.rei: -------------------------------------------------------------------------------- 1 | type scalars = { 2 | string, 3 | int 4 | }; 5 | 6 | module MyQuery: { 7 | type t = Js.t({. variousScalars: scalars}); 8 | 9 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 10 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 11 | 12 | let query: string; 13 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/recursiveInput.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($arg: RecursiveInput!) { 3 | recursiveInput(arg: $arg) 4 | } 5 | |}]; 6 | 7 | Jest.(describe("Recursive input types", () => { 8 | open Expect; 9 | open! Expect.Operators; 10 | 11 | test("Constructing a recursive input type", () => 12 | expect(MyQuery.make( 13 | ~arg={ 14 | "otherField": Some("test"), 15 | "enum": None, 16 | "inner": Some({ 17 | "otherField": Some("inner"), 18 | "inner": None, 19 | "enum": Some(`SECOND), 20 | }) 21 | }, 22 | ())##variables) 23 | == Js.Json.parseExn({| { 24 | "arg": { 25 | "otherField": "test", 26 | "enum": null, 27 | "inner": { 28 | "otherField": "inner", 29 | "inner": null, 30 | "enum": "SECOND" 31 | } 32 | } 33 | } |})); 34 | })); 35 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/recursiveInput.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. recursiveInput: string}); 3 | 4 | let make: ( 5 | ~arg: Js.t({ . inner: option('a), enum: option([ | `FIRST | `SECOND | `THIRD ]), otherField: option(string) }) as 'a, 6 | unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 7 | let makeWithVariables: Js.t({ 8 | . 9 | arg: Js.t({ . inner: option('a), enum: option([ | `FIRST | `SECOND | `THIRD ]), otherField: option(string) }) as 'a, 10 | }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 11 | 12 | let query: string; 13 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/scalars.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | variousScalars { 4 | nullableString 5 | string 6 | nullableInt 7 | int 8 | nullableFloat 9 | float 10 | nullableBoolean 11 | boolean 12 | nullableID 13 | id 14 | } 15 | } 16 | |}]; 17 | 18 | Jest.(describe("Scalars", () => { 19 | open Expect; 20 | open! Expect.Operators; 21 | 22 | test("Decodes non-null scalars", () => 23 | expect(MyQuery.parse(Js.Json.parseExn({| { 24 | "variousScalars": { 25 | "nullableString": "a nullable string", 26 | "string": "a string", 27 | "nullableInt": 456, 28 | "int": 123, 29 | "nullableFloat": 678.5, 30 | "float": 1234.5, 31 | "nullableBoolean": false, 32 | "boolean": true, 33 | "nullableID": "a nullable ID", 34 | "id": "an ID" 35 | } 36 | } |}))) == { 37 | "variousScalars": { 38 | "nullableString": Some("a nullable string"), 39 | "string": "a string", 40 | "nullableInt": Some(456), 41 | "int": 123, 42 | "nullableFloat": Some(678.5), 43 | "float": 1234.5, 44 | "nullableBoolean": Some(false), 45 | "boolean": true, 46 | "nullableID": Some("a nullable ID"), 47 | "id": "an ID", 48 | } 49 | }); 50 | 51 | test("Decodes null scalars", () => 52 | expect(MyQuery.parse(Js.Json.parseExn({| { 53 | "variousScalars": { 54 | "nullableString": null, 55 | "string": "a string", 56 | "nullableInt": null, 57 | "int": 123, 58 | "nullableFloat": null, 59 | "float": 1234.5, 60 | "nullableBoolean": null, 61 | "boolean": true, 62 | "nullableID": null, 63 | "id": "an ID" 64 | } 65 | } |}))) == { 66 | "variousScalars": { 67 | "nullableString": None, 68 | "string": "a string", 69 | "nullableInt": None, 70 | "int": 123, 71 | "nullableFloat": None, 72 | "float": 1234.5, 73 | "nullableBoolean": None, 74 | "boolean": true, 75 | "nullableID": None, 76 | "id": "an ID", 77 | } 78 | }); 79 | 80 | test("Decodes omitted scalars", () => 81 | expect(MyQuery.parse(Js.Json.parseExn({| { 82 | "variousScalars": { 83 | "string": "a string", 84 | "int": 123, 85 | "float": 1234.5, 86 | "boolean": true, 87 | "id": "an ID" 88 | } 89 | } |}))) == { 90 | "variousScalars": { 91 | "nullableString": None, 92 | "string": "a string", 93 | "nullableInt": None, 94 | "int": 123, 95 | "nullableFloat": None, 96 | "float": 1234.5, 97 | "nullableBoolean": None, 98 | "boolean": true, 99 | "nullableID": None, 100 | "id": "an ID", 101 | } 102 | }); 103 | })); 104 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/scalars.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | variousScalars : Js.t({ 5 | . 6 | nullableString : option(string), 7 | string: string, 8 | nullableInt: option(int), 9 | int: int, 10 | nullableFloat: option(float), 11 | float: float, 12 | nullableBoolean: option(bool), 13 | boolean: bool, 14 | nullableID: option(string), 15 | id: string, 16 | }) 17 | }); 18 | 19 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 20 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 21 | 22 | let query: string; 23 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/scalarsArgs.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ( 3 | $nullableString: String, 4 | $string: String!, 5 | $nullableInt: Int, 6 | $int: Int!, 7 | $nullableFloat: Float, 8 | $float: Float!, 9 | $nullableBoolean: Boolean, 10 | $boolean: Boolean!, 11 | $nullableID: ID, 12 | $id: ID!, 13 | ) { 14 | scalarsInput(arg: { 15 | nullableString: $nullableString, 16 | string: $string, 17 | nullableInt: $nullableInt, 18 | int: $int, 19 | nullableFloat: $nullableFloat, 20 | float: $float, 21 | nullableBoolean: $nullableBoolean, 22 | boolean: $boolean, 23 | nullableID: $nullableID, 24 | id: $id, 25 | }) 26 | } 27 | |}]; 28 | 29 | Jest.(describe("Scalars as arguments", () => { 30 | open Expect; 31 | open! Expect.Operators; 32 | 33 | test("Allows you to omit nullable arguments", () => 34 | expect(MyQuery.make( 35 | ~string="a string", 36 | ~int=123, 37 | ~float=1234.5, 38 | ~boolean=true, 39 | ~id="an ID", 40 | ())##variables) 41 | == Js.Json.parseExn({| { 42 | "nullableString": null, 43 | "string": "a string", 44 | "nullableInt": null, 45 | "int": 123, 46 | "nullableFloat": null, 47 | "float": 1234.5, 48 | "nullableBoolean": null, 49 | "boolean": true, 50 | "nullableID": null, 51 | "id": "an ID" 52 | } |})); 53 | 54 | test("Includes non-nulled arguments", () => 55 | expect(MyQuery.make( 56 | ~nullableString="a nullable string", 57 | ~string="a string", 58 | ~nullableInt=456, 59 | ~int=123, 60 | ~nullableFloat=567.5, 61 | ~float=1234.5, 62 | ~nullableBoolean=false, 63 | ~boolean=true, 64 | ~nullableID="a nullable ID", 65 | ~id="an ID", 66 | ())##variables) 67 | == Js.Json.parseExn({| { 68 | "nullableString": "a nullable string", 69 | "string": "a string", 70 | "nullableInt": 456, 71 | "int": 123, 72 | "nullableFloat": 567.5, 73 | "float": 1234.5, 74 | "nullableBoolean": false, 75 | "boolean": true, 76 | "nullableID": "a nullable ID", 77 | "id": "an ID" 78 | } |})); 79 | 80 | })); 81 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/scalarsArgs.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. scalarsInput: string}); 3 | 4 | let make: ( 5 | ~nullableString: string=?, 6 | ~string: string, 7 | ~nullableInt: int=?, 8 | ~int: int, 9 | ~nullableFloat: float=?, 10 | ~float: float, 11 | ~nullableBoolean: bool=?, 12 | ~boolean: bool, 13 | ~nullableID: string=?, 14 | ~id: string, 15 | unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 16 | 17 | let makeWithVariables: 18 | Js.t({ 19 | . 20 | nullableString: option(string), 21 | string: string, 22 | nullableInt: option(int), 23 | int: int, 24 | nullableFloat: option(float), 25 | float: float, 26 | nullableBoolean: option(bool), 27 | boolean: bool, 28 | nullableID: option(string), 29 | id: string, 30 | }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 31 | 32 | let query: string; 33 | }; 34 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/scalarsInput.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | query ($arg: VariousScalarsInput!) { 3 | scalarsInput(arg: $arg) 4 | } 5 | |}]; 6 | 7 | Jest.(describe("Scalars as arguments through an input object", () => { 8 | open Expect; 9 | open! Expect.Operators; 10 | 11 | test("Includes non-nulled arguments", () => 12 | expect(MyQuery.make( 13 | ~arg={ 14 | "nullableString": Some("a nullable string"), 15 | "string": "a string", 16 | "nullableInt": Some(456), 17 | "int": 123, 18 | "nullableFloat": Some(567.5), 19 | "float": 1234.5, 20 | "nullableBoolean": Some(false), 21 | "boolean": true, 22 | "nullableID": Some("a nullable ID"), 23 | "id": "an ID", 24 | }, 25 | ())##variables) 26 | == Js.Json.parseExn({| { 27 | "arg": { 28 | "nullableString": "a nullable string", 29 | "string": "a string", 30 | "nullableInt": 456, 31 | "int": 123, 32 | "nullableFloat": 567.5, 33 | "float": 1234.5, 34 | "nullableBoolean": false, 35 | "boolean": true, 36 | "nullableID": "a nullable ID", 37 | "id": "an ID" 38 | } 39 | } |})); 40 | })); 41 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/scalarsInput.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({. scalarsInput: string}); 3 | 4 | let make: ( 5 | ~arg: Js.t ({ 6 | .. 7 | nullableString: option(string), 8 | string: string, 9 | nullableInt: option(int), 10 | int: int, 11 | nullableFloat: option(float), 12 | float: float, 13 | nullableBoolean: option(bool), 14 | boolean: bool, 15 | nullableID: option(string), 16 | id: string, 17 | }), 18 | unit) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 19 | 20 | let makeWithVariables: 21 | Js.t({ 22 | . 23 | arg: Js.t({ 24 | . 25 | nullableString: option(string), 26 | string: string, 27 | nullableInt: option(int), 28 | int: int, 29 | nullableFloat: option(float), 30 | float: float, 31 | nullableBoolean: option(bool), 32 | boolean: bool, 33 | nullableID: option(string), 34 | id: string 35 | }), 36 | }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 37 | 38 | let query: string; 39 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/skipDirectives.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql 2 | {| 3 | query ($var: Boolean!) { 4 | v1: variousScalars { 5 | nullableString @skip(if: $var) 6 | string @skip(if: $var) 7 | } 8 | v2: variousScalars { 9 | nullableString @include(if: $var) 10 | string @include(if: $var) 11 | } 12 | } 13 | |} 14 | ]; 15 | 16 | Jest.(describe("Skip/include directives", () => { 17 | open Expect; 18 | open! Expect.Operators; 19 | 20 | test("Responds with None to nulled fields", () => 21 | expect(MyQuery.parse(Js.Json.parseExn({|{"v1": {"nullableString": null, "string": null}, "v2": {"nullableString": null, "string": null}}|}))) 22 | == {"v1": {"nullableString": None, "string": None}, "v2": {"nullableString": None, "string": None}}); 23 | 24 | test("Responds with None to omitted fields", () => 25 | expect(MyQuery.parse(Js.Json.parseExn({|{"v1": {}, "v2": {}}|}))) 26 | == {"v1": {"nullableString": None, "string": None}, "v2": {"nullableString": None, "string": None}}); 27 | })); 28 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/skipDirectives.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | v1: Js.t({. nullableString: option(string), string: option(string)}), 5 | v2: Js.t({. nullableString: option(string), string: option(string)}), 6 | }); 7 | 8 | let make: (~var: bool, unit) 9 | => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 10 | 11 | let makeWithVariables: Js.t({ . var: bool }) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 12 | 13 | let query: string; 14 | }; 15 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/subscription.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | subscription { 3 | simpleSubscription { 4 | ...on Dog { 5 | name 6 | } 7 | ...on Human { 8 | name 9 | } 10 | } 11 | } 12 | |}]; 13 | 14 | Jest.(describe("Subscriptions", () => { 15 | open Expect; 16 | open! Expect.Operators; 17 | 18 | test("Printed query is a subscription", () => 19 | expect(MyQuery.query |> Js.String.indexOf("subscription")) == 0); 20 | })); 21 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/subscription.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | simpleSubscription: [ 5 | | `Dog(Js.t({ . name: string })) 6 | | `Human(Js.t({ . name: string })) 7 | ], 8 | }); 9 | 10 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 11 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 12 | let query: string; 13 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/typename.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | first: nestedObject { 4 | __typename 5 | inner { 6 | __typename 7 | inner { 8 | __typename 9 | field 10 | } 11 | } 12 | } 13 | } 14 | |}]; 15 | 16 | Jest.(describe("Typename as implicit field", () => { 17 | open Expect; 18 | open! Expect.Operators; 19 | 20 | test("Decodes typename as a non-nullable string", () => 21 | expect(MyQuery.parse(Js.Json.parseExn({| 22 | {"first": {"__typename": "NestedObject", "inner": null}} 23 | |}))) == {"first": {"__typename": "NestedObject", "inner": None}}); 24 | })); 25 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/typename.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | first: Js.t({ 5 | . 6 | __typename: string, 7 | inner: option(Js.t({ 8 | . 9 | __typename: string, 10 | inner: option(Js.t({ 11 | . 12 | __typename: string, 13 | field: string 14 | })), 15 | })), 16 | }), 17 | }); 18 | 19 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 20 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 21 | let query: string; 22 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/union.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | dogOrHuman { 4 | ...on Dog { 5 | name 6 | barkVolume 7 | } 8 | 9 | ...on Human { 10 | name 11 | } 12 | } 13 | } 14 | |}]; 15 | 16 | Jest.(describe("Union types", () => { 17 | open Expect; 18 | open! Expect.Operators; 19 | 20 | test("Decodes exhaustive query", () => 21 | expect(MyQuery.parse(Js.Json.parseExn({| { 22 | "dogOrHuman": { 23 | "__typename": "Dog", 24 | "name": "Fido", 25 | "barkVolume": 123 26 | } 27 | } |}))) == { 28 | "dogOrHuman": `Dog({ 29 | "name": "Fido", 30 | "barkVolume": 123.0, 31 | }), 32 | }); 33 | })); 34 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/union.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | dogOrHuman : [ 5 | | `Dog(Js.t({. name : string, barkVolume : float})) 6 | | `Human(Js.t({. name : string})) 7 | ], 8 | }); 9 | 10 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 11 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 12 | let query: string; 13 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/unionPartial.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | dogOrHuman { 4 | ...on Dog { 5 | name 6 | barkVolume 7 | } 8 | } 9 | } 10 | |}]; 11 | 12 | Jest.(describe("Union types", () => { 13 | open Expect; 14 | open! Expect.Operators; 15 | 16 | test("Decodes non-exhaustive query", () => 17 | expect(MyQuery.parse(Js.Json.parseExn({| { 18 | "dogOrHuman": { 19 | "__typename": "Human", 20 | "name": "Max" 21 | } 22 | } |}))) == { 23 | "dogOrHuman": `Nonexhaustive, 24 | }); 25 | })); 26 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/unionPartial.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | dogOrHuman: [ | `Dog (Js.t({. name: string, barkVolume: float})) | `Nonexhaustive] 5 | }); 6 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 7 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 8 | let query: string; 9 | }; -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/variant.re: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | mutation { 3 | mutationWithError @bsVariant { 4 | value { 5 | stringField 6 | } 7 | 8 | errors { 9 | field 10 | message 11 | } 12 | } 13 | } 14 | |}]; 15 | 16 | Jest.(describe("Non-union variant through @bsVariant", () => { 17 | open Expect; 18 | open! Expect.Operators; 19 | 20 | test("Converts object into variant", () => 21 | expect(MyQuery.parse(Js.Json.parseExn({| { 22 | "mutationWithError": { 23 | "value": { 24 | "stringField": "a string" 25 | } 26 | } 27 | } |}))) == { 28 | "mutationWithError": `Value({ 29 | "stringField": "a string", 30 | }), 31 | }); 32 | })); 33 | -------------------------------------------------------------------------------- /tests_bucklescript/__tests__/variant.rei: -------------------------------------------------------------------------------- 1 | module MyQuery: { 2 | type t = Js.t({ 3 | . 4 | mutationWithError : [ 5 | | `Value(Js.t({. stringField: string})) 6 | | `Errors(array(Js.t({. field: [ | `FIRST | `SECOND | `THIRD], message: string}))) 7 | ], 8 | }); 9 | 10 | let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 11 | let makeWithVariables: Js.t({.}) => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t }); 12 | let query: string; 13 | }; -------------------------------------------------------------------------------- /tests_bucklescript/bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql_ppx_test", 3 | "sources": [ 4 | "__tests__" 5 | ], 6 | "ppx-flags": [ 7 | "../graphql_ppx.exe -apollo-mode" 8 | ], 9 | "bs-dependencies": [ 10 | "@glennsl/bs-jest" 11 | ], 12 | "refmt": 3, 13 | "bsc-flags": [ "-bs-super-errors" ] 14 | } 15 | -------------------------------------------------------------------------------- /tests_native/arg_named_query.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module My_query = [%graphql {| 4 | query ($query: String!) { 5 | argNamedQuery(query: $query) 6 | } 7 | |}] 8 | 9 | let serializes_variables () = 10 | test_json 11 | ((My_query.make ~query:"a query" ())#variables) 12 | (Yojson.Basic.from_string {|{"query": "a query"}|}) 13 | 14 | let no_name_clash () = 15 | Alcotest.(check (neg string) "strings" 16 | ((My_query.make ~query:"a query" ())#query) 17 | "a query") 18 | 19 | let tests = [ 20 | "Serializes variables", `Quick, serializes_variables; 21 | "The name 'query' does not clash with the query argument", `Quick, no_name_clash; 22 | ] 23 | -------------------------------------------------------------------------------- /tests_native/comment.ml: -------------------------------------------------------------------------------- 1 | module My_query = [%graphql {| 2 | query ($arg: NonrecursiveInput!) { 3 | nonrecursiveInput(arg: $arg) # comment to test 4 | } 5 | |}] 6 | -------------------------------------------------------------------------------- /tests_native/custom_decoder.ml: -------------------------------------------------------------------------------- 1 | module My_query = [%graphql {| 2 | { 3 | variousScalars { 4 | string @bsDecoder(fn: "int_of_string") 5 | int @bsDecoder(fn: "string_of_int") 6 | } 7 | } 8 | |}] 9 | 10 | type qt = < variousScalars : < string : int; int: string > > 11 | 12 | let my_query = ( 13 | module struct 14 | type t = qt 15 | 16 | let pp formatter obj = 17 | Format.fprintf 18 | formatter 19 | "string = @[%i@]; int = @[%s@]>@] >" 20 | obj#variousScalars#string 21 | obj#variousScalars#int 22 | 23 | let equal a b = 24 | a#variousScalars#string = b#variousScalars#string && 25 | a#variousScalars#int = b#variousScalars#int 26 | 27 | end : Alcotest.TESTABLE with type t = qt) 28 | 29 | let runs_the_decoder () = 30 | Alcotest.check my_query "query result equality" 31 | (My_query.parse (Yojson.Basic.from_string {|{"variousScalars": {"string": "123", "int": 456}}|})) 32 | (object 33 | method variousScalars = object 34 | method string = 123 35 | method int = "456" 36 | end 37 | end) 38 | 39 | let tests = [ 40 | "Runs the decoder", `Quick, runs_the_decoder; 41 | ] 42 | -------------------------------------------------------------------------------- /tests_native/custom_scalars.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module My_query = [%graphql {| 4 | query ($opt: CustomScalar, $req: CustomScalar!) { 5 | customScalarField(argOptional: $opt, argRequired: $req) { 6 | nullable 7 | nonNullable 8 | } 9 | } 10 | |}] 11 | 12 | type qt = < customScalarField : < nullable : Yojson.Basic.json option ; nonNullable : Yojson.Basic.json > > 13 | 14 | let my_query = ( 15 | module struct 16 | type t = qt 17 | 18 | let pp formatter (obj: qt) = 19 | Format.fprintf 20 | formatter 21 | "< customScalarField = @[<>@] >" 22 | (Yojson.Basic.pretty_print ~std:false |> print_option) obj#customScalarField#nullable 23 | (Yojson.Basic.pretty_print ~std:false) obj#customScalarField#nonNullable 24 | 25 | let equal a b = 26 | a#customScalarField#nullable = b#customScalarField#nullable && 27 | a#customScalarField#nonNullable = b#customScalarField#nonNullable 28 | 29 | end : Alcotest.TESTABLE with type t = qt) 30 | 31 | let encodes_json_objects () = 32 | test_json 33 | (My_query.make ~opt:(`Int 123) ~req:(`Int 456) ())#variables 34 | (Yojson.Basic.from_string {| { "opt": 123, "req": 456 } |}) 35 | 36 | let encodes_json_objects_from_obj () = 37 | test_json 38 | (My_query.makeWithVariables 39 | (object 40 | method opt = Some (`Int 123) 41 | method req = `Int 456 42 | end))#variables 43 | (Yojson.Basic.from_string {| { "opt": 123, "req": 456 } |}) 44 | 45 | let decodes_to_json () = 46 | Alcotest.check my_query "query equality" 47 | (My_query.parse (Yojson.Basic.from_string {|{"customScalarField": { "nullable": 123, "nonNullable": 456 }}|})) 48 | (object 49 | method customScalarField = object 50 | method nullable = Some (`Int 123) 51 | method nonNullable = `Int 456 52 | end 53 | end) 54 | 55 | let tests = [ 56 | "Encodes custom scalar variables as Json objects", `Quick, encodes_json_objects; 57 | "Encodes nullable scalar variables as optional Json objects", `Quick, encodes_json_objects_from_obj; 58 | "Decodes results to JSON", `Quick, decodes_to_json; 59 | ] 60 | -------------------------------------------------------------------------------- /tests_native/dune: -------------------------------------------------------------------------------- 1 | (test 2 | (name main) 3 | (libraries alcotest yojson str) 4 | (preprocess (pps graphql_ppx ppxlib.runner))) 5 | -------------------------------------------------------------------------------- /tests_native/enum_input.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | query ($arg: SampleField!) { 5 | enumInput(arg: $arg) 6 | } 7 | |}] 8 | 9 | let encodes_arguments () = 10 | Alcotest.check yojson "json" 11 | (MyQuery.make ~arg:`FIRST ())#variables 12 | (Yojson.Basic.from_string {| { "arg": "FIRST" } |}) 13 | 14 | let tests = [ 15 | "Encodes enum arguments to strings", `Quick, encodes_arguments; 16 | ] 17 | -------------------------------------------------------------------------------- /tests_native/fragment_definition.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module Fragments = [%graphql {| 4 | fragment listFragment on Lists { 5 | nullableOfNullable 6 | nullableOfNonNullable 7 | } 8 | |}] 9 | 10 | type ft = < nullableOfNullable : string option array option; nullableOfNonNullable : string array option > 11 | 12 | module MyQuery = [%graphql {| 13 | query { 14 | l1: lists { 15 | ...Fragments.ListFragment 16 | } 17 | 18 | l2: lists { 19 | ...Fragments.ListFragment @bsField(name: "frag1") 20 | ...Fragments.ListFragment @bsField(name: "frag2") 21 | } 22 | } 23 | |}] 24 | 25 | type qt = < l1 : ft ; l2 : < frag1 : ft ; frag2 : ft > > 26 | 27 | let print_fragment formatter (obj: ft) = 28 | Format.fprintf 29 | formatter 30 | "< nullableOfNullable = @[%a@]; nullableOfNonNullable = @[%a@] >" 31 | (Format.pp_print_string |> print_option |> print_array |> print_option) obj#nullableOfNullable 32 | (Format.pp_print_string |> print_array |> print_option) obj#nullableOfNonNullable 33 | 34 | let fragment_equal a b = 35 | a#nullableOfNullable = b#nullableOfNullable && 36 | a#nullableOfNonNullable = b#nullableOfNonNullable 37 | 38 | let fragment = ( 39 | module struct 40 | type t = ft 41 | 42 | let pp = print_fragment 43 | 44 | let equal = fragment_equal 45 | 46 | end : Alcotest.TESTABLE with type t = ft) 47 | 48 | let my_query = ( 49 | module struct 50 | type t = qt 51 | 52 | let pp formatter (obj: qt) = 53 | Format.fprintf 54 | formatter 55 | "< l1 = @[%a@]; l2 = @[<>< frag1 = @[%a@]; frag2 = @[%a@] >@] >" 56 | print_fragment obj#l1 57 | print_fragment obj#l2#frag1 58 | print_fragment obj#l2#frag2 59 | 60 | let equal a b = 61 | fragment_equal a#l1 b#l1 && 62 | fragment_equal a#l2#frag1 b#l2#frag1 && 63 | fragment_equal a#l2#frag2 b#l2#frag2 64 | 65 | end : Alcotest.TESTABLE with type t = qt) 66 | 67 | 68 | let decodes_the_fragment () = 69 | Alcotest.check my_query "query result equality" 70 | (MyQuery.parse (Yojson.Basic.from_string({| 71 | { 72 | "l1": {"nullableOfNullable": ["a", null, "b"]}, 73 | "l2": {"nullableOfNullable": ["a", null, "b"]} 74 | }|}))) 75 | (object 76 | method l1 = object 77 | method nullableOfNullable = Some([| Some "a"; None; Some "b" |]) 78 | method nullableOfNonNullable = None 79 | end 80 | 81 | method l2 = object 82 | method frag1 = object 83 | method nullableOfNullable = Some([| Some "a"; None; Some "b" |]) 84 | method nullableOfNonNullable = None 85 | end 86 | method frag2 = object 87 | method nullableOfNullable = Some([| Some "a"; None; Some "b" |]) 88 | method nullableOfNonNullable = None 89 | end 90 | end 91 | end) 92 | 93 | let tests = [ 94 | "Decodes the fragment", `Quick, decodes_the_fragment; 95 | ] 96 | -------------------------------------------------------------------------------- /tests_native/interface.ml: -------------------------------------------------------------------------------- 1 | module QueryWithFragments = [%graphql 2 | {| 3 | query { 4 | users { 5 | id 6 | ... on AdminUser { 7 | name 8 | } 9 | ... on AnonymousUser { 10 | anonymousId 11 | } 12 | } 13 | } 14 | |}] 15 | 16 | type user = [ 17 | | `User of < id : string > 18 | | `AdminUser of < id : string; name: string > 19 | | `AnonymousUser of < id : string; anonymousId : int > 20 | ] 21 | 22 | type only_user = [ 23 | | `User of < id : string > 24 | ] 25 | 26 | module QueryWithoutFragments = [%graphql 27 | {| 28 | query { 29 | users { 30 | id 31 | } 32 | } 33 | |}] 34 | 35 | let json = {|{ 36 | "users": [ 37 | { "__typename": "AdminUser", "id": "1", "name": "bob" }, 38 | { "__typename": "AnonymousUser", "id": "2", "anonymousId": 1}, 39 | { "__typename": "OtherUser", "id": "3"} 40 | ]}|} 41 | 42 | let user = ( 43 | module struct 44 | type t = user 45 | 46 | let pp formatter = function 47 | | `User u -> Format.fprintf formatter "`User < id = @[%s@] >" u#id 48 | | `AdminUser u -> Format.fprintf formatter "`AdminUser < id = @[%s@]; name = @[%s@] >" u#id u#name 49 | | `AnonymousUser u -> Format.fprintf formatter "`AnonymousUser < id = @[%s@]; anonymousId = @[%i@] >" u#id u#anonymousId 50 | 51 | let equal (a: user) (b: user) = 52 | match a, b with 53 | | (`User u1), (`User u2) -> u1#id = u2#id 54 | | (`AdminUser u1), (`AdminUser u2) -> u1#id = u2#id && u1#name = u2#name 55 | | (`AnonymousUser u1), (`AnonymousUser u2) -> u1#id = u2#id && u1#anonymousId = u2#anonymousId 56 | | _ -> false 57 | 58 | end : Alcotest.TESTABLE with type t = user) 59 | 60 | let only_user = ( 61 | module struct 62 | type t = only_user 63 | 64 | let pp formatter = function 65 | | `User u -> Format.fprintf formatter "`User < id = @[%s@] >" u#id 66 | 67 | let equal (a: only_user) (b: only_user) = 68 | match a, b with 69 | | (`User u1), (`User u2) -> u1#id = u2#id 70 | 71 | end : Alcotest.TESTABLE with type t = only_user) 72 | 73 | let decode_with_fragments () = 74 | Alcotest.(check (array user)) "query result equality" 75 | (QueryWithFragments.parse (Yojson.Basic.from_string json))#users 76 | [| 77 | `AdminUser (object method id = "1" method name = "bob" end); 78 | `AnonymousUser (object method id = "2" method anonymousId = 1 end); 79 | `User(object method id = "3" end); 80 | |] 81 | 82 | let decode_without_fragments () = 83 | Alcotest.(check (array only_user)) "query result equality" 84 | (QueryWithoutFragments.parse (Yojson.Basic.from_string json))#users 85 | [| 86 | `User(object method id = "1" end); 87 | `User(object method id = "2" end); 88 | `User(object method id = "3" end); 89 | |] 90 | 91 | let tests = [ 92 | "Decodes the interface with fragments", `Quick, decode_with_fragments; 93 | "Decodes the interface without fragments", `Quick, decode_without_fragments; 94 | ] 95 | -------------------------------------------------------------------------------- /tests_native/list_args.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql 4 | {| 5 | query ( 6 | $nullableOfNullable: [String], 7 | $nullableOfNonNullable: [String!], 8 | $nonNullableOfNullable: [String]!, 9 | $nonNullableOfNonNullable: [String!]!, 10 | ) { 11 | listsInput(arg: { 12 | nullableOfNullable: $nullableOfNullable, 13 | nullableOfNonNullable: $nullableOfNonNullable, 14 | nonNullableOfNullable: $nonNullableOfNullable, 15 | nonNullableOfNonNullable: $nonNullableOfNonNullable, 16 | }) 17 | } 18 | |} 19 | ] 20 | 21 | type qt = < lists : string > 22 | 23 | let my_query = ( 24 | module struct 25 | type t = qt 26 | 27 | let pp formatter (obj: qt) = 28 | Format.fprintf 29 | formatter 30 | "< lists = @[%s@] >" 31 | obj#lists 32 | 33 | let equal (a: qt) (b: qt) = 34 | a#lists = b#lists 35 | end : Alcotest.TESTABLE with type t = qt) 36 | 37 | let omit_nullable_args () = 38 | test_json 39 | ((MyQuery.make ~nonNullableOfNullable:[||] ~nonNullableOfNonNullable:[||] ())#variables) 40 | (Yojson.Basic.from_string {| { 41 | "nullableOfNullable": null, 42 | "nullableOfNonNullable": null, 43 | "nonNullableOfNullable": [], 44 | "nonNullableOfNonNullable": [] 45 | } |}) 46 | 47 | let allows_none_in_lists () = 48 | test_json 49 | ((MyQuery.make 50 | ~nullableOfNullable:[| Some "x"; None; Some "y" |] 51 | ~nonNullableOfNullable:[| Some "a"; None; Some "b" |] 52 | ~nonNullableOfNonNullable:[|"1"; "2"; "3"|] 53 | ())#variables) 54 | (Yojson.Basic.from_string {| { 55 | "nullableOfNullable": ["x", null, "y"], 56 | "nullableOfNonNullable": null, 57 | "nonNullableOfNullable": ["a", null, "b"], 58 | "nonNullableOfNonNullable": ["1", "2", "3"] 59 | } |}) 60 | 61 | let tests = [ 62 | "Can omit nullable arguments", `Quick, omit_nullable_args; 63 | "Allows None in lists with nullable items", `Quick, allows_none_in_lists; 64 | ] 65 | -------------------------------------------------------------------------------- /tests_native/list_inputs.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | query ($arg: ListsInput!) { 5 | listsInput(arg: $arg) 6 | } 7 | |}] 8 | 9 | type qt = < listsInput : string > 10 | 11 | let my_query = ( 12 | module struct 13 | type t = qt 14 | 15 | let pp formatter (obj: qt) = 16 | Format.fprintf 17 | formatter 18 | "< listsInput = @[%s@] >" 19 | obj#listsInput 20 | 21 | let equal (a: qt) (b: qt) = 22 | a#listsInput = b#listsInput 23 | 24 | end : Alcotest.TESTABLE with type t = qt) 25 | 26 | let allows_none_in_lists_of_nullable () = 27 | test_json 28 | ((MyQuery.make 29 | ~arg:(object 30 | method nullableOfNullable = Some [| Some "x"; None; Some"y" |] 31 | method nullableOfNonNullable = None 32 | method nonNullableOfNullable = [|Some "a"; None; Some "b"|] 33 | method nonNullableOfNonNullable = [|"1"; "2"; "3"|] 34 | end) 35 | ())#variables) 36 | (Yojson.Basic.from_string {| { 37 | "arg": { 38 | "nullableOfNullable": ["x", null, "y"], 39 | "nullableOfNonNullable": null, 40 | "nonNullableOfNullable": ["a", null, "b"], 41 | "nonNullableOfNonNullable": ["1", "2", "3"] 42 | } 43 | } |}) 44 | 45 | let tests = [ 46 | "Allows None in lists of nullable types", `Quick, allows_none_in_lists_of_nullable 47 | ] 48 | -------------------------------------------------------------------------------- /tests_native/lists.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | { 5 | lists { 6 | nullableOfNullable 7 | nullableOfNonNullable 8 | nonNullableOfNullable 9 | nonNullableOfNonNullable 10 | } 11 | } 12 | |}] 13 | 14 | type qt = < lists : < 15 | nullableOfNullable: string option array option; 16 | nullableOfNonNullable: string array option; 17 | nonNullableOfNullable: string option array; 18 | nonNullableOfNonNullable: string array; 19 | > > 20 | 21 | let my_query = ( 22 | module struct 23 | type t = qt 24 | 25 | let pp formatter (obj: qt) = 26 | Format.fprintf 27 | formatter 28 | "< nullableOfNullable = @[%a@]; nullableOfNonNullable = @[%a@]; nonNullableOfNullable = @[%a@]; nonNullableOfNonNullable = @[%a@] >" 29 | (Format.pp_print_string |> print_option |> print_array |> print_option) obj#lists#nullableOfNullable 30 | (Format.pp_print_string |> print_array |> print_option) obj#lists#nullableOfNonNullable 31 | (Format.pp_print_string |> print_option |> print_array) obj#lists#nonNullableOfNullable 32 | (Format.pp_print_string |> print_array) obj#lists#nonNullableOfNonNullable 33 | 34 | let equal (a: qt) (b: qt) = 35 | a#lists#nullableOfNullable = b#lists#nullableOfNullable && 36 | a#lists#nullableOfNonNullable = b#lists#nullableOfNonNullable && 37 | a#lists#nonNullableOfNullable = b#lists#nonNullableOfNullable && 38 | a#lists#nonNullableOfNonNullable = b#lists#nonNullableOfNonNullable 39 | 40 | end : Alcotest.TESTABLE with type t = qt) 41 | 42 | let null_in_lists () = 43 | Alcotest.check my_query "query result equality" 44 | (MyQuery.parse (Yojson.Basic.from_string {|{"lists": {"nullableOfNullable": [null, "123"], "nonNullableOfNullable": [null, "123"], "nonNullableOfNonNullable": ["a", "b"]}}|})) 45 | (object 46 | method lists = object 47 | method nullableOfNullable = Some [| None; Some "123" |] 48 | method nullableOfNonNullable = None 49 | method nonNullableOfNullable = [| None; Some "123" |] 50 | method nonNullableOfNonNullable = [| "a"; "b" |] 51 | end 52 | end) 53 | 54 | let tests = [ 55 | "Null in nullable lists", `Quick, null_in_lists 56 | ] 57 | -------------------------------------------------------------------------------- /tests_native/main.ml: -------------------------------------------------------------------------------- 1 | let () = Alcotest.run "Native PPX tests" [ 2 | "Argument named 'query'", Arg_named_query.tests; 3 | "Custom decoder", Custom_decoder.tests; 4 | "Custom scalars", Custom_scalars.tests; 5 | "Enum input", Enum_input.tests; 6 | "Fragment definition", Fragment_definition.tests; 7 | "Interface", Interface.tests; 8 | "Lists", Lists.tests; 9 | "List arguments", List_args.tests; 10 | "List inputs", List_inputs.tests; 11 | "Mutations", Mutation.tests; 12 | "Nested decoding", Nested.tests; 13 | "Nonrecursive input", Nonrecursive_input.tests; 14 | "Records", Record.tests; 15 | "Recursive input", Recursive_input.tests; 16 | "Scalars", Scalars.tests; 17 | "Scalar arguments", Scalars_args.tests; 18 | "Scalar inputs", Scalars_input.tests; 19 | "Skip directives", Skip_directives.tests; 20 | "Typename", Typename.tests; 21 | "Unions", Union.tests; 22 | "Partial unions", Union_partial.tests; 23 | "Variant conversion", Variant.tests; 24 | ] 25 | -------------------------------------------------------------------------------- /tests_native/mutation.ml: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | mutation { 3 | mutationWithError { 4 | value { 5 | stringField 6 | } 7 | 8 | errors { 9 | field 10 | message 11 | } 12 | } 13 | } 14 | |}] 15 | 16 | let printed_query () = 17 | Alcotest.check Alcotest.int "string equal" 18 | (Str.search_forward (Str.regexp "^mutation") MyQuery.query 0) 19 | 0 20 | 21 | let tests = [ 22 | "Printed query is a mutation", `Quick, printed_query; 23 | ] 24 | -------------------------------------------------------------------------------- /tests_native/nested.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | type record = {f1: string; f2: string} 4 | 5 | module MyQuery = [%graphql {| 6 | { 7 | first: nestedObject { 8 | inner { 9 | inner { 10 | field 11 | } 12 | } 13 | } 14 | 15 | second: nestedObject { 16 | inner { 17 | inner @bsRecord { 18 | f1: field 19 | f2: field 20 | } 21 | } 22 | } 23 | } 24 | |}] 25 | 26 | type qt = < 27 | first : < inner : < inner : < field : string > option > option >; 28 | second : < inner : < inner : record option > option >; 29 | > 30 | 31 | let my_query = ( 32 | module struct 33 | type t = qt 34 | 35 | let pp formatter (obj: qt) = 36 | Format.fprintf 37 | formatter 38 | "< first = < inner = @[%a@] > ; second = < inner = @[%a@] > >" 39 | ((fun formatter v -> Format.fprintf 40 | formatter 41 | "< inner = @[%a@] >" 42 | ((fun formatter v -> Format.fprintf 43 | formatter 44 | "< field = %a >" 45 | Format.pp_print_string v#field 46 | ) |> print_option) 47 | v#inner 48 | ) |> print_option) 49 | obj#first#inner 50 | ((fun formatter v -> Format.fprintf 51 | formatter 52 | "< inner = @[%a@] >" 53 | ((fun formatter v -> Format.fprintf 54 | formatter 55 | "{ f1 = %a ; f2 = %a }" 56 | Format.pp_print_string v.f1 57 | Format.pp_print_string v.f2 58 | ) |> print_option) 59 | v#inner 60 | ) |> print_option) 61 | obj#second#inner 62 | 63 | let equal (a: qt) (b: qt) = 64 | (opt_eq (fun a b -> 65 | opt_eq (fun a b -> 66 | a#field = b#field) 67 | a#inner b#inner) 68 | a#first#inner b#first#inner) && 69 | (opt_eq (fun a b -> 70 | opt_eq (fun a b -> 71 | a.f1 = b.f1 && a.f2 = b.f2) 72 | a#inner b#inner) 73 | a#second#inner b#second#inner) 74 | end : Alcotest.TESTABLE with type t = qt) 75 | 76 | let decodes_recursively () = 77 | Alcotest.check my_query "query result equality" 78 | (MyQuery.parse (Yojson.Basic.from_string {| { 79 | "first": {"inner": {"inner": {"field": "second"}}}, 80 | "second": {"inner": null} 81 | } |})) 82 | (object 83 | method first = object 84 | method inner = Some (object 85 | method inner = Some (object 86 | method field = "second" 87 | end) 88 | end) 89 | end 90 | method second = object 91 | method inner = None 92 | end 93 | end) 94 | 95 | let tests = [ 96 | "Decodes recursively", `Quick, decodes_recursively; 97 | ] 98 | -------------------------------------------------------------------------------- /tests_native/nonrecursive_input.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | query ($arg: NonrecursiveInput!) { 5 | nonrecursiveInput(arg: $arg) 6 | } 7 | |}] 8 | 9 | type qt = < nonrecursiveInput : string > 10 | 11 | let my_query = ( 12 | module struct 13 | type t = qt 14 | 15 | let pp formatter (t: qt) = 16 | Format.fprintf 17 | formatter 18 | "< nonrecursiveInput = %a >" 19 | Format.pp_print_string t#nonrecursiveInput 20 | 21 | let equal (a: qt) (b: qt) = 22 | a#nonrecursiveInput = b#nonrecursiveInput 23 | 24 | end : Alcotest.TESTABLE with type t = qt) 25 | 26 | let construct_recursive_input_type () = 27 | test_json 28 | ((MyQuery.make 29 | ~arg:(object 30 | method field = Some "test" 31 | method enum = Some `SECOND 32 | end) 33 | ())#variables) 34 | (Yojson.Basic.from_string {| { 35 | "arg": { 36 | "field": "test", 37 | "enum": "SECOND" 38 | } 39 | } |}) 40 | 41 | let tests = [ 42 | "Constructing a recursive input type", `Quick, construct_recursive_input_type; 43 | ] 44 | -------------------------------------------------------------------------------- /tests_native/record.ml: -------------------------------------------------------------------------------- 1 | type scalars = { 2 | string: string; 3 | int: int; 4 | } 5 | 6 | let scalars = ( 7 | module struct 8 | type t = scalars 9 | 10 | let pp formatter (obj: scalars) = 11 | Format.fprintf 12 | formatter 13 | "{ string = %a ; int = %a }" 14 | Format.pp_print_string obj.string 15 | Format.pp_print_int obj.int 16 | 17 | let equal (a: scalars) (b: scalars) = 18 | a.string = b.string && a.int = b.int 19 | 20 | end : Alcotest.TESTABLE with type t = scalars) 21 | 22 | type dog = { 23 | name: string; 24 | barkVolume: float; 25 | } 26 | 27 | let dog = ( 28 | module struct 29 | type t = dog 30 | 31 | let pp formatter (obj: dog) = 32 | Format.fprintf 33 | formatter 34 | "{ name = %a ; barkVolume = %a }" 35 | Format.pp_print_string obj.name 36 | Format.pp_print_float obj.barkVolume 37 | 38 | let equal (a: dog) (b: dog) = 39 | a.name = b.name && a.barkVolume = b.barkVolume 40 | 41 | end : Alcotest.TESTABLE with type t = dog) 42 | 43 | module MyQuery = [%graphql {| 44 | { 45 | variousScalars @bsRecord { 46 | string 47 | int 48 | } 49 | } 50 | |}] 51 | 52 | type qt = < variousScalars : scalars > 53 | 54 | let my_query = ( 55 | module struct 56 | type t = qt 57 | 58 | let pp formatter (obj: qt) = 59 | Format.fprintf 60 | formatter 61 | "< variousScalars = @[%a@] >" 62 | (Alcotest.pp scalars) obj#variousScalars 63 | 64 | let equal (a: qt) (b: qt) = 65 | Alcotest.equal scalars a#variousScalars b#variousScalars 66 | 67 | end : Alcotest.TESTABLE with type t = qt) 68 | 69 | module ExternalFragmentQuery = [%graphql {| 70 | fragment Fragment on VariousScalars @bsRecord { 71 | string 72 | int 73 | } 74 | 75 | { 76 | variousScalars { 77 | ...Fragment 78 | } 79 | } 80 | |}] 81 | 82 | module InlineFragmentQuery = [%graphql {| 83 | { 84 | dogOrHuman { 85 | ...on Dog @bsRecord { 86 | name 87 | barkVolume 88 | } 89 | } 90 | } 91 | |}] 92 | 93 | type if_qt = < dogOrHuman : [ | `Dog of dog | `Nonexhaustive ] > 94 | 95 | let inline_fragment_query = ( 96 | module struct 97 | type t = if_qt 98 | 99 | let pp formatter (obj: if_qt) = 100 | Format.fprintf 101 | formatter 102 | "< dogOrHuman = @[%a@] >" 103 | (fun formatter v -> match v with 104 | | `Dog d -> Format.fprintf 105 | formatter "`Dog %a" (Alcotest.pp dog) d 106 | | `Nonexhaustive -> Format.fprintf formatter "`Nonexhaustive") 107 | obj#dogOrHuman 108 | 109 | let equal (a: if_qt) (b: if_qt) = 110 | match a#dogOrHuman, b#dogOrHuman with 111 | | (`Dog a), (`Dog b) -> Alcotest.equal dog a b 112 | | `Nonexhaustive, `Nonexhaustive -> true 113 | | _ -> false 114 | 115 | end : Alcotest.TESTABLE with type t = if_qt) 116 | 117 | module UnionExternalFragmentQuery = [%graphql {| 118 | fragment DogFragment on Dog @bsRecord { 119 | name 120 | barkVolume 121 | } 122 | 123 | { 124 | dogOrHuman { 125 | ...on Dog { 126 | ...DogFragment 127 | } 128 | } 129 | } 130 | |}] 131 | 132 | let decodes_record_in_selection () = 133 | Alcotest.check my_query "query result equality" 134 | (MyQuery.parse (Yojson.Basic.from_string {| {"variousScalars": {"string": "a string", "int": 123}} |})) 135 | (object 136 | method variousScalars = { string = "a string"; int = 123 } 137 | end) 138 | 139 | let decodes_record_in_external_fragment () = 140 | Alcotest.check my_query "query result equality" 141 | (ExternalFragmentQuery.parse (Yojson.Basic.from_string {| {"variousScalars": {"string": "a string", "int": 123}} |})) 142 | (object 143 | method variousScalars = { string = "a string"; int = 123 } 144 | end) 145 | 146 | let decodes_record_in_inline_fragment () = 147 | Alcotest.check inline_fragment_query "query result equality" 148 | (InlineFragmentQuery.parse (Yojson.Basic.from_string {| {"dogOrHuman": {"__typename": "Dog", "name": "name", "barkVolume": 123}} |})) 149 | (object 150 | method dogOrHuman = `Dog { name = "name"; barkVolume = 123.0 } 151 | end) 152 | 153 | let decodes_record_in_external_fragment_on_union_selections () = 154 | Alcotest.check inline_fragment_query "query result equality" 155 | (UnionExternalFragmentQuery.parse (Yojson.Basic.from_string {| {"dogOrHuman": {"__typename": "Dog", "name": "name", "barkVolume": 123}} |})) 156 | (object 157 | method dogOrHuman = `Dog { name = "name"; barkVolume = 123.0 } 158 | end) 159 | 160 | let tests = [ 161 | "Decodes a record in a selection", `Quick, decodes_record_in_selection; 162 | "Decodes a record in an external fragment", `Quick, decodes_record_in_external_fragment; 163 | "Decodes a record in an inline fragment", `Quick, decodes_record_in_inline_fragment; 164 | "Decodes a record in an external fragment on union selections", `Quick, decodes_record_in_external_fragment_on_union_selections; 165 | ] 166 | -------------------------------------------------------------------------------- /tests_native/recursive_input.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | query ($arg: RecursiveInput!) { 5 | recursiveInput(arg: $arg) 6 | } 7 | |}] 8 | 9 | let construct_recursive_input_type () = 10 | Alcotest.check yojson "json equality" 11 | ((MyQuery.make 12 | ~arg:(object 13 | method otherField = Some "test" 14 | method enum = None 15 | method inner = Some (object 16 | method otherField = Some "inner" 17 | method enum = Some `SECOND 18 | method inner = None 19 | end) 20 | end) 21 | ())#variables) 22 | (Yojson.Basic.from_string {| { 23 | "arg": { 24 | "otherField": "test", 25 | "inner": { 26 | "otherField": "inner", 27 | "inner": null, 28 | "enum": "SECOND" 29 | }, 30 | "enum": null 31 | } 32 | } |}) 33 | 34 | let tests = [ 35 | "Constructing a recursive input type", `Quick, construct_recursive_input_type; 36 | ] 37 | -------------------------------------------------------------------------------- /tests_native/scalars.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | { 5 | variousScalars { 6 | nullableString 7 | string 8 | nullableInt 9 | int 10 | nullableFloat 11 | float 12 | nullableBoolean 13 | boolean 14 | nullableID 15 | id 16 | } 17 | } 18 | |}] 19 | 20 | type qt = < variousScalars : < 21 | nullableString : string option ; 22 | string : string ; 23 | nullableInt : int option ; 24 | int : int ; 25 | nullableFloat : float option ; 26 | float : float ; 27 | nullableBoolean : bool option ; 28 | boolean : bool ; 29 | nullableID : string option ; 30 | id : string > > 31 | 32 | let my_query = ( 33 | module struct 34 | type t = qt 35 | 36 | let pp formatter (obj: qt) = 37 | Format.fprintf 38 | formatter 39 | "< variousScalars = @[<>< nullablleString = %a ; string = %a ; nullableInt = %a ; int = %a ; nullableFloat = %a ; float = %a ; nullableBoolean = %a ; boolean = %a ; nullableID = %a ; id = %a >@]" 40 | (Format.pp_print_string |> print_option) obj#variousScalars#nullableString 41 | Format.pp_print_string obj#variousScalars#string 42 | (Format.pp_print_int |> print_option) obj#variousScalars#nullableInt 43 | Format.pp_print_int obj#variousScalars#int 44 | (Format.pp_print_float |> print_option) obj#variousScalars#nullableFloat 45 | Format.pp_print_float obj#variousScalars#float 46 | (Format.pp_print_bool |> print_option) obj#variousScalars#nullableBoolean 47 | Format.pp_print_bool obj#variousScalars#boolean 48 | (Format.pp_print_string |> print_option) obj#variousScalars#nullableID 49 | Format.pp_print_string obj#variousScalars#id 50 | 51 | let equal (a: qt) (b: qt) = 52 | a#variousScalars#nullableString = b#variousScalars#nullableString && 53 | a#variousScalars#string = b#variousScalars#string && 54 | a#variousScalars#nullableInt = b#variousScalars#nullableInt && 55 | a#variousScalars#int = b#variousScalars#int && 56 | a#variousScalars#nullableFloat = b#variousScalars#nullableFloat && 57 | a#variousScalars#float = b#variousScalars#float && 58 | a#variousScalars#nullableBoolean = b#variousScalars#nullableBoolean && 59 | a#variousScalars#boolean = b#variousScalars#boolean && 60 | a#variousScalars#nullableID = b#variousScalars#nullableID && 61 | a#variousScalars#id = b#variousScalars#id 62 | 63 | end : Alcotest.TESTABLE with type t = qt) 64 | 65 | let decodes_non_null_scalars () = 66 | Alcotest.check my_query "query result equality" 67 | (MyQuery.parse (Yojson.Basic.from_string {| { 68 | "variousScalars": { 69 | "nullableString": "a nullable string", 70 | "string": "a string", 71 | "nullableInt": 456, 72 | "int": 123, 73 | "nullableFloat": 678.5, 74 | "float": 1234.5, 75 | "nullableBoolean": false, 76 | "boolean": true, 77 | "nullableID": "a nullable ID", 78 | "id": "an ID" 79 | } 80 | } |})) 81 | (object 82 | method variousScalars = (object 83 | method nullableString = Some "a nullable string" 84 | method string = "a string" 85 | method nullableInt = Some 456 86 | method int = 123 87 | method nullableFloat = Some 678.5 88 | method float = 1234.5 89 | method nullableBoolean = Some false 90 | method boolean = true 91 | method nullableID = Some "a nullable ID" 92 | method id = "an ID" 93 | end) 94 | end) 95 | 96 | let decodes_null_scalars () = 97 | Alcotest.check my_query "query result equality" 98 | (MyQuery.parse (Yojson.Basic.from_string {| { 99 | "variousScalars": { 100 | "nullableString": null, 101 | "string": "a string", 102 | "nullableInt": null, 103 | "int": 123, 104 | "nullableFloat": null, 105 | "float": 1234.5, 106 | "nullableBoolean": null, 107 | "boolean": true, 108 | "nullableID": null, 109 | "id": "an ID" 110 | } 111 | } |})) 112 | (object 113 | method variousScalars = (object 114 | method nullableString = None 115 | method string = "a string" 116 | method nullableInt = None 117 | method int = 123 118 | method nullableFloat = None 119 | method float = 1234.5 120 | method nullableBoolean = None 121 | method boolean = true 122 | method nullableID = None 123 | method id = "an ID" 124 | end) 125 | end) 126 | 127 | let decodes_omitted_scalars () = 128 | Alcotest.check my_query "query result equality" 129 | (MyQuery.parse (Yojson.Basic.from_string {| { 130 | "variousScalars": { 131 | "string": "a string", 132 | "int": 123, 133 | "float": 1234.5, 134 | "boolean": true, 135 | "id": "an ID" 136 | } 137 | } |})) 138 | (object 139 | method variousScalars = (object 140 | method nullableString = None 141 | method string = "a string" 142 | method nullableInt = None 143 | method int = 123 144 | method nullableFloat = None 145 | method float = 1234.5 146 | method nullableBoolean = None 147 | method boolean = true 148 | method nullableID = None 149 | method id = "an ID" 150 | end) 151 | end) 152 | 153 | let tests = [ 154 | "Decodes non-null scalars", `Quick, decodes_non_null_scalars; 155 | "Decodes null scalars", `Quick, decodes_null_scalars; 156 | "Decodes omitted scalars", `Quick, decodes_omitted_scalars; 157 | ] 158 | -------------------------------------------------------------------------------- /tests_native/scalars_args.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | query ( 5 | $nullableString: String, 6 | $string: String!, 7 | $nullableInt: Int, 8 | $int: Int!, 9 | $nullableFloat: Float, 10 | $float: Float!, 11 | $nullableBoolean: Boolean, 12 | $boolean: Boolean!, 13 | $nullableID: ID, 14 | $id: ID!, 15 | ) { 16 | scalarsInput(arg: { 17 | nullableString: $nullableString, 18 | string: $string, 19 | nullableInt: $nullableInt, 20 | int: $int, 21 | nullableFloat: $nullableFloat, 22 | float: $float, 23 | nullableBoolean: $nullableBoolean, 24 | boolean: $boolean, 25 | nullableID: $nullableID, 26 | id: $id, 27 | }) 28 | } 29 | |}] 30 | 31 | let allows_you_to_omit_nullable_arguments () = 32 | Alcotest.check yojson "json equality" 33 | ((MyQuery.make 34 | ~string:"a string" 35 | ~int:123 36 | ~float:1234.5 37 | ~boolean:true 38 | ~id:"an ID" 39 | ())#variables) 40 | (Yojson.Basic.from_string({| { 41 | "nullableString": null, 42 | "string": "a string", 43 | "nullableInt": null, 44 | "int": 123, 45 | "nullableFloat": null, 46 | "float": 1234.5, 47 | "nullableBoolean": null, 48 | "boolean": true, 49 | "nullableID": null, 50 | "id": "an ID" 51 | } |})) 52 | 53 | let includes_non_nulled_arguments () = 54 | Alcotest.check yojson "json equality" 55 | ((MyQuery.make 56 | ~nullableString:"a nullable string" 57 | ~string:"a string" 58 | ~nullableInt:456 59 | ~int:123 60 | ~nullableFloat:567.5 61 | ~float:1234.5 62 | ~nullableBoolean:false 63 | ~boolean:true 64 | ~nullableID:"a nullable ID" 65 | ~id:"an ID" 66 | ())#variables) 67 | (Yojson.Basic.from_string({| { 68 | "nullableString": "a nullable string", 69 | "string": "a string", 70 | "nullableInt": 456, 71 | "int": 123, 72 | "nullableFloat": 567.5, 73 | "float": 1234.5, 74 | "nullableBoolean": false, 75 | "boolean": true, 76 | "nullableID": "a nullable ID", 77 | "id": "an ID" 78 | } |})) 79 | 80 | let tests = [ 81 | "Allows you to omit nullable arguments", `Quick, allows_you_to_omit_nullable_arguments; 82 | "Includes non-nulled arguments", `Quick, includes_non_nulled_arguments; 83 | ] 84 | -------------------------------------------------------------------------------- /tests_native/scalars_input.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | query ($arg: VariousScalarsInput!) { 5 | scalarsInput(arg: $arg) 6 | } 7 | |}] 8 | 9 | let includes_non_nulled_arguments () = 10 | Alcotest.check yojson "json equality" 11 | ((MyQuery.make 12 | ~arg:(object 13 | method nullableString = Some "a nullable string" 14 | method string = "a string" 15 | method nullableInt = Some 456 16 | method int = 123 17 | method nullableFloat = Some 567.5 18 | method float = 1234.5 19 | method nullableBoolean = Some false 20 | method boolean = true 21 | method nullableID = Some "a nullable ID" 22 | method id = "an ID" 23 | end) 24 | ())#variables) 25 | (Yojson.Basic.from_string {| { 26 | "arg": { 27 | "nullableString": "a nullable string", 28 | "string": "a string", 29 | "nullableInt": 456, 30 | "int": 123, 31 | "nullableFloat": 567.5, 32 | "float": 1234.5, 33 | "nullableBoolean": false, 34 | "boolean": true, 35 | "nullableID": "a nullable ID", 36 | "id": "an ID" 37 | } 38 | } |}) 39 | 40 | let tests = [ 41 | "Includes non-nulled arguments", `Quick, includes_non_nulled_arguments; 42 | ] 43 | -------------------------------------------------------------------------------- /tests_native/skip_directives.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql 4 | {| 5 | query ($var: Boolean!) { 6 | v1: variousScalars { 7 | nullableString @skip(if: $var) 8 | string @skip(if: $var) 9 | } 10 | v2: variousScalars { 11 | nullableString @include(if: $var) 12 | string @include(if: $var) 13 | } 14 | } 15 | |}] 16 | 17 | type qt = < 18 | v1 : < nullableString : string option ; string : string option > ; 19 | v2 : < nullableString : string option ; string : string option > ; 20 | > 21 | 22 | let my_query = ( 23 | module struct 24 | type t = qt 25 | 26 | let pp formatter (obj: qt) = 27 | Format.fprintf 28 | formatter 29 | "< v1 = @[<>< nullableString = %a ; string = %a >@] ; @[<>< nullableString = %a ; string = %a >@] >" 30 | (Format.pp_print_string |> print_option) obj#v1#nullableString 31 | (Format.pp_print_string |> print_option) obj#v1#string 32 | (Format.pp_print_string |> print_option) obj#v2#nullableString 33 | (Format.pp_print_string |> print_option) obj#v2#string 34 | 35 | let equal (a: qt) (b: qt) = 36 | a#v1#nullableString = b#v1#nullableString && 37 | a#v1#string = b#v1#string && 38 | a#v2#nullableString = b#v2#nullableString && 39 | a#v2#string = b#v2#string 40 | 41 | end : Alcotest.TESTABLE with type t = qt) 42 | 43 | let responds_with_none_to_nulled_fields () = 44 | Alcotest.check my_query "query equality" 45 | (MyQuery.parse (Yojson.Basic.from_string {|{"v1": {"nullableString": null, "string": null}, "v2": {"nullableString": null, "string": null}}|})) 46 | (object 47 | method v1 = (object 48 | method nullableString = None 49 | method string = None 50 | end) 51 | method v2 = (object 52 | method nullableString = None 53 | method string = None 54 | end) 55 | end) 56 | 57 | let responds_with_none_to_omitted_fields () = 58 | Alcotest.check my_query "query equality" 59 | (MyQuery.parse (Yojson.Basic.from_string {|{"v1": {}, "v2": {}}|})) 60 | (object 61 | method v1 = (object 62 | method nullableString = None 63 | method string = None 64 | end) 65 | method v2 = (object 66 | method nullableString = None 67 | method string = None 68 | end) 69 | end) 70 | 71 | let tests = [ 72 | "Responds with None to nulled fields", `Quick, responds_with_none_to_nulled_fields; 73 | "Responds with None to omitted fields", `Quick, responds_with_none_to_omitted_fields; 74 | ] 75 | -------------------------------------------------------------------------------- /tests_native/test_shared.ml: -------------------------------------------------------------------------------- 1 | let yojson = ( 2 | module struct 3 | type t = Yojson.Basic.json 4 | 5 | let pp formatter t = 6 | Format.pp_print_text formatter (Yojson.Basic.pretty_to_string t) 7 | 8 | let equal = (=) 9 | end : Alcotest.TESTABLE with type t = Yojson.Basic.json) 10 | 11 | let test_json a b = Alcotest.check yojson "JSON equality" a b 12 | 13 | let print_option inner formatter = function 14 | | None -> Format.pp_print_string formatter "None" 15 | | Some v -> Format.fprintf formatter "Some(@[%a@])" inner v 16 | 17 | let print_array inner formatter value = 18 | let open Format in 19 | pp_print_string formatter "[ "; 20 | Array.iteri (fun idx v -> 21 | if idx > 0 then pp_print_string formatter "; "; 22 | pp_open_hovbox formatter 1; 23 | inner formatter v; 24 | pp_close_box formatter (); 25 | ) value; 26 | pp_print_string formatter " ]" 27 | 28 | let array_zipmap f a b = 29 | let min = min (Array.length a) (Array.length b) in 30 | Array.init min (fun i -> f a.(i) b.(i)) 31 | 32 | let opt_eq f a b = match a, b with 33 | | (Some a), (Some b) -> f a b 34 | | None, None -> true 35 | | _ -> false 36 | -------------------------------------------------------------------------------- /tests_native/typename.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | { 5 | first: nestedObject { 6 | __typename 7 | inner { 8 | __typename 9 | inner { 10 | __typename 11 | field 12 | } 13 | } 14 | } 15 | } 16 | |}] 17 | 18 | type qt = < 19 | first: < 20 | __typename : string ; 21 | inner : < 22 | __typename : string ; 23 | inner : < 24 | __typename : string ; 25 | field : string ; 26 | > option ; 27 | > option ; 28 | > 29 | > 30 | 31 | let my_query = ( 32 | module struct 33 | type t = qt 34 | 35 | let pp formatter (obj: qt) = 36 | Format.fprintf 37 | formatter 38 | "< first = @[<>< __typename = %a ; inner = %a >@] >" 39 | Format.pp_print_string obj#first#__typename 40 | (fun formatter -> function 41 | | Some v -> Format.fprintf 42 | formatter 43 | "Some @[<>< __typename = %a ; inner = %a >@]" 44 | Format.pp_print_string v#__typename 45 | (fun formatter -> function 46 | | Some v -> Format.fprintf 47 | formatter 48 | "Some @[<>< __typename = %a ; field = %a >@]" 49 | Format.pp_print_string v#__typename 50 | Format.pp_print_string v#field 51 | | None -> Format.fprintf formatter "None") 52 | v#inner 53 | | None -> Format.fprintf formatter "None") 54 | obj#first#inner 55 | 56 | let equal (a: qt) (b: qt) = 57 | a#first#__typename = b#first#__typename && 58 | opt_eq 59 | (fun a b -> 60 | a#__typename = b#__typename && 61 | opt_eq 62 | (fun a b -> 63 | a#__typename = b#__typename && 64 | a#field = b#field) 65 | a#inner b#inner) 66 | a#first#inner b#first#inner 67 | 68 | end : Alcotest.TESTABLE with type t = qt) 69 | 70 | let decodes_typename () = 71 | Alcotest.check my_query "result equality" 72 | (MyQuery.parse (Yojson.Basic.from_string {| 73 | {"first": {"__typename": "NestedObject", "inner": null}} 74 | |})) 75 | (object 76 | method first = (object 77 | method __typename = "NestedObject" 78 | method inner = None 79 | end) 80 | end) 81 | 82 | let tests = [ 83 | "Decodes typename as a non-nullable string", `Quick, decodes_typename; 84 | ] 85 | -------------------------------------------------------------------------------- /tests_native/union.ml: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | dogOrHuman { 4 | ...on Dog { 5 | name 6 | barkVolume 7 | } 8 | 9 | ...on Human { 10 | name 11 | } 12 | } 13 | } 14 | |}] 15 | 16 | type qt = < 17 | dogOrHuman : [ 18 | | `Dog of < name : string ; barkVolume : float > 19 | | `Human of < name : string > 20 | ] 21 | > 22 | 23 | let my_query = ( 24 | module struct 25 | type t = qt 26 | 27 | let pp formatter (obj: qt) = 28 | Format.fprintf formatter "< dogOrHuman = %a >" 29 | (fun formatter -> function 30 | | `Dog dog -> Format.fprintf formatter "`Dog @[<>< name = %a ; barkVolume = %a >@]" 31 | Format.pp_print_string dog#name 32 | Format.pp_print_float dog#barkVolume 33 | | `Human human -> Format.fprintf formatter "`Human @[<>< name = %a >@]" 34 | Format.pp_print_string human#name) 35 | obj#dogOrHuman 36 | 37 | let equal (a: qt) (b: qt) = 38 | match a#dogOrHuman, b#dogOrHuman with 39 | | (`Dog a), (`Dog b) -> a#name = b#name && a#barkVolume = b#barkVolume 40 | | (`Human a), (`Human b) -> a#name = b#name 41 | | _ -> false 42 | end : Alcotest.TESTABLE with type t = qt 43 | ) 44 | 45 | let decodes_exhaustive_query () = 46 | Alcotest.check my_query "result equality" 47 | (MyQuery.parse (Yojson.Basic.from_string{| { 48 | "dogOrHuman": { 49 | "__typename": "Dog", 50 | "name": "Fido", 51 | "barkVolume": 123 52 | } 53 | } |})) 54 | (object 55 | method dogOrHuman = `Dog (object 56 | method name = "Fido" 57 | method barkVolume = 123.0 58 | end) 59 | end) 60 | 61 | let tests = [ 62 | "Decodes exhaustive query", `Quick, decodes_exhaustive_query; 63 | ] 64 | -------------------------------------------------------------------------------- /tests_native/union_partial.ml: -------------------------------------------------------------------------------- 1 | module MyQuery = [%graphql {| 2 | { 3 | dogOrHuman { 4 | ...on Dog { 5 | name 6 | barkVolume 7 | } 8 | } 9 | } 10 | |}] 11 | 12 | type qt = < 13 | dogOrHuman : [ 14 | | `Dog of < name : string ; barkVolume : float > 15 | | `Nonexhaustive 16 | ] 17 | > 18 | 19 | let my_query = ( 20 | module struct 21 | type t = qt 22 | 23 | let pp formatter (obj: qt) = 24 | Format.fprintf formatter "< dogOrHuman = %a >" 25 | (fun formatter -> function 26 | | `Dog dog -> Format.fprintf formatter "`Dog @[<>< name = %a ; barkVolume = %a >@]" 27 | Format.pp_print_string dog#name 28 | Format.pp_print_float dog#barkVolume 29 | | `Nonexhaustive -> Format.fprintf formatter "`Nonexhaustive") 30 | obj#dogOrHuman 31 | 32 | let equal (a: qt) (b: qt) = 33 | match a#dogOrHuman, b#dogOrHuman with 34 | | (`Dog a), (`Dog b) -> a#name = b#name && a#barkVolume = b#barkVolume 35 | | `Nonexhaustive, `Nonexhaustive -> true 36 | | _ -> false 37 | end : Alcotest.TESTABLE with type t = qt 38 | ) 39 | 40 | let decodes_non_exhaustive_query () = 41 | Alcotest.check my_query "result equality" 42 | (MyQuery.parse (Yojson.Basic.from_string{| { 43 | "dogOrHuman": { 44 | "__typename": "Human", 45 | "name": "Max" 46 | } 47 | } |})) 48 | (object 49 | method dogOrHuman = `Nonexhaustive 50 | end) 51 | 52 | let tests = [ 53 | "Decodes non-exhaustive query", `Quick, decodes_non_exhaustive_query; 54 | ] 55 | -------------------------------------------------------------------------------- /tests_native/variant.ml: -------------------------------------------------------------------------------- 1 | open Test_shared 2 | 3 | module MyQuery = [%graphql {| 4 | mutation { 5 | mutationWithError @bsVariant { 6 | value { 7 | stringField 8 | } 9 | 10 | errors { 11 | field 12 | message 13 | } 14 | } 15 | } 16 | |}] 17 | 18 | type qt = < 19 | mutationWithError : [ 20 | | `Value of < stringField : string > 21 | | `Errors of < field : [ | `FIRST | `SECOND | `THIRD ] ; message : string > array 22 | ] 23 | > 24 | 25 | let my_query = ( 26 | module struct 27 | type t = qt 28 | 29 | let pp formatter (obj: qt) = 30 | Format.fprintf formatter "< mutationWithError = %a >" 31 | (fun formatter -> function 32 | | `Value v -> Format.fprintf formatter "`Value @[<>< stringField = %a >@]" 33 | Format.pp_print_string v#stringField 34 | | `Errors v -> Format.fprintf formatter "`Errors %a" 35 | (print_array (fun formatter v -> 36 | Format.fprintf formatter "< field = %a ; message = %a >" 37 | Format.pp_print_string 38 | (match v#field with | `FIRST -> "FIRST" | `SECOND -> "SECOND" | `THIRD -> "THIRD") 39 | Format.pp_print_string v#message)) 40 | v) 41 | obj#mutationWithError 42 | 43 | let equal (a: qt) (b: qt) = 44 | match a#mutationWithError, b#mutationWithError with 45 | | (`Value a), (`Value b) -> a#stringField = b#stringField 46 | | (`Errors a), (`Errors b) -> 47 | array_zipmap 48 | (fun a b -> a#field = b#field && a#message = b#message) 49 | a b |> Array.for_all (fun x -> x) 50 | | _ -> false 51 | 52 | end : Alcotest.TESTABLE with type t = qt) 53 | 54 | let converts_into_variant () = 55 | Alcotest.check my_query "result equality" 56 | (MyQuery.parse (Yojson.Basic.from_string {| { 57 | "mutationWithError": { 58 | "value": { 59 | "stringField": "a string" 60 | } 61 | } 62 | } |})) 63 | (object 64 | method mutationWithError = `Value (object 65 | method stringField = "a string" 66 | end) 67 | end) 68 | 69 | let tests = [ 70 | "Converts object into variant", `Quick, converts_into_variant; 71 | ] 72 | --------------------------------------------------------------------------------