├── .eslintrc ├── .github └── workflows │ ├── draft-new-release.yml │ ├── publish-new-release.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── gql2postman.js ├── index.js ├── lib ├── assets │ └── gql-generator.js ├── index.js └── util.js ├── npm ├── release.sh ├── test-lint.js ├── test-unit.js └── test.js ├── package-lock.json ├── package.json └── test ├── .eslintrc └── unit ├── converter.test.js ├── fixtures ├── circularInput.graphql ├── custom-queryname.gql ├── invalidNestedArgumentSchema.graphql ├── invalidSchema.json ├── invalidSchemaSDL.graphql ├── issue#10.graphql ├── seflRefDepthUnionSchema.graphql ├── selfRefUnionTypeExample.graphql ├── validNestedArgumentSchema.graphql ├── validNestedSchema.graphql ├── validSchema.json └── validSchemaSDL.graphql ├── gql-generator.test.js └── plugin.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "jsdoc", 4 | "security", 5 | "lodash" 6 | ], 7 | "env": { 8 | "node": true, 9 | "es6": true 10 | }, 11 | "rules": { 12 | // Possible Errors 13 | "for-direction": "error", 14 | "getter-return": "error", 15 | "no-await-in-loop": "error", 16 | "no-compare-neg-zero": "error", 17 | "no-cond-assign": "error", 18 | "no-console": "off", 19 | "no-constant-condition": "error", 20 | "no-control-regex": "error", 21 | "no-debugger": "error", 22 | "no-dupe-args": "error", 23 | "no-dupe-keys": "error", 24 | "no-duplicate-case": "error", 25 | "no-empty": "error", 26 | "no-empty-character-class": "error", 27 | "no-ex-assign": "error", 28 | "no-extra-boolean-cast": "error", 29 | "no-extra-parens": "off", 30 | "no-extra-semi": "error", 31 | "no-func-assign": "error", 32 | "no-inner-declarations": "off", 33 | "no-invalid-regexp": "error", 34 | "no-irregular-whitespace": "error", 35 | "no-obj-calls": "error", 36 | "no-prototype-builtins": "off", 37 | "no-regex-spaces": "error", 38 | "no-sparse-arrays": "error", 39 | "no-template-curly-in-string": "error", 40 | "no-unexpected-multiline": "error", 41 | "no-unreachable": "error", 42 | "no-unsafe-finally": "error", 43 | "no-unsafe-negation": "error", 44 | "use-isnan": "error", 45 | "valid-jsdoc": "off", 46 | "valid-typeof": "error", 47 | 48 | // Best Practices 49 | "accessor-pairs": "error", 50 | "array-callback-return": "off", 51 | "block-scoped-var": "error", 52 | "class-methods-use-this": "error", 53 | "complexity": "off", 54 | "consistent-return": "warn", 55 | "curly": "error", 56 | "default-case": "error", 57 | "dot-location": ["error", "property"], 58 | "dot-notation": "error", 59 | "eqeqeq": "error", 60 | "guard-for-in": "warn", 61 | "no-alert": "error", 62 | "no-caller": "error", 63 | "no-case-declarations": "error", 64 | "no-div-regex": "error", 65 | "no-else-return": "error", 66 | "no-empty-function": "error", 67 | "no-empty-pattern": "error", 68 | "no-eq-null": "error", 69 | "no-eval": "error", 70 | "no-extend-native": "error", 71 | "no-extra-bind": "error", 72 | "no-extra-label": "error", 73 | "no-fallthrough": "error", 74 | "no-floating-decimal": "error", 75 | "no-global-assign": "error", 76 | "no-implicit-coercion": "error", 77 | "no-implicit-globals": "error", 78 | "no-implied-eval": "error", 79 | "no-invalid-this": "error", 80 | "no-iterator": "error", 81 | "no-labels": "error", 82 | "no-lone-blocks": "error", 83 | "no-loop-func": "error", 84 | "no-magic-numbers": "off", 85 | "no-multi-spaces": "error", 86 | "no-multi-str": "error", 87 | "no-new": "error", 88 | "no-new-func": "error", 89 | "no-new-wrappers": "error", 90 | "no-octal": "error", 91 | "no-octal-escape": "error", 92 | "no-param-reassign": "off", 93 | "no-proto": "error", 94 | "no-redeclare": "error", 95 | "no-restricted-properties": "error", 96 | "no-return-assign": "error", 97 | "no-return-await": "error", 98 | "no-script-url": "error", 99 | "no-self-assign": "error", 100 | "no-self-compare": "error", 101 | "no-sequences": "error", 102 | "no-throw-literal": "error", 103 | "no-unmodified-loop-condition": "error", 104 | "no-unused-expressions": "off", 105 | "no-unused-labels": "error", 106 | "no-useless-call": "error", 107 | "no-useless-concat": "error", 108 | "no-useless-escape": "error", 109 | "no-useless-return": "error", 110 | "no-void": "error", 111 | "no-warning-comments": "off", 112 | "no-with": "error", 113 | "prefer-promise-reject-errors": "error", 114 | "radix": "off", 115 | "require-await": "error", 116 | "vars-on-top": "off", 117 | "wrap-iife": "error", 118 | "yoda": "error", 119 | 120 | // Strict Mode 121 | "strict": "off", 122 | 123 | // Variables 124 | "init-declarations": "off", 125 | "no-catch-shadow": "error", 126 | "no-delete-var": "error", 127 | "no-label-var": "error", 128 | "no-restricted-globals": "error", 129 | "no-shadow": "off", 130 | "no-shadow-restricted-names": "error", 131 | "no-undef": "off", 132 | "no-undef-init": "error", 133 | "no-undefined": "off", 134 | "no-unused-vars": "error", 135 | "no-use-before-define": "error", 136 | 137 | // Node.js and CommonJS 138 | "callback-return": "error", 139 | "global-require": "off", 140 | "handle-callback-err": "error", 141 | "no-buffer-constructor": "error", 142 | "no-mixed-requires": "off", 143 | "no-new-require": "off", 144 | "no-path-concat": "error", 145 | "no-process-env": "error", 146 | "no-process-exit": "off", 147 | "no-restricted-modules": "error", 148 | "no-sync": "off", 149 | 150 | // Stylistic Issues 151 | "array-bracket-newline": "off", 152 | "array-bracket-spacing": "off", 153 | "array-element-newline": "off", 154 | "block-spacing": "error", 155 | "brace-style": [2, "stroustrup", { "allowSingleLine": true }], 156 | "camelcase": "off", 157 | "capitalized-comments": "off", 158 | "comma-dangle": ["error", "never"], 159 | "comma-spacing": [2, { "before": false, "after": true }], 160 | "comma-style": ["error", "last"], 161 | "computed-property-spacing": "error", 162 | "consistent-this": "off", 163 | "eol-last": "error", 164 | "func-call-spacing": "error", 165 | "func-name-matching": "off", 166 | "func-names": "off", 167 | "func-style": "off", 168 | "id-blacklist": "error", 169 | "id-length": "off", 170 | "id-match": "error", 171 | "indent": ["error", 2, { 172 | "VariableDeclarator": { "var": 1, "let": 1, "const": 1 }, 173 | "SwitchCase": 1 174 | }], 175 | "jsx-quotes": ["error", "prefer-single"], 176 | "key-spacing": "error", 177 | "keyword-spacing": "error", 178 | "line-comment-position": "off", 179 | "linebreak-style": ["error", "unix"], 180 | "lines-around-comment": ["error", { 181 | "beforeBlockComment": true, 182 | "afterBlockComment": false, 183 | "beforeLineComment": false, 184 | "afterLineComment": false, 185 | "allowBlockStart": true, 186 | "allowBlockEnd": false, 187 | "allowObjectStart": true, 188 | "allowObjectEnd": false, 189 | "allowArrayStart": true, 190 | "allowArrayEnd": false 191 | }], 192 | "max-depth": "error", 193 | "max-len": ["error", { 194 | "code": 120 195 | }], 196 | "max-lines": "off", 197 | "max-nested-callbacks": "error", 198 | "max-params": "off", 199 | "max-statements": "off", 200 | "max-statements-per-line": "off", 201 | "multiline-ternary": "off", 202 | "new-cap": "off", 203 | "new-parens": "error", 204 | "newline-per-chained-call": "off", 205 | "no-array-constructor": "error", 206 | "no-bitwise": "off", 207 | "no-continue": "off", 208 | "no-inline-comments": "off", 209 | "no-lonely-if": "error", 210 | "no-mixed-operators": "off", 211 | "no-mixed-spaces-and-tabs": "error", 212 | "no-multi-assign": "off", 213 | "no-multiple-empty-lines": "error", 214 | "no-negated-condition": "off", 215 | "no-nested-ternary": "off", 216 | "no-new-object": "error", 217 | "no-plusplus": "off", 218 | "no-restricted-syntax": "error", 219 | "no-tabs": "error", 220 | "no-ternary": "off", 221 | "no-trailing-spaces": "error", 222 | "no-underscore-dangle": "off", 223 | "no-unneeded-ternary": "error", 224 | "no-whitespace-before-property": "error", 225 | "nonblock-statement-body-position": "error", 226 | "object-curly-newline": "off", 227 | "object-curly-spacing": "off", 228 | "object-property-newline": "off", 229 | "one-var": ["error", "always"], 230 | "one-var-declaration-per-line": "error", 231 | "operator-assignment": "error", 232 | "operator-linebreak": ["error", "after"], 233 | "padded-blocks": "off", 234 | "padding-line-between-statements": "off", 235 | "quote-props": "off", 236 | "quotes": ["error", "single"], 237 | "require-jsdoc": "warn", 238 | "semi": "error", 239 | "semi-spacing": "error", 240 | "sort-keys": "off", 241 | "sort-vars": "off", 242 | "space-before-blocks": "error", 243 | "space-before-function-paren": "error", 244 | "space-in-parens": "error", 245 | "space-infix-ops": "error", 246 | "space-unary-ops": "error", 247 | "spaced-comment": ["error", "always", { 248 | "block": { 249 | "exceptions": ["!"] 250 | } 251 | }], 252 | "switch-colon-spacing": "error", 253 | "template-tag-spacing": "error", 254 | "unicode-bom": "error", 255 | "wrap-regex": "error", 256 | 257 | // ECMAScript 6 258 | "arrow-body-style": ["error", "always"], 259 | "arrow-parens": ["error", "always"], 260 | "arrow-spacing": "error", 261 | "constructor-super": "error", 262 | "generator-star-spacing": "error", 263 | "no-class-assign": "error", 264 | "no-confusing-arrow": "error", 265 | "no-const-assign": "error", 266 | "no-dupe-class-members": "error", 267 | "no-duplicate-imports": "error", 268 | "no-new-symbol": "error", 269 | "no-restricted-imports": "error", 270 | "no-this-before-super": "error", 271 | "no-useless-computed-key": "error", 272 | "no-useless-constructor": "off", 273 | "no-var": "off", 274 | "object-shorthand": "off", 275 | "prefer-arrow-callback": "off", 276 | "prefer-const": "off", 277 | "prefer-destructuring": "off", 278 | "prefer-numeric-literals": "off", 279 | "prefer-rest-params": "off", 280 | "prefer-spread": "error", 281 | "prefer-template": "off", 282 | "require-yield": "error", 283 | "rest-spread-spacing": "error", 284 | "sort-imports": "off", 285 | "symbol-description": "off", 286 | "template-curly-spacing": "error", 287 | "yield-star-spacing": "error", 288 | 289 | // Lodash 290 | "lodash/callback-binding": "error", 291 | "lodash/collection-method-value": "warn", 292 | "lodash/collection-return": "error", 293 | "lodash/no-double-unwrap": "error", 294 | "lodash/no-extra-args": "error", 295 | "lodash/no-unbound-this": "error", 296 | "lodash/unwrap": "error", 297 | 298 | "lodash/chain-style": ["error", "as-needed"], 299 | "lodash/chaining": ["error", "always", 3], 300 | "lodash/consistent-compose": ["error", "flow"], 301 | "lodash/identity-shorthand": ["error", "always"], 302 | "lodash/import-scope": "off", 303 | "lodash/matches-prop-shorthand": ["error", "always"], 304 | "lodash/matches-shorthand": ["error", "always", 3], 305 | "lodash/no-commit": "error", 306 | "lodash/path-style": ["error", "as-needed"], 307 | "lodash/prefer-compact": "error", 308 | "lodash/prefer-filter": ["off", 3], 309 | "lodash/prefer-flat-map": "error", 310 | "lodash/prefer-invoke-map": "error", 311 | "lodash/prefer-map": "error", 312 | "lodash/prefer-reject": ["error", 3], 313 | "lodash/prefer-thru": "error", 314 | "lodash/prefer-wrapper-method": "error", 315 | "lodash/preferred-alias": "off", 316 | "lodash/prop-shorthand": ["error", "always"], 317 | 318 | "lodash/prefer-constant": "off", 319 | "lodash/prefer-get": ["warn", 4], 320 | "lodash/prefer-includes": ["error", { "includeNative": true }], 321 | "lodash/prefer-is-nil": "error", 322 | "lodash/prefer-lodash-chain": "error", 323 | "lodash/prefer-lodash-method": "off", 324 | "lodash/prefer-lodash-typecheck": "off", 325 | "lodash/prefer-matches": ["off", 3], 326 | "lodash/prefer-noop": "off", 327 | "lodash/prefer-over-quantifier": "warn", 328 | "lodash/prefer-some": "off", 329 | "lodash/prefer-startswith": "off", 330 | "lodash/prefer-times": "off", 331 | 332 | // JsDoc 333 | "jsdoc/check-param-names": "error", 334 | "jsdoc/check-tag-names": "off", 335 | "jsdoc/check-types": "off", 336 | "jsdoc/newline-after-description": "error", 337 | "jsdoc/require-description-complete-sentence": "off", 338 | "jsdoc/require-example": "off", 339 | "jsdoc/require-hyphen-before-param-description": "off", 340 | "jsdoc/require-param": "error", 341 | "jsdoc/require-param-description": "off", 342 | "jsdoc/require-param-type": "off", 343 | "jsdoc/require-returns-description": "off", 344 | "jsdoc/require-returns-type": "error", 345 | 346 | // Security 347 | "security/detect-unsafe-regex": "off", 348 | "security/detect-buffer-noassert": "error", 349 | "security/detect-child-process": "error", 350 | "security/detect-disable-mustache-escape": "error", 351 | "security/detect-eval-with-expression": "error", 352 | "security/detect-no-csrf-before-method-override": "off", 353 | "security/detect-non-literal-fs-filename": "off", 354 | "security/detect-non-literal-regexp": "warn", 355 | "security/detect-non-literal-require": "off", 356 | "security/detect-object-injection": "off", 357 | "security/detect-possible-timing-attacks": "error", 358 | "security/detect-pseudoRandomBytes": "error" 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /.github/workflows/draft-new-release.yml: -------------------------------------------------------------------------------- 1 | name: Draft new release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: The version you want to release. Must be a valid semver version. 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | draft-new-release: 13 | if: startsWith(github.event.inputs.version, 'v') 14 | name: Draft a new release 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v3 23 | 24 | - name: Create release branch 25 | run: git checkout -b release/${{ github.event.inputs.version }} 26 | 27 | - name: Update changelog 28 | uses: thomaseizinger/keep-a-changelog-new-release@1.1.0 29 | with: 30 | version: ${{ github.event.inputs.version }} 31 | 32 | - name: Initialize mandatory git config 33 | run: | 34 | git config user.name "GitHub Actions" 35 | git config user.email noreply@github.com 36 | 37 | - name: Bump version 38 | run: npm version ${{ github.event.inputs.version }} --git-tag-version false 39 | 40 | - name: Commit changelog and manifest files 41 | id: make-commit 42 | run: | 43 | git add CHANGELOG.md package.json package-lock.json 44 | git commit --message "Prepare release ${{ github.event.inputs.version }}" 45 | echo "::set-output name=commit::$(git rev-parse HEAD)" 46 | 47 | - name: Push new branch 48 | run: git push origin release/${{ github.event.inputs.version }} 49 | 50 | - name: Create pull request for master 51 | uses: thomaseizinger/create-pull-request@1.0.0 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | with: 55 | head: release/${{ github.event.inputs.version }} 56 | base: master 57 | title: "Release version ${{ github.event.inputs.version }}" 58 | reviewers: ${{ github.actor }} 59 | body: | 60 | Hi @${{ github.actor }}! 61 | 62 | This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. 63 | I've updated the changelog and bumped the versions in the manifest files in this commit: ${{ steps.make-commit.outputs.commit }}. 64 | 65 | - name: Create pull request for develop 66 | uses: thomaseizinger/create-pull-request@1.0.0 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | with: 70 | head: release/${{ github.event.inputs.version }} 71 | base: develop 72 | title: "Release version ${{ github.event.inputs.version }}" 73 | reviewers: ${{ github.actor }} 74 | body: | 75 | Hi @${{ github.actor }}! 76 | 77 | This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. 78 | I've updated the changelog and bumped the versions in the manifest files in this commit: ${{ steps.make-commit.outputs.commit }}. 79 | -------------------------------------------------------------------------------- /.github/workflows/publish-new-release.yml: -------------------------------------------------------------------------------- 1 | name: "Publish new release" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | types: 8 | - closed 9 | 10 | jobs: 11 | release: 12 | name: Publish new release 13 | runs-on: ubuntu-latest 14 | # only merged pull requests that begin with 'release/' or 'hotfix/' must trigger this job 15 | if: github.event.pull_request.merged == true && 16 | (contains(github.event.pull_request.head.ref, 'release/') || contains(github.event.pull_request.head.ref, 'hotfix/')) 17 | permissions: 18 | contents: write 19 | 20 | steps: 21 | - name: Extract version from branch name (for release branches) 22 | if: contains(github.event.pull_request.head.ref, 'release/') 23 | run: | 24 | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" 25 | VERSION=${BRANCH_NAME#release/} 26 | 27 | echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV 28 | 29 | - name: Extract version from branch name (for hotfix branches) 30 | if: contains(github.event.pull_request.head.ref, 'hotfix/') 31 | run: | 32 | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" 33 | VERSION=${BRANCH_NAME#hotfix/} 34 | 35 | echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV 36 | 37 | - name: Create Release 38 | uses: thomaseizinger/create-release@1.0.0 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | target_commitish: ${{ github.event.pull_request.merge_commit_sha }} 43 | tag_name: ${{ env.RELEASE_VERSION }} 44 | name: ${{ env.RELEASE_VERSION }} 45 | draft: false 46 | prerelease: false 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [develop, master] 6 | pull_request: 7 | 8 | jobs: 9 | Unit-Tests: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [18.x, 20.x, 22.x] 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm ci 21 | - run: npm run test-lint 22 | - run: npm run test-unit 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ### NPM Specific: Disregard recursive project files 2 | ### =============================================== 3 | /.editorconfig 4 | /.gitmodules 5 | /test 6 | 7 | ### Borrowed from .gitignore 8 | ### ======================== 9 | 10 | # Logs 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Prevent IDE stuff 24 | .idea 25 | .vscode 26 | *.sublime-* 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | .coverage 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # Bower dependency directory (https://bower.io/) 41 | bower_components 42 | 43 | # node-waf configuration 44 | .lock-wscript 45 | 46 | # Compiled binary addons (http://nodejs.org/api/addons.html) 47 | build/Release 48 | 49 | # Dependency directories 50 | node_modules/ 51 | jspm_packages/ 52 | 53 | # Typescript v1 declaration files 54 | typings/ 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | 74 | out/ 75 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | - "8" 5 | - "9" 6 | - "node" 7 | - "lts/*" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for graphql-to-postman 2 | 3 | ## [Unreleased] 4 | 5 | ## [v1.0.0] - 2025-03-07 6 | 7 | ### Breaking Changes 8 | 9 | - Drop support for node < v18. 10 | 11 | ## [v0.3.0] - 2024-07-10 12 | 13 | ### Chore 14 | 15 | - Updated postman-collection to v4.4.0. 16 | 17 | ## [v0.2.0] - 2024-07-03 18 | 19 | ### Updated 20 | 21 | - Updated graphql version to v15.8.0. 22 | 23 | ## [v0.1.1] - 2024-04-05 24 | 25 | ### Fixed 26 | 27 | - Fixed an issue where GQL definition of having Union self refs past depth limit was failing with RangeError. 28 | 29 | #### v0.1.0 (June 06, 2023) 30 | 31 | - Added support for CLI usage to convert GraphQL definition to collection with custom depth. 32 | - Added maximum limit to depth allowed via usage of module APIs. 33 | 34 | ### v0.0.12 (March 30, 2023) 35 | 36 | - Fixed issue where conversion failed with type error while resolving non-defined variables. 37 | - Added support for release script. 38 | 39 | ### v0.0.12 (January 9, 2023) 40 | 41 | - Fix for - [#10070](hhttps://github.com/postmanlabs/postman-app-support/issues/10070) Added support for nested lists. 42 | 43 | ### v0.0.11 (Sept 27, 2021) 44 | 45 | - Fix for - [#24](https://github.com/postmanlabs/graphql-to-postman/issues/24) Fixed an issue where nesting was faulty. 46 | 47 | ### v0.0.10 (Sept 27, 2021) 48 | 49 | - Fix for - [#9884](https://github.com/postmanlabs/postman-app-support/issues/9884) Fixed an issue with union types self referencing 50 | 51 | ### v0.0.9 (April 9, 2021) 52 | 53 | - Added the support for changing stack depth if required. 54 | 55 | ### v0.0.8 (March 15, 2021) 56 | 57 | - Fixed issue where error shown was meaningless for incorrect GraphQL SDL. 58 | 59 | ### v0.0.7 (Oct 23, 2020) 60 | 61 | - fix for - [#8863](https://github.com/postmanlabs/postman-app-support/issues/8863) Fixed an issue where custom name for type threw an error. 62 | 63 | ### v0.0.6 (Jul 23, 2020) 64 | 65 | - Fix for circular reference input object types. 66 | - Fix for introspection query response type support. 67 | 68 | ### v0.0.5 (May 15, 2020) 69 | 70 | - Fix for - [#8429](https://github.com/postmanlabs/postman-app-support/issues/8429) [#10](https://github.com/postmanlabs/graphql-to-postman/issues/10) - Schemas with Input type will now be converted successfully. 71 | 72 | #### v0.0.4 (April 29, 2020) 73 | 74 | - Sanitization of options. 75 | - Added a function for getting meta data. 76 | 77 | #### v0.0.3 (March 26, 2020) 78 | 79 | - Fix for empty collection generation for certain queries. 80 | 81 | #### v0.0.2 (December 20, 2019) 82 | 83 | - Support for GraphQL variables. 84 | 85 | #### v0.0.1 (December 10, 2019) 86 | 87 | - Base release 88 | 89 | [Unreleased]: https://github.com/postmanlabs/graphql-to-postman/compare/v1.0.0...HEAD 90 | 91 | [v1.0.0]: https://github.com/postmanlabs/graphql-to-postman/compare/v0.3.0...v1.0.0 92 | 93 | [v0.3.0]: https://github.com/postmanlabs/graphql-to-postman/compare/v0.2.0...v0.3.0 94 | 95 | [v0.2.0]: https://github.com/postmanlabs/graphql-to-postman/compare/v0.1.1...v0.2.0 96 | 97 | [v0.1.1]: https://github.com/postmanlabs/graphql-to-postman/compare/011f91a2fff94f02aeefcfc004a96777a62829bb...v0.1.1 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Postman Logo 2 | 3 | *Supercharge your API workflow.* 4 | *Modern software is built on APIs. Postman helps you develop APIs faster.* 5 | 6 | # GraphQL to Postman Collection 7 | 8 | ![Build Status](https://github.com/postmanlabs/graphql-to-postman/actions/workflows/test.yml/badge.svg) 9 | 10 | ![npm](https://img.shields.io/npm/v/graphql-to-postman.svg) 11 | ![npm](https://img.shields.io/npm/dw/graphql-to-postman.svg) 12 | 13 | #### Contents 14 | 15 | 1. [Getting Started](#getting-started) 16 | 2. [Command Line Interface](#command-line-interface) 17 | 1. [Options](#options) 18 | 2. [Usage](#usage) 19 | 3. [Using the converter as a NodeJS module](#using-the-converter-as-a-nodejs-module) 20 | 1. [Convert Function](#convert) 21 | 2. [Options](#options) 22 | 3. [ConversionResult](#conversionresult) 23 | 4. [Sample usage](#sample-usage) 24 | 5. [Validate function](#validate-function) 25 | 4. [Conversion Schema](#conversion-schema) 26 | 27 | --- 28 | 29 | --- 30 | 31 | 32 | ## 💭 Getting Started 33 | 34 | To use the converter as a Node module, you need to have a copy of the NodeJS runtime. The easiest way to do this is through npm. If you have NodeJS installed you have npm installed as well. 35 | 36 | ```terminal 37 | $ npm install graphql-to-postman 38 | ``` 39 | 40 | If you want to use the converter in the CLI, install it globally with NPM: 41 | 42 | ```terminal 43 | $ npm i -g graphql-to-postman 44 | ``` 45 | 46 | 47 | ## 📖 Command Line Interface 48 | 49 | The converter can be used as a CLI tool as well. The following [command line options](#options) are available. 50 | 51 | `gql2postman [options]` 52 | 53 | ### Options 54 | 55 | - `-s `, `--spec ` 56 | Used to specify the GraphQL specification (file path) which is to be converted 57 | 58 | - `-o `, `--output ` 59 | Used to specify the destination file in which the collection is to be written 60 | 61 | - `-p`, `--pretty` 62 | Used to pretty print the collection object while writing to a file 63 | 64 | - `-i`, `--interface-version` 65 | Specifies the interface version of the converter to be used. Value can be 'v2' or 'v1'. Default is 'v2'. 66 | 67 | - `-O`, `--options` 68 | Used to supply options to the converter, for complete options details see [here](/OPTIONS.md) 69 | 70 | - `-c`, `--options-config` 71 | Used to supply options to the converter through config file, for complete options details see [here](/OPTIONS.md) 72 | 73 | - `-t`, `--test` 74 | Used to test the collection with an in-built sample specification 75 | 76 | - `-v`, `--version` 77 | Specifies the version of the converter 78 | 79 | - `-h`, `--help` 80 | Specifies all the options along with a few usage examples on the terminal 81 | 82 | 83 | ### Usage 84 | 85 | - Takes a specification (spec.yaml) as an input and writes to a file (collection.json) with pretty printing and using provided options 86 | ```terminal 87 | $ gql2postman -s spec.yaml -o collection.json -p -O depth=3,includeDeprecatedFields=true 88 | ``` 89 | 90 | - Takes a specification (spec.yaml) as an input and writes to a file (collection.json) with pretty printing and using provided options via config file 91 | ```terminal 92 | $ gql2postman -s spec.yaml -o collection.json -p -c ./examples/cli-options-config.json 93 | ``` 94 | 95 | - Takes a specification (spec.yaml) as an input and writes to a file (collection.json) with pretty printing and using provided options with larger depth limit 96 | to make sure more detailed and nested data is generated. 97 | ```terminal 98 | $ gql2postman -s spec.yaml -o collection.json -p -O depth=7,includeDeprecatedFields=true,optimizeConversion=false 99 | ``` 100 | 101 | - Testing the converter 102 | ```terminal 103 | $ gql2postman --test 104 | ``` 105 | 106 | 107 | ## 🛠 Using the converter as a NodeJS module 108 | 109 | In order to use the convert in your node application, you need to import the package using `require`. 110 | 111 | ```javascript 112 | var Converter = require('graphql-to-postman') 113 | ``` 114 | 115 | The converter provides the following functions: 116 | 117 | ### Convert 118 | 119 | The convert function takes in your GraphQL schema or SDL and converts it to a Postman collection. 120 | 121 | Signature: `convert (data, options, callback);` 122 | 123 | **data:** 124 | 125 | ```javascript 126 | { type: 'file', data: 'filepath' } 127 | OR 128 | { type: 'string', data: '' } 129 | ``` 130 | 131 | **options:** 132 | ```javascript 133 | { 134 | depth: 4, 135 | includeDeprecatedFields: false, 136 | optimizeConversion: false 137 | } 138 | /* 139 | All three properties are optional. Check the options section below for possible values for each option. 140 | */ 141 | ``` 142 | 143 | **callback:** 144 | ```javascript 145 | function (err, result) { 146 | /* 147 | result = { 148 | result: true, 149 | output: [ 150 | { 151 | type: 'collection', 152 | data: {..collection object..} 153 | } 154 | ] 155 | } 156 | */ 157 | } 158 | ``` 159 | 160 | ### Options 161 | 162 | - `depth` - The number of levels of information that should be returned. (A depth level of “1” returns that object and 163 | its properties. A depth of “2” will return all the nodes connected to the level 1 node, etc.) 164 | 165 | - `includeDeprecatedFields` - Generated queries will include deprecated fields or not. 166 | 167 | - `optimizeConversion` - Optimizes conversion for schemas with complex and nested input objects by reducing the depth to 168 | which input objects are resolved in GraphQL variables. 169 | 170 | ### ConversionResult 171 | 172 | - `result` - Flag responsible for providing a status whether the conversion was successful or not. 173 | 174 | - `reason` - Provides the reason for an unsuccessful conversion, defined only if result if `false`. 175 | 176 | - `output` - Contains an array of Postman objects, each one with a `type` and `data`. The only type currently supported is `collection`. 177 | 178 | 179 | 180 | ### Sample Usage 181 | ```javascript 182 | const fs = require('fs'), 183 | Converter = require('graphql-to-postman'), 184 | gqlData = fs.readFileSync('sample-spec.yaml', {encoding: 'UTF8'}); 185 | 186 | Converter.convert({ type: 'string', data: gqlData }, 187 | {}, (err, conversionResult) => { 188 | if (!conversionResult.result) { 189 | console.log('Could not convert', conversionResult.reason); 190 | } 191 | else { 192 | console.log('The collection object is: ', conversionResult.output[0].data); 193 | } 194 | } 195 | ); 196 | ``` 197 | 198 | ### Validate Function 199 | 200 | The validate function is meant to ensure that the data that is being passed to the [convert function](#convert-function) is a valid JSON object or a valid (YAML/JSON) string. 201 | 202 | The validate function is synchronous and returns a status object which conforms to the following schema 203 | 204 | #### Validation object schema 205 | 206 | ```javascript 207 | { 208 | type: 'object', 209 | properties: { 210 | result: { type: 'boolean'}, 211 | reason: { type: 'string' } 212 | }, 213 | required: ['result'] 214 | } 215 | ``` 216 | 217 | ##### Validation object explanation 218 | - `result` - true if the data is valid GraphQL and can be passed to the convert function 219 | 220 | - `reason` - Provides a reason for an unsuccessful validation of the specification 221 | -------------------------------------------------------------------------------- /bin/gql2postman.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const _ = require('lodash'), 3 | { Command } = require('commander'), 4 | program = new Command(), 5 | Converter = require('../index.js'), 6 | fs = require('fs'), 7 | path = require('path'), 8 | availableOptions = _.map(Converter.getOptions(), 'id'); 9 | 10 | let inputFile, 11 | outputFile, 12 | prettyPrintFlag, 13 | configFile, 14 | definedOptions, 15 | gqlInput, 16 | gqlData; 17 | 18 | /** 19 | * Parses comma separated options mentioned in command args and generates JSON object 20 | * 21 | * @param {String} value - User defined options value 22 | * @returns {Object} - Parsed option in format of JSON object 23 | */ 24 | function parseOptions (value) { 25 | let definedOptions = value.split(','), 26 | parsedOptions = {}; 27 | 28 | _.forEach(definedOptions, (definedOption) => { 29 | let option = definedOption.split('='); 30 | 31 | if (option.length === 2 && _.includes(availableOptions, option[0])) { 32 | try { 33 | // parse parsable data types (e.g. boolean, integer etc) 34 | parsedOptions[option[0]] = JSON.parse(option[1]); 35 | } 36 | catch (e) { 37 | // treat value as string if can not be parsed 38 | parsedOptions[option[0]] = option[1]; 39 | } 40 | } 41 | else { 42 | console.warn('\x1b[33m%s\x1b[0m', 'Warning: Invalid option supplied ', option[0]); 43 | } 44 | }); 45 | 46 | /** 47 | * As v2 interface uses parametersResolution instead of previous requestParametersResolution option, 48 | * override value of parametersResolution if it's not defined and requestParametersResolution is defined 49 | */ 50 | if (_.has(parsedOptions, 'requestParametersResolution') && !_.has(parsedOptions, 'parametersResolution')) { 51 | parsedOptions.parametersResolution = parsedOptions.requestParametersResolution; 52 | } 53 | return parsedOptions; 54 | } 55 | 56 | program 57 | .version(require('../package.json').version, '-v, --version') 58 | .option('-s, --spec ', 'Convert given GraphQL schema to Postman Collection v2.0') 59 | .option('-o, --output ', 'Write the collection to an output file') 60 | .option('-p, --pretty', 'Pretty print the JSON file') 61 | .option('-c, --options-config ', 'JSON file containing Converter options') 62 | .option('-O, --options ', 'comma separated list of options', parseOptions); 63 | 64 | program.on('--help', function () { 65 | /* eslint-disable */ 66 | console.log(' Converts a given GraphQL schema to POSTMAN Collections v2.1.0 '); 67 | console.log(' '); 68 | console.log(' Examples:'); 69 | console.log(' Read spec.yaml or spec.json and store the output in output.json after conversion '); 70 | console.log(' ./gql2postman -s spec.yaml -o output.json '); 71 | console.log(' '); 72 | console.log(' Read spec.yaml or spec.json and print the output to the Console '); 73 | console.log(' ./gql2postman -s spec.yaml '); 74 | console.log(' '); 75 | console.log(' Read spec.yaml or spec.json and print the prettified output to the Console'); 76 | console.log(' ./gql2postman -s spec.yaml -p'); 77 | console.log(' '); 78 | /* eslint-enable */ 79 | }); 80 | 81 | program.parse(process.argv); 82 | 83 | console.log(program.spec); 84 | 85 | inputFile = program.spec; 86 | outputFile = program.output || false; 87 | prettyPrintFlag = program.pretty || false; 88 | configFile = program.optionsConfig || false; 89 | definedOptions = (!(program.options instanceof Array) ? program.options : {}); 90 | gqlInput; 91 | gqlData; 92 | 93 | 94 | /** 95 | * Helper function for the CLI to perform file writes based on the flags 96 | * 97 | * @param {Boolean} prettyPrintFlag - flag for pretty printing while writing the file 98 | * @param {String} file - Destination file to which the write is to be performed 99 | * @param {Object} collection - POSTMAN collection object 100 | * @returns {void} 101 | */ 102 | function writetoFile (prettyPrintFlag, file, collection) { 103 | if (prettyPrintFlag) { 104 | fs.writeFile(file, JSON.stringify(collection, null, 4), (err) => { 105 | if (err) { console.log('Could not write to file', err); } // eslint-disable-line no-console 106 | // eslint-disable-next-line no-console 107 | console.log('\x1b[32m%s\x1b[0m', 'Conversion successful, collection written to file'); 108 | }); 109 | } 110 | else { 111 | fs.writeFile(file, JSON.stringify(collection), (err) => { 112 | if (err) { console.log('Could not write to file', err); } // eslint-disable-line no-console 113 | // eslint-disable-next-line no-console 114 | console.log('\x1b[32m%s\x1b[0m', 'Conversion successful, collection written to file'); 115 | }); 116 | } 117 | } 118 | 119 | /** 120 | * Helper function for the CLI to convert gql data input 121 | * 122 | * @param {String} gqlData - gql data used for conversion input 123 | * @returns {void} 124 | */ 125 | function convert (gqlData) { 126 | let options = {}; 127 | 128 | // apply options from config file if present 129 | if (configFile) { 130 | configFile = path.resolve(configFile); 131 | console.log('Options Config file: ', configFile); // eslint-disable-line no-console 132 | options = JSON.parse(fs.readFileSync(configFile, 'utf8')); 133 | } 134 | 135 | // override options provided via cli 136 | if (definedOptions && !_.isEmpty(definedOptions)) { 137 | options = definedOptions; 138 | } 139 | 140 | // Add __CLI flag for not overriding the depth option 141 | options.__CLI = true; 142 | 143 | Converter.convert({ 144 | type: 'string', 145 | data: gqlData 146 | }, options, (err, status) => { 147 | if (err) { 148 | return console.error(err); 149 | } 150 | if (!status.result) { 151 | console.log(status.reason); // eslint-disable-line no-console 152 | process.exit(0); 153 | } 154 | else if (outputFile) { 155 | let file = path.resolve(outputFile); 156 | console.log('Writing to file: ', prettyPrintFlag, file, status); // eslint-disable-line no-console 157 | writetoFile(prettyPrintFlag, file, status.output[0].data); 158 | } 159 | else { 160 | console.log(status.output[0].data); // eslint-disable-line no-console 161 | process.exit(0); 162 | } 163 | }); 164 | } 165 | 166 | if (inputFile) { 167 | inputFile = path.resolve(inputFile); 168 | console.log('Input file: ', inputFile); // eslint-disable-line no-console 169 | gqlData = fs.readFileSync(inputFile, 'utf8'); 170 | convert(gqlData); 171 | } 172 | else { 173 | program.emit('--help'); 174 | process.exit(0); 175 | } 176 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/index'); 2 | -------------------------------------------------------------------------------- /lib/assets/gql-generator.js: -------------------------------------------------------------------------------- 1 | // Forked from https://github.com/timqian/gql-generator. 2 | 3 | var graphql = require('graphql'), 4 | _ = require('lodash'), 5 | 6 | /** 7 | * Gets depth of stringified array 8 | * 9 | * @param {String} value Stringified array 10 | */ 11 | getDepthOfArray = (value) => { 12 | return (value.match(/\[/g)).length; 13 | }, 14 | 15 | /** 16 | * Generate variables string 17 | * 18 | * @param dict dictionary of arguments 19 | */ 20 | getArgsToVarsStr = (dict) => { 21 | if (typeof dict !== 'object') { 22 | return ''; 23 | } 24 | 25 | return Object.entries(dict) 26 | .map(([varName, arg]) => { return `${arg.name}: $${varName}`; }) 27 | .join(', '); 28 | }, 29 | 30 | /** 31 | * Sanitizes type i.e. Removes '!' and '[]' 32 | * 33 | * @param {object} type GraphQL type 34 | */ 35 | getTypeFromTypeObject = (type) => { 36 | const scalarTypes = ['String', 'ID', 'Boolean']; 37 | type = _.toString(type); 38 | 39 | let matchedType; 40 | 41 | scalarTypes.every((scalarType) => { 42 | if (type.includes(scalarType)) { 43 | matchedType = scalarType; 44 | 45 | return false; 46 | } 47 | 48 | return true; 49 | }); 50 | 51 | if (matchedType) { 52 | return matchedType; 53 | } 54 | // If type is not from the sclar types but is a user defined type 55 | else if (type.includes('[')) { 56 | const getSplitString = (ArrayDepth, identifier) => { 57 | let splitString = identifier; 58 | 59 | while (ArrayDepth > 1) { 60 | splitString += identifier; 61 | 62 | ArrayDepth--; 63 | } 64 | 65 | return splitString; 66 | }, 67 | depth = getDepthOfArray(type), 68 | splitStartIdentifier = getSplitString(depth, '['), 69 | splitEndIdentifier = getSplitString(depth, ']'); 70 | 71 | type = type.split(splitStartIdentifier)[1].split(splitEndIdentifier)[0]; 72 | } 73 | 74 | return type.split('!')[0]; 75 | }, 76 | 77 | /** 78 | * Returns default for a particular type 79 | * 80 | * @param {String} type Scalar type 81 | */ 82 | getDefaultForType = (type) => { 83 | switch (type) { 84 | case 'String': 85 | return ''; 86 | case 'Int': 87 | return 0; 88 | case 'Int!': 89 | return 0; 90 | case 'Boolean': 91 | return true; 92 | case 'ID': 93 | return 0; 94 | case 'object': 95 | return {}; 96 | case '[]': 97 | return []; 98 | default: 99 | return ''; 100 | } 101 | }, 102 | 103 | /** 104 | * Generate types string 105 | * 106 | * @param dict dictionary of arguments 107 | */ 108 | getVarsToTypesStr = (dict) => { 109 | if (typeof dict !== 'object') { 110 | return ''; 111 | } 112 | 113 | return Object.entries(dict) 114 | .map(([varName, arg]) => { 115 | return `$${varName}: ${arg.type}`; 116 | }) 117 | .join(', '); 118 | }; 119 | 120 | /** 121 | * Compile arguments dictionary for a field 122 | * 123 | * @param field current field object 124 | * @param duplicateArgCounts map for deduping argument name collisions 125 | * @param allArgsDict dictionary of all arguments 126 | */ 127 | function getFieldArgsDict (field, duplicateArgCounts, allArgsDict = {}) { 128 | return field.args.reduce((argumentDict, arg) => { 129 | if (arg.name in duplicateArgCounts) { 130 | const index = duplicateArgCounts[arg.name] + 1; 131 | duplicateArgCounts[arg.name] = index; 132 | argumentDict[`${arg.name}${index}`] = arg; 133 | } 134 | // Dedupe arguments 135 | else if (allArgsDict[arg.name]) { 136 | duplicateArgCounts[arg.name] = 1; 137 | argumentDict[`${arg.name}1`] = arg; 138 | } 139 | else { 140 | argumentDict[arg.name] = arg; 141 | } 142 | return argumentDict; 143 | }, {}); 144 | } 145 | 146 | /** This function recursively resolves variable types and returns a default value for that type. 147 | * 148 | * @param {object} type 149 | * @param {object} gqlSchema 150 | * @param {Number} stack 151 | * @param {Number} stackLimit 152 | */ 153 | function resolveVariableType (type, gqlSchema, stack = 0, stackLimit = 4) { 154 | var fieldObj = {}, 155 | fields; 156 | stack++; 157 | const argType = gqlSchema.getType(getTypeFromTypeObject(type)), 158 | typeString = _.toString(type); 159 | 160 | if (graphql.isInputObjectType(argType)) { 161 | fields = argType.getFields(); 162 | typeof fields === 'object' && Object.keys(fields).forEach((field) => { 163 | if (fields[field].type === type) { 164 | fieldObj[field] = ''; 165 | } 166 | else if (stack <= stackLimit) { 167 | if (typeString.includes('[') && typeString.includes(']')) { 168 | fieldObj[field] = [resolveVariableType(fields[field].type, gqlSchema, stack, stackLimit)]; 169 | } 170 | fieldObj[field] = resolveVariableType(fields[field].type, gqlSchema, stack, stackLimit); 171 | } 172 | }); 173 | return fieldObj; 174 | } 175 | 176 | // If type is an array, get the default value for the type and return the desired depth nested array 177 | if (typeString.includes('[')) { 178 | const defaultForType = getDefaultForType(_.toString(argType)); 179 | 180 | let depth = getDepthOfArray(typeString), 181 | result = defaultForType; 182 | 183 | // Create nested array w.r.t the depth of type 184 | while (depth > 0) { 185 | result = [result]; 186 | depth--; 187 | } 188 | 189 | return result; 190 | } 191 | 192 | return getDefaultForType(_.toString(argType)); 193 | } 194 | 195 | module.exports = { 196 | /** Generates queries from GraphQL schema 197 | * 198 | * @param {String} gqlSchema - String of GraphQL Schema in SDL format 199 | * @param {Object} options - Options 200 | * options.depth - depth to which query should be generated 201 | * options.variableDepth - determines the depth to which variables objects should be resolved 202 | * options.includeDeprecatedFields - Deprecated fields to be included or not. 203 | */ 204 | schemaToQuery (gqlSchema, options) { 205 | const output = {}; 206 | let depthLimit = options.depth, 207 | includeDeprecatedFields = options.includeDeprecatedFields || false, 208 | stackLimit = options.variableDepth || 4; 209 | 210 | /** 211 | * Generate the query for the specified field 212 | * 213 | * @param curName name of the current field 214 | * @param curParentType parent type of the current field 215 | * @param curParentName parent name of the current field 216 | * @param argumentsDict dictionary of arguments from all fields 217 | * @param duplicateArgCounts map for deduping argument name collisions 218 | * @param crossReferenceKeyList list of the cross reference 219 | * @param curDepth current depth of field 220 | */ 221 | function generateQuery ( 222 | curName, 223 | curParentType, 224 | curParentName, 225 | argumentsDict = {}, 226 | duplicateArgCounts = {}, 227 | crossReferenceKeyList = {}, 228 | curDepth = 1) { 229 | 230 | const field = gqlSchema.getType(curParentType).getFields()[curName], 231 | curTypeName = field.type.inspect().replace(/[[\]!]/g, ''), 232 | curType = gqlSchema.getType(curTypeName), 233 | 234 | /** 235 | * This is reference key that's used to determine if current element was already visited once, 236 | * it's done to avoid the circular refs inclusion in generated body. 237 | */ 238 | crossReferenceKey = curName; 239 | 240 | let queryStr = '', 241 | childQuery = ''; 242 | 243 | if (curType.getFields) { 244 | if ((crossReferenceKeyList.hasOwnProperty(crossReferenceKey) && crossReferenceKeyList[crossReferenceKey]) || 245 | curDepth > depthLimit 246 | ) { 247 | crossReferenceKeyList[crossReferenceKey] = false; 248 | return ''; 249 | } 250 | crossReferenceKeyList[crossReferenceKey] = true; 251 | 252 | let childKeys = Object.keys(curType.getFields() || {}); 253 | childQuery = childKeys 254 | .filter((fieldName) => { 255 | /* Exclude deprecated fields */ 256 | const fieldSchema = gqlSchema.getType(curType).getFields()[fieldName]; 257 | return includeDeprecatedFields || !fieldSchema.isDeprecated; 258 | }) 259 | .map((cur) => { 260 | return generateQuery(cur, curType, curName, argumentsDict, duplicateArgCounts, 261 | crossReferenceKeyList, curDepth + 1).queryStr; 262 | }) 263 | .filter((cur) => { 264 | return cur; 265 | }) 266 | .join('\n'); 267 | } 268 | 269 | if (!(curType.getFields && !childQuery)) { 270 | queryStr = `${' '.repeat(curDepth)}${field.name}`; 271 | if (field.args.length > 0) { 272 | const dict = getFieldArgsDict(field, duplicateArgCounts, argumentsDict); 273 | Object.assign(argumentsDict, dict); 274 | 275 | queryStr += ` (${getArgsToVarsStr(dict)})`; 276 | } 277 | if (childQuery) { 278 | queryStr += ` {\n${childQuery}\n${' '.repeat(curDepth)}}`; 279 | } 280 | } 281 | 282 | /* Union types */ 283 | if (curType.astNode && curType.astNode.kind === 'UnionTypeDefinition') { 284 | 285 | /* Make sure UnionTypeDefinition are also not circularly referenced */ 286 | if ((crossReferenceKeyList.hasOwnProperty(crossReferenceKey) && crossReferenceKeyList[crossReferenceKey]) || 287 | curDepth > depthLimit 288 | ) { 289 | crossReferenceKeyList[crossReferenceKey] = false; 290 | return ''; 291 | } 292 | crossReferenceKeyList[crossReferenceKey] = true; 293 | 294 | const types = curType.getTypes(); 295 | if (types && types.length) { 296 | const indent = `${' '.repeat(curDepth)}`, 297 | fragIndent = `${' '.repeat(curDepth + 1)}`; 298 | queryStr += ' {\n'; 299 | 300 | for (let i = 0, len = types.length; i < len; i++) { 301 | const valueTypeName = types[i], 302 | valueType = gqlSchema.getType(valueTypeName), 303 | unionChildQuery = Object.keys(valueType.getFields() || {}) 304 | .map((cur) => { 305 | // Don't genrate query fields that have self referencing 306 | if (cur === curName) { 307 | // add a comment indicating which field was self referenced. 308 | const comment = `${' '.repeat(curDepth + 2)}# self reference detected\n` + 309 | `${' '.repeat(curDepth + 2)}# skipping "${cur}"`; 310 | 311 | return comment; 312 | } 313 | return generateQuery(cur, valueType, curName, argumentsDict, duplicateArgCounts, 314 | crossReferenceKeyList, curDepth + 2).queryStr; 315 | }) 316 | .filter((cur) => { return cur; }) 317 | .join('\n'); 318 | 319 | queryStr += `${fragIndent}... on ${valueTypeName} {\n${unionChildQuery}\n${fragIndent}}\n`; 320 | } 321 | queryStr += `${indent}}`; 322 | } 323 | } 324 | crossReferenceKeyList[crossReferenceKey] = false; 325 | return { queryStr, argumentsDict }; 326 | } 327 | 328 | /** 329 | * Generate the query for the specified field 330 | * 331 | * @param obj one of the root objects(Query, Mutation, Subscription) 332 | * @param description description of the current object 333 | */ 334 | function generateQueries (obj, description) { 335 | let currentObject = {}, 336 | outputFolderName; 337 | switch (description) { 338 | case 'Mutation': 339 | outputFolderName = 'mutations'; 340 | break; 341 | case 'Query': 342 | outputFolderName = 'queries'; 343 | break; 344 | case 'Subscription': 345 | outputFolderName = 'subscriptions'; 346 | break; 347 | default: 348 | console.log('[gqlg warning]:', 'description is required'); 349 | } 350 | 351 | typeof obj === 'object' && Object.keys(obj).forEach((type) => { 352 | 353 | let field, 354 | newDescription; 355 | 356 | // The name of the mutationType can be anything other than 'Mutation' 357 | // Handle for each type separately and use the new description to traverse through 358 | // the fields. 359 | if (description === 'Mutation') { 360 | field = gqlSchema._mutationType._fields[type]; 361 | newDescription = gqlSchema._mutationType.name; 362 | } 363 | else if (description === 'Query') { 364 | field = gqlSchema._queryType._fields[type]; 365 | newDescription = gqlSchema._queryType.name; 366 | } 367 | else if (description === 'Subscription') { 368 | field = gqlSchema._subscriptionType._fields[type]; 369 | newDescription = gqlSchema._subscriptionType.name; 370 | } 371 | 372 | /* Only process non-deprecated queries/mutations: */ 373 | if (includeDeprecatedFields || !field.isDeprecated) { 374 | const queryResult = generateQuery(type, newDescription), 375 | varsToTypesStr = getVarsToTypesStr(queryResult.argumentsDict); 376 | 377 | /* Generate variables Object from argumentDict */ 378 | var variables = {}; 379 | 380 | if (typeof queryResult.argumentsDict === 'object') { 381 | Object.entries(queryResult.argumentsDict).map(([varName, arg]) => { 382 | variables[varName] = resolveVariableType(arg.type, gqlSchema, 0, stackLimit); 383 | }); 384 | } 385 | 386 | let query = queryResult.queryStr; 387 | // here the `description` is used to construct the actual queries 388 | // Here has to be one of query, mutation, or subscription. 389 | query = `${description.toLowerCase()} ${type}${varsToTypesStr ? ` (${varsToTypesStr}) ` : ' '}{\n${query}\n}`; 390 | currentObject[type] = { 391 | query: query, 392 | variables: JSON.stringify(variables, null, 2) 393 | }; 394 | } 395 | }); 396 | output[outputFolderName] = currentObject; 397 | } 398 | 399 | if (gqlSchema.getMutationType()) { 400 | generateQueries(gqlSchema.getMutationType().getFields(), 'Mutation'); 401 | } 402 | 403 | if (gqlSchema.getQueryType()) { 404 | generateQueries(gqlSchema.getQueryType().getFields(), 'Query'); 405 | } 406 | 407 | if (gqlSchema.getSubscriptionType()) { 408 | generateQueries(gqlSchema.getSubscriptionType().getFields(), 'Subscription'); 409 | } 410 | 411 | return output; 412 | } 413 | }; 414 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var schemaToQuery = require('./assets/gql-generator').schemaToQuery, 2 | converter, 3 | util = require('./util'), 4 | { Collection } = require('postman-collection/lib/collection/collection'), 5 | { ItemGroup } = require('postman-collection/lib/collection/item-group'), 6 | { Variable } = require('postman-collection/lib/collection/variable'), 7 | _ = require('lodash'), 8 | graphql = require('graphql'); 9 | 10 | const DEFAULT_NAME = 'Postman Collection (from GraphQL)', 11 | MAX_ALLOWED_DEPTH = 4; 12 | 13 | /** 14 | * This function overrides options. If option is not present than default value from getOptions() will be used. 15 | * It also checks if availableOptions are present then option should be one of them, otherwise default will be used. 16 | * And checks for type of option if it does not match then default is used. 17 | * 18 | * @param {Array} options - Array of option objects 19 | * @returns {Object} overridden options 20 | */ 21 | function overrideOptions (options) { 22 | var optionsToOverride = converter.getOptions(); 23 | 24 | _.forEach(optionsToOverride, (option) => { 25 | if (!_.has(options, option.id)) { 26 | options[option.id] = option.default; 27 | } 28 | else if (option.availableOptions) { 29 | if (!_.includes(option.availableOptions, options[option.id])) { 30 | options[option.id] = option.default; 31 | } 32 | } 33 | else if (typeof options[option.id] !== option.type) { 34 | if (!typeof parseInt(options[option.id]) === option.type) { 35 | options[option.id] = option.default; 36 | } 37 | } 38 | }); 39 | 40 | // Override depth to MAX_ALLOWED_DEPTH if more 41 | if (!options.__CLI && options.depth > MAX_ALLOWED_DEPTH) { 42 | options.depth = MAX_ALLOWED_DEPTH; 43 | } 44 | 45 | return options; 46 | } 47 | 48 | /** Function for analyzing the spec. 49 | * Currently we are only finding out the size of the spec. 50 | * 51 | * Later this could be used to judge the complexity of the spec too. 52 | * 53 | * @param {Object} data 54 | */ 55 | function analyzeSpec (data) { 56 | let specString = JSON.stringify(data), 57 | size = Buffer.byteLength(specString, 'utf8') / (1024 * 1024); 58 | 59 | return { 60 | size 61 | }; 62 | } 63 | 64 | /** Returns valid GraphQLSchemaObject from input else returns false 65 | * 66 | * @param {String} data String of input data 67 | * 68 | */ 69 | function getGraphQLSchemaObject (data) { 70 | var gqlSchemaObj, 71 | introspectionObject; 72 | 73 | try { 74 | // Check if valid JSON object and generate SDL 75 | introspectionObject = util.asJson(data); 76 | // introspection query result has the following structure: 77 | // https://graphql.org/learn/introspection/ 78 | // The user can directly provide this 79 | // { 80 | // data: { 81 | // // Or may provide this. 82 | // __schema: { 83 | 84 | // } 85 | // } 86 | // } 87 | 88 | // Either way, the function `graphql.buildClientSchema` 89 | // only takes in the value of `data` from above. 90 | if (introspectionObject.data) { 91 | introspectionObject = introspectionObject.data; 92 | } 93 | gqlSchemaObj = graphql.buildClientSchema(introspectionObject); 94 | } 95 | catch (err) { 96 | try { 97 | // if not JSON, data must be SDL, check if it is a valid GraphQL SDL 98 | gqlSchemaObj = graphql.buildSchema(data); 99 | } 100 | catch (error) { 101 | return false; 102 | } 103 | } 104 | 105 | return gqlSchemaObj; 106 | } 107 | 108 | converter = { 109 | 110 | /** Returns meta data for a graphql schema 111 | * 112 | * @param {Object} input Input 113 | * @param {String} input.type Type of input 'file' / 'string' 114 | * @param {String} input.data Input data 115 | * @param {Function} cb Callback 116 | */ 117 | getMetaData: function (input, cb) { 118 | if (input.data) { 119 | return cb(null, { 120 | result: true, 121 | name: DEFAULT_NAME, 122 | output: [{ 123 | type: 'collection', 124 | name: DEFAULT_NAME 125 | }] 126 | }); 127 | } 128 | return cb(null, { 129 | result: false, 130 | reason: 'Invalid input data.' 131 | }); 132 | }, 133 | 134 | /** Validate function for validating schema is graphql or not. 135 | * 136 | * @param input 137 | * @param input.type Type of input 'file' / 'string' 138 | * @param input.data Input data to be validated 139 | */ 140 | validate: function (input) { 141 | var gqlSchemaObj, 142 | data; 143 | 144 | if (input.type === 'file') { 145 | try { 146 | data = util.getDataFromFile(input.data); 147 | } 148 | catch (error) { 149 | return { 150 | result: false 151 | }; 152 | } 153 | } 154 | else if (input.type === 'string') { 155 | data = input.data; 156 | } 157 | else { 158 | return { result: false }; 159 | } 160 | 161 | // Try generting SDL from the data / validate SDL. 162 | gqlSchemaObj = getGraphQLSchemaObject(data); 163 | 164 | if (gqlSchemaObj && !gqlSchemaObj._mutationType && !gqlSchemaObj._queryType && !gqlSchemaObj._subscriptionType) { 165 | return { 166 | result: false, 167 | reason: 'Specification doesn\'t contain valid mutation, query or subscription type' 168 | }; 169 | } 170 | return gqlSchemaObj ? { result: true } : { result: false }; 171 | }, 172 | 173 | /** 174 | * Used in order to get additional options for importing of GraphQL 175 | * 176 | * @module getOptions 177 | * 178 | * @returns {Array} Options specific to generation of postman collection from RAML 1.0 schema 179 | */ 180 | getOptions: function () { 181 | return [ 182 | { 183 | name: 'Include deprecated fields', 184 | id: 'includeDeprecatedFields', 185 | type: 'boolean', 186 | default: false, 187 | description: 'Generated queries will include deprecated fields', 188 | external: true 189 | }, 190 | { 191 | name: 'Query depth level', 192 | id: 'depth', 193 | type: 'number', 194 | default: 1, 195 | description: 'The number of levels of information that should be returned. (A depth level of “1” returns that' + 196 | ' object and its properties. A depth of “2” will return all the nodes connected to the level 1 node, etc.).' + 197 | ' Maximum depth supported is 4, to work with more depth checkout' + 198 | ' [CLI usage](https://github.com/postmanlabs/graphql-to-postman#-command-line-interface).', 199 | external: true 200 | }, 201 | { 202 | name: 'Optimize conversion', 203 | id: 'optimizeConversion', 204 | type: 'boolean', 205 | default: false, 206 | description: 'Optimizes conversion for schemas with complex and nested input objects by reducing the depth to' + 207 | ' which input objects are resolved in GraphQL variables.', 208 | external: true 209 | }, 210 | { 211 | name: 'CLI flag', 212 | id: '__CLI', 213 | type: 'boolean', 214 | default: false, 215 | description: 'Indicates if current environment running env is CLI or not.', 216 | external: false 217 | } 218 | ]; 219 | }, 220 | 221 | /** Converts GraphQL schema into a Postman Collection. 222 | * 223 | * @param {Object} input Input 224 | * @param {String} input.type Type of input 'file' / 'string' 225 | * @param {String} input.data Input data 226 | * @param {Object} options Options for configuration 227 | * @param {Function} callback Callback 228 | */ 229 | convert: function (input, options, callback) { 230 | var gqlSchemaObj, 231 | data, 232 | collection = new Collection(), 233 | analysis, 234 | queryCollection; 235 | 236 | // default options for unselected options. 237 | options = overrideOptions(options); 238 | 239 | collection.name = DEFAULT_NAME; 240 | 241 | if (input.type === 'file') { 242 | try { 243 | data = util.getDataFromFile(input.data); 244 | } 245 | catch (e) { 246 | return callback(null, { 247 | result: false, 248 | reason: e.message 249 | }); 250 | } 251 | } 252 | else if (input.type === 'string') { 253 | data = input.data; 254 | } 255 | else { 256 | return callback(null, { 257 | result: false, 258 | reason: 'Input type not supported.' 259 | }); 260 | } 261 | 262 | // Assuming data is valid 263 | gqlSchemaObj = getGraphQLSchemaObject(data); 264 | 265 | try { 266 | if (gqlSchemaObj) { 267 | analysis = analyzeSpec(gqlSchemaObj); 268 | 269 | // default variable depth should be 4. 270 | options.variableDepth = MAX_ALLOWED_DEPTH; 271 | 272 | // if size is more than 5MB 273 | // OR optimizeConversion is true 274 | // reduce the variable depth to 2 275 | if (analysis.size >= 5 || options.optimizeConversion) { 276 | options.variableDepth = 2; 277 | } 278 | 279 | queryCollection = schemaToQuery(gqlSchemaObj, options); 280 | } 281 | else { 282 | return callback(null, { 283 | result: false, 284 | reason: 'Invalid Data.' 285 | }); 286 | } 287 | 288 | _.forEach(queryCollection, (value, key) => { 289 | var folder = new ItemGroup(); 290 | folder.name = key; 291 | _.forEach(value, (graphqlObj, name) => { 292 | var request = {}, 293 | item = {}; 294 | 295 | item.name = name; 296 | request.url = '{{url}}'; 297 | request.method = 'POST'; 298 | request.body = { 299 | mode: 'graphql', 300 | graphql: graphqlObj 301 | }; 302 | item.request = request; 303 | folder.items.add(item); 304 | }); 305 | collection.items.add(folder); 306 | }); 307 | 308 | collection.variables.add(new Variable({ 309 | id: 'url', 310 | value: '', 311 | description: 'URL for the request.' 312 | })); 313 | 314 | return callback(null, { 315 | result: true, 316 | output: [{ 317 | type: 'collection', 318 | data: collection.toJSON() 319 | }] 320 | }); 321 | } 322 | catch (e) { 323 | if (e.message) { 324 | return callback(null, { 325 | result: false, 326 | reason: 'Could not generate collection. Error Message:' + e.message 327 | }); 328 | } 329 | return callback(e); 330 | } 331 | } 332 | }; 333 | 334 | module.exports = converter; 335 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | module.exports = { 3 | asJson: function (spec) { 4 | try { 5 | return JSON.parse(spec); 6 | } 7 | catch (jsonException) { 8 | throw new SyntaxError(`Specification is not a valid JSON. ${jsonException}`); 9 | } 10 | }, 11 | 12 | getDataFromFile: function (path) { 13 | return fs.readFileSync(path).toString(); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /npm/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------------------------------------------------------------- 3 | # This script is intended to automate release process. 4 | # Explanation: This script will pull all latest changes from master and develop and checkout release branch from develop 5 | # with specified version and then bump up package version and add CHANGELOGS (input required) and commit them. After 6 | # that merge release branch into master and develop with appropriate tags. 7 | # 8 | # Example: "npm run release 1.2.3" 9 | # ---------------------------------------------------------------------------------------------------------------------- 10 | 11 | # Stop on first error 12 | set -e; 13 | 14 | # Ensure that the provided version is in valid semver format 15 | if [[ ! $1 =~ ^v?[0-9]+(\.[0-9]+){2}(-[a-z]+\.\d+)?$ ]]; then 16 | echo "A valid version must be provided as the first argument."; 17 | exit 1; 18 | fi 19 | 20 | ver=${1/v/}; # Strip the leading v from the version (if it exists) 21 | msg=$2; 22 | 23 | [[ -z $msg ]] && msg="Released v${ver}"; 24 | 25 | # Update the master branch to the latest 26 | git checkout master; 27 | git pull origin master; 28 | 29 | # Update develop to the latest, and create a release brach off of it. 30 | git checkout develop; 31 | git pull origin develop; 32 | git checkout -b release/$ver; 33 | 34 | # Bump version in package.json, but do not create a git tag 35 | npm version $ver --no-git-tag-version; 36 | 37 | # Inject the current release version and date into the CHANGELOG file 38 | sed -i "" "3i\\ 39 | #### v${ver} (`date '+%B %d, %Y'`)\\ 40 | \\ 41 | " CHANGELOG.md; 42 | 43 | # Find all commits between the HEAD on develop and the latest tag on master, and pipe their messages into the clipboard 44 | git log $(git describe --tags master --abbrev=0)..HEAD --merges --pretty=format:'* %s' | pbcopy; 45 | 46 | # Provision manual intervention for CHANGELOG.md 47 | vi CHANGELOG.md 48 | 49 | # Create the release 50 | git add CHANGELOG.md package.json; 51 | [[ -f package-lock.json ]] && git add package-lock.json; 52 | git commit -am "$msg"; 53 | 54 | # Merge the release branch into develop and push 55 | git checkout develop; 56 | git merge --no-ff release/$ver; 57 | git push origin develop; 58 | 59 | # Merge the release branch into master, create a tag and push 60 | git checkout master; 61 | git merge --no-ff release/$ver; 62 | git tag -a "$ver" -m "$msg"; 63 | git push origin master --follow-tags; 64 | 65 | # Move back to develop 66 | git checkout develop; 67 | git branch -d release/$ver; 68 | 69 | unset msg ver; 70 | -------------------------------------------------------------------------------- /npm/test-lint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('shelljs/global'); 3 | 4 | var chalk = require('chalk'), 5 | async = require('async'), 6 | ESLintCLIEngine = require('eslint').CLIEngine, 7 | 8 | /** 9 | * The list of source code files / directories to be linted. 10 | * 11 | * @type {Array} 12 | */ 13 | LINT_SOURCE_DIRS = [ 14 | './lib', 15 | './bin', 16 | './test', 17 | './examples/*.js', 18 | './npm/*.js', 19 | './index.js' 20 | ]; 21 | 22 | module.exports = function (exit) { 23 | // banner line 24 | console.info(chalk.yellow.bold('\nLinting files using eslint...')); 25 | 26 | async.waterfall([ 27 | 28 | /** 29 | * Instantiates an ESLint CLI engine and runs it in the scope defined within LINT_SOURCE_DIRS. 30 | * 31 | * @param {Function} next - The callback function whose invocation marks the end of the lint test run. 32 | * @returns {*} 33 | */ 34 | function (next) { 35 | next(null, (new ESLintCLIEngine()).executeOnFiles(LINT_SOURCE_DIRS)); 36 | }, 37 | 38 | /** 39 | * Processes a test report from the Lint test runner, and displays meaningful results. 40 | * 41 | * @param {Object} report - The overall test report for the current lint test. 42 | * @param {Object} report.results - The set of test results for the current lint run. 43 | * @param {Function} next - The callback whose invocation marks the completion of the post run tasks. 44 | * @returns {*} 45 | */ 46 | function (report, next) { 47 | var errorReport = ESLintCLIEngine.getErrorResults(report.results); 48 | // log the result to CLI 49 | console.info(ESLintCLIEngine.getFormatter()(report.results)); 50 | // log the success of the parser if it has no errors 51 | (errorReport && !errorReport.length) && console.info(chalk.green('eslint ok!')); 52 | // ensure that the exit code is non zero in case there was an error 53 | next(Number(errorReport && errorReport.length) || 0); 54 | } 55 | ], exit); 56 | }; 57 | 58 | // ensure we run this script exports if this is a direct stdin.tty run 59 | !module.parent && module.exports(exit); 60 | -------------------------------------------------------------------------------- /npm/test-unit.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-env node, es6 */ 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | // This script is intended to execute all unit tests. 5 | // --------------------------------------------------------------------------------------------------------------------- 6 | 7 | require('shelljs/global'); 8 | 9 | // set directories and files for test and coverage report 10 | var path = require('path'), 11 | 12 | NYC = require('nyc'), 13 | chalk = require('chalk'), 14 | recursive = require('recursive-readdir'), 15 | 16 | COV_REPORT_PATH = '.coverage', 17 | SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'unit'); 18 | 19 | module.exports = function (exit) { 20 | // banner line 21 | console.info(chalk.yellow.bold('Running unit tests using mocha on node...')); 22 | 23 | test('-d', COV_REPORT_PATH) && rm('-rf', COV_REPORT_PATH); 24 | mkdir('-p', COV_REPORT_PATH); 25 | 26 | var Mocha = require('mocha'), 27 | nyc = new NYC({ 28 | reportDir: COV_REPORT_PATH, 29 | tempDirectory: COV_REPORT_PATH, 30 | reporter: ['text', 'lcov', 'text-summary'], 31 | exclude: ['config', 'test'], 32 | hookRunInContext: true, 33 | hookRunInThisContext: true 34 | }); 35 | 36 | nyc.wrap(); 37 | // add all spec files to mocha 38 | recursive(SPEC_SOURCE_DIR, function (err, files) { 39 | if (err) { console.error(err); return exit(1); } 40 | 41 | var mocha = new Mocha({ timeout: 1000 * 60 }); 42 | 43 | files.filter(function (file) { // extract all test files 44 | return (file.substr(-8) === '.test.js'); 45 | }).forEach(mocha.addFile.bind(mocha)); 46 | 47 | mocha.run(function (runError) { 48 | runError && console.error(runError.stack || runError); 49 | 50 | nyc.reset(); 51 | nyc.writeCoverageFile(); 52 | nyc.report(); 53 | exit(runError ? 1 : 0); 54 | }); 55 | }); 56 | }; 57 | 58 | // ensure we run this script exports if this is a direct stdin.tty run 59 | !module.parent && module.exports(exit); 60 | -------------------------------------------------------------------------------- /npm/test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var chalk = require('chalk'), 3 | exit = require('shelljs').exit, 4 | prettyms = require('pretty-ms'), 5 | startedAt = Date.now(), 6 | name = require('../package.json').name; 7 | 8 | require('async').series([ 9 | require('./test-lint'), 10 | require('./test-unit') 11 | // Add a separate folder for every new suite of tests 12 | // require('./test-unit') 13 | // require('./test-browser') 14 | // require('./test-integration') 15 | ], function (code) { 16 | // eslint-disable-next-line max-len 17 | console.info(chalk[code ? 'red' : 'green'](`\n${name}: duration ${prettyms(Date.now() - startedAt)}\n${name}: ${code ? 'not ok' : 'ok'}!`)); 18 | exit(code && (typeof code === 'number' ? code : 1) || 0); 19 | }); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-to-postman", 3 | "version": "1.0.0", 4 | "description": "Generates a Postman Collection from a GraphQL schema.", 5 | "main": "index.js", 6 | "bin": { 7 | "gql2postman": "./bin/gql2postman.js" 8 | }, 9 | "scripts": { 10 | "test": "node npm/test.js", 11 | "test-lint": "node npm/test-lint.js", 12 | "test-unit": "node npm/test-unit.js", 13 | "release": "npm/release.sh" 14 | }, 15 | "com_postman_plugin": { 16 | "plugin_type": "importer", 17 | "name": "GraphQL", 18 | "source_format": "GRAPHQL", 19 | "source_format_name": "GraphQL", 20 | "sample_input": { 21 | "type": "string", 22 | "data": "type Rocket { id: ID! name: String type: String } type User { id: ID! email: String! trips: [Launch]! } type Mission { name: String missionPatch(size: PatchSize): String } enum PatchSize { SMALL LARGE } type Mutation { # if false, booking trips failed -- check errors bookTrips(launchIds: [ID]!): TripUpdateResponse! # if false, cancellation failed -- check errors cancelTrip(launchId: ID!): TripUpdateResponse! login(email: String): String # login token } type TripUpdateResponse { success: Boolean! message: String launches: [Launch] } type Launch { id: ID! site: String mission: Mission rocket: Rocket isBooked: Boolean! } type Query { launches: [Launch]! launch(id: ID!): Launch # Queries for the current user me: User }" 23 | } 24 | }, 25 | "keywords": [ 26 | "graphql", 27 | "postman", 28 | "collection" 29 | ], 30 | "engines": { 31 | "node": ">=16" 32 | }, 33 | "author": "Postman Labs ", 34 | "license": "Apache-2.0", 35 | "dependencies": { 36 | "commander": "2.20.3", 37 | "graphql": "15.8.0", 38 | "lodash": "4.17.19", 39 | "postman-collection": "^5.0.0" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/postmanlabs/graphql-to-postman" 44 | }, 45 | "devDependencies": { 46 | "async": "3.1.0", 47 | "chai": "4.2.0", 48 | "chalk": "2.1.0", 49 | "eslint": "4.18.2", 50 | "eslint-plugin-jsdoc": "3.1.3", 51 | "eslint-plugin-lodash": "2.4.5", 52 | "eslint-plugin-mocha": "4.11.0", 53 | "eslint-plugin-security": "1.4.0", 54 | "istanbul": "0.4.5", 55 | "jsdoc": "3.6.3", 56 | "mocha": "6.2.2", 57 | "nyc": "14.1.1", 58 | "path": "0.12.7", 59 | "pretty-ms": "5.1.0", 60 | "recursive-readdir": "2.2.2", 61 | "shelljs": "0.8.3" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "mocha" 4 | ], 5 | "env": { 6 | "mocha": true, 7 | "node": true, 8 | "es6": true 9 | }, 10 | "rules": { 11 | "mocha/handle-done-callback": "error", 12 | "mocha/max-top-level-suites": "error", 13 | "mocha/no-exclusive-tests": "error", 14 | "mocha/no-global-tests": "error", 15 | "mocha/no-hooks-for-single-case": "off", 16 | "mocha/no-hooks": "off", 17 | "mocha/no-identical-title": "error", 18 | "mocha/no-mocha-arrows": "error", 19 | "mocha/no-nested-tests": "error", 20 | "mocha/no-pending-tests": "error", 21 | "mocha/no-return-and-callback": "error", 22 | "mocha/no-sibling-hooks": "error", 23 | "mocha/no-skipped-tests": "warn", 24 | "mocha/no-synchronous-tests": "off", 25 | "mocha/no-top-level-hooks": "warn", 26 | "mocha/valid-test-description": "off", 27 | "mocha/valid-suite-description": "off" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/unit/converter.test.js: -------------------------------------------------------------------------------- 1 | var converter = require('../../index'), 2 | convert = converter.convert, 3 | validate = converter.validate, 4 | getOptions = converter.getOptions, 5 | fs = require('fs'), 6 | path = require('path'), 7 | validSchemaJson = require('./fixtures/validSchema.json'), 8 | validNestArgumentSchemaSDL = fs.readFileSync( 9 | path.join(__dirname, './fixtures/validNestedArgumentSchema.graphql') 10 | ).toString(), 11 | validNestedSchema = fs.readFileSync( 12 | path.join(__dirname, './fixtures/validNestedSchema.graphql') 13 | ).toString(), 14 | invalidNestArgumentSchemaSDL = fs.readFileSync( 15 | path.join(__dirname, './fixtures/invalidNestedArgumentSchema.graphql') 16 | ).toString(), 17 | invalidSchemaJson = require('./fixtures/invalidSchema.json'), 18 | validSchemaSDL = fs.readFileSync(path.join(__dirname, './fixtures/validSchemaSDL.graphql')).toString(), 19 | selfRefSchema = fs.readFileSync(path.join(__dirname, './fixtures/selfRefUnionTypeExample.graphql')).toString(), 20 | seflRefDepthUnionSchema = fs.readFileSync( 21 | path.join(__dirname, './fixtures/seflRefDepthUnionSchema.graphql') 22 | ).toString(), 23 | customTypeNames = fs.readFileSync(path.join(__dirname, './fixtures/custom-queryname.gql')).toString(), 24 | issue10 = fs.readFileSync(path.join(__dirname, './fixtures/issue#10.graphql')).toString(), 25 | circularInput = fs.readFileSync(path.join(__dirname, './fixtures/circularInput.graphql')).toString(), 26 | invalidSchemaSDL = fs.readFileSync(path.join(__dirname, './fixtures/invalidSchemaSDL.graphql')).toString(), 27 | expect = require('chai').expect; 28 | 29 | describe('Converter tests', function () { 30 | describe('getOptions function', function () { 31 | it('should return array of options', function () { 32 | const options = getOptions(); 33 | 34 | expect(options).to.be.an('array'); 35 | expect(options[0]).to.be.an('object'); 36 | expect(options[1]).to.be.an('object'); 37 | expect(options[0].id).to.be.equal('includeDeprecatedFields'); 38 | expect(options[1].id).to.be.equal('depth'); 39 | }); 40 | }); 41 | 42 | describe('Convert function', function () { 43 | it('should generate a collection for a valid JSON schema', function (done) { 44 | convert({ type: 'string', 45 | data: JSON.stringify(validSchemaJson) 46 | }, {}, function (error, result) { 47 | if (error) { 48 | expect.fail(null, null, error); 49 | return done(); 50 | } 51 | const collection = result.output[0].data; 52 | 53 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 54 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 55 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 56 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.equal( 57 | 'mutation bookTrips ($launchIds: [ID]!) {\n ' + 58 | 'bookTrips (launchIds: $launchIds) {\n success\n message\n }\n}' 59 | ); 60 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 61 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.equal( 62 | '{\n "launchIds": [\n 0\n ]\n}' 63 | ); 64 | return done(); 65 | }); 66 | }); 67 | 68 | it('should generate a collection for a valid JSON schema using the response recieved from ' + 69 | 'introspection query result', function (done) { 70 | const schema = { 71 | data: validSchemaJson 72 | }; 73 | 74 | convert({ type: 'string', 75 | data: JSON.stringify(schema) 76 | }, {}, function (error, result) { 77 | if (error) { 78 | expect.fail(null, null, error); 79 | return done(); 80 | } 81 | const collection = result.output[0].data; 82 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 83 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 84 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 85 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.equal( 86 | 'mutation bookTrips ($launchIds: [ID]!) {\n ' + 87 | 'bookTrips (launchIds: $launchIds) {\n success\n message\n }\n}' 88 | ); 89 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 90 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.equal( 91 | '{\n "launchIds": [\n 0\n ]\n}' 92 | ); 93 | return done(); 94 | }); 95 | }); 96 | 97 | it('should generate a collection for a valid SDL schema', function (done) { 98 | convert({ type: 'string', 99 | data: validSchemaSDL 100 | }, {}, function (error, result) { 101 | if (error) { 102 | expect.fail(null, null, error); 103 | return done(); 104 | } 105 | const collection = result.output[0].data; 106 | 107 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 108 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 109 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 110 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.equal( 111 | 'mutation bookTrips ($launchIds: [ID]!) {\n ' + 112 | 'bookTrips (launchIds: $launchIds) {\n success\n message\n }\n}' 113 | ); 114 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 115 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.equal( 116 | '{\n "launchIds": [\n 0\n ]\n}' 117 | ); 118 | 119 | return done(); 120 | }); 121 | }); 122 | 123 | it('should generate a collection for a valid SDL schema with a union type self referencing', function (done) { 124 | convert({ type: 'string', 125 | data: selfRefSchema 126 | }, { depth: 3 }, function (error, result) { 127 | if (error) { 128 | expect.fail(null, null, error); 129 | return done(); 130 | } 131 | const collection = result.output[0].data; 132 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 133 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 134 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 135 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 136 | expect(collection.item[0].item[0].request.body.graphql.query).to.contain('# self reference detected'); 137 | expect(collection.item[0].item[0].request.body.graphql.query).to.contain('# skipping "newLaunchId"'); 138 | 139 | return done(); 140 | }); 141 | }); 142 | 143 | it('should generate a collection for a valid SDL schema with custom query, mutation and' + 144 | 'subscription names', function (done) { 145 | convert({ type: 'string', 146 | data: customTypeNames 147 | }, {}, function (error, result) { 148 | if (error) { 149 | expect.fail(null, null, error); 150 | return done(); 151 | } 152 | const collection = result.output[0].data; 153 | 154 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 155 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 156 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 157 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.equal( 158 | 'mutation addUser ($input: UserInput) {\n addUser (input: $input) {\n id\n name\n }\n}' 159 | ); 160 | expect(collection.item[1].item[0].request.body.graphql.query).to.be.equal( 161 | 'query user ($id: String) {\n user (id: $id) {\n id\n name\n }\n}' 162 | ); 163 | expect(collection.item[2].item[0].request.body.graphql.query).to.be.equal( 164 | 'subscription addUser ($input: UserInput) {\n addUser (input: $input) ' + 165 | '{\n id\n name\n }\n}' 166 | ); 167 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 168 | expect(collection.item[1].item[0].request.body.graphql.variables).to.be.a('string'); 169 | expect(collection.item[2].item[0].request.body.graphql.variables).to.be.a('string'); 170 | 171 | return done(); 172 | }); 173 | }); 174 | 175 | it('should not throw an error for schema containing an input type', function (done) { 176 | convert({ type: 'string', 177 | data: issue10 178 | }, {}, function (error, result) { 179 | if (error) { 180 | expect.fail(null, null, error); 181 | return done(); 182 | } 183 | const collection = result.output[0].data; 184 | expect(result.result).to.be.equal(true); 185 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.equal('mutation addUser ' + 186 | '($input: UserInput) {\n addUser (input: $input) {\n id\n name\n }\n}'); 187 | 188 | return done(); 189 | }); 190 | }); 191 | 192 | it('should successfully convert a schema with circular reference', function (done) { 193 | convert({ type: 'string', 194 | data: circularInput 195 | }, {}, function (error, result) { 196 | if (error) { 197 | expect.fail(null, null, error); 198 | return done(); 199 | } 200 | const collection = result.output[0].data; 201 | 202 | expect(result.result).to.be.equal(true); 203 | expect(collection.item[0].item[0].request.body.graphql.variables).to.contain('"name": "",\n "email": "",' + 204 | '\n "friend": ""'); 205 | 206 | return done(); 207 | }); 208 | }); 209 | 210 | it('should generate a collection for a valid SDL schema with nested list arguments', function (done) { 211 | convert({ type: 'string', 212 | data: validNestArgumentSchemaSDL 213 | }, {}, function (error, result) { 214 | if (error) { 215 | expect.fail(null, null, error); 216 | return done(); 217 | } 218 | const collection = result.output[0].data; 219 | 220 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 221 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 222 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 223 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.equal('mutation nested' + 224 | ' ($filterBy: [[[String]]]) {\n nested {\n nested (filterBy: $filterBy)\n }\n}'); 225 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 226 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.equal( 227 | '{\n "filterBy": [\n [\n [\n ""\n ]\n ]\n ]\n}' 228 | ); 229 | 230 | return done(); 231 | }); 232 | }); 233 | 234 | it('should generate a collection for a valid SDL schema with user defined return type list', function (done) { 235 | convert({ type: 'string', 236 | data: validNestedSchema 237 | }, {}, function (error, result) { 238 | if (error) { 239 | expect.fail(null, null, error); 240 | return done(); 241 | } 242 | const collection = result.output[0].data; 243 | 244 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 245 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 246 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 247 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.equal('mutation ' + 248 | 'addUser ($User: [[User]]!) {\n addUser (User: $User) {\n id\n name\n }\n}'); 249 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 250 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.equal( 251 | '{\n "User": [\n [\n ""\n ]\n ]\n}' 252 | ); 253 | 254 | return done(); 255 | }); 256 | }); 257 | 258 | it('should throw an error for invalid SDL schema with nested list arguments', function (done) { 259 | convert({ type: 'string', 260 | data: invalidNestArgumentSchemaSDL 261 | }, {}, function (error, result) { 262 | if (error) { 263 | expect.fail(null, null, error); 264 | return done(); 265 | } 266 | expect(result).to.eql({ 267 | reason: 'Invalid Data.', 268 | result: false 269 | }); 270 | 271 | return done(); 272 | }); 273 | }); 274 | 275 | it('should generate a collection for a valid SDL schema with a union type self referencing' + 276 | ' and past allowed depth limit', function (done) { 277 | convert({ type: 'string', 278 | data: seflRefDepthUnionSchema 279 | }, { depth: 3 }, function (error, result) { 280 | if (error) { 281 | expect.fail(null, null, error); 282 | return done(); 283 | } 284 | const collection = result.output[0].data; 285 | expect(collection.item[0].item[0].request.body.mode).to.be.equal('graphql'); 286 | expect(collection.item[0].item[0].request.body.graphql).to.be.an('object'); 287 | expect(collection.item[0].item[0].request.body.graphql.query).to.be.a('string'); 288 | expect(collection.item[0].item[0].request.body.graphql.variables).to.be.a('string'); 289 | expect(collection.item[0].item[0].request.body.graphql.query).to.contain('# self reference detected'); 290 | expect(collection.item[0].item[0].request.body.graphql.query).to.contain('# skipping "createdBy"'); 291 | expect(collection.item[0].item[0].request.body.graphql.query).to.contain('# skipping "updatedBy"'); 292 | 293 | return done(); 294 | }); 295 | }); 296 | }); 297 | 298 | describe('Validate function', function () { 299 | it('should return true for a valid JSON schema', function () { 300 | const value = validate({ type: 'string', 301 | data: JSON.stringify(validSchemaJson) 302 | }); 303 | 304 | expect(value).to.be.an('object'); 305 | expect(value.result).to.be.equal(true); 306 | }); 307 | 308 | it('should return true for a valid JSON schema from introspection query result', function () { 309 | const schema = { 310 | data: validSchemaJson 311 | }, 312 | value = validate({ type: 'string', 313 | data: JSON.stringify(schema) 314 | }); 315 | 316 | expect(value).to.be.an('object'); 317 | expect(value.result).to.be.equal(true); 318 | }); 319 | 320 | it('should return false for a invalid JSON schema', function () { 321 | const value = validate({ type: 'string', 322 | data: JSON.stringify(invalidSchemaJson) 323 | }); 324 | 325 | expect(value).to.be.an('object'); 326 | expect(value.result).to.be.equal(false); 327 | }); 328 | 329 | it('should return false for a invalid SDL schema', function () { 330 | const value = validate({ type: 'string', 331 | data: invalidSchemaSDL 332 | }); 333 | 334 | expect(value).to.be.an('object'); 335 | expect(value.result).to.be.equal(false); 336 | }); 337 | 338 | it('should return true for a valid SDL schema', function () { 339 | const value = validate({ type: 'string', 340 | data: validSchemaSDL 341 | }); 342 | 343 | expect(value).to.be.an('object'); 344 | expect(value.result).to.be.equal(true); 345 | }); 346 | 347 | it('should return false for a graphql query', function () { 348 | const value = validate({ type: 'string', 349 | data: '{ hello }' 350 | }); 351 | 352 | expect(value).to.be.an('object'); 353 | expect(value.result).to.be.equal(false); 354 | }); 355 | 356 | it('should return correct reason for a invalid SDL schema', function () { 357 | const value = validate({ type: 'string', 358 | data: `input UserInput { 359 | name: String! 360 | email: String! 361 | } 362 | type User { 363 | id: String! 364 | name: String 365 | } 366 | type RandomQueryName { 367 | user (id: String): User 368 | } 369 | type RandomMutationName { 370 | addUser (input: UserInput): User! 371 | }` 372 | }); 373 | 374 | expect(value).to.be.an('object'); 375 | expect(value.result).to.be.equal(false); 376 | expect(value.reason).to.be.equal('Specification doesn\'t contain valid mutation, query or subscription type'); 377 | }); 378 | }); 379 | }); 380 | -------------------------------------------------------------------------------- /test/unit/fixtures/circularInput.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | mutation: Mutation 4 | } 5 | input UserInput { 6 | name: String! 7 | email: String! 8 | friend: UserInput! 9 | } 10 | type User { 11 | id: String! 12 | name: String 13 | } 14 | type Query { 15 | user (id: String): User 16 | } 17 | type Mutation { 18 | addUser (input: UserInput): User! 19 | } -------------------------------------------------------------------------------- /test/unit/fixtures/custom-queryname.gql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: RandomQueryName 3 | mutation: RandomMutationName 4 | subscription: RandomSubscriptionName 5 | } 6 | input UserInput { 7 | name: String! 8 | email: String! 9 | } 10 | type User { 11 | id: String! 12 | name: String 13 | } 14 | type RandomQueryName { 15 | user (id: String): User 16 | } 17 | type RandomMutationName { 18 | addUser (input: UserInput): User! 19 | } 20 | 21 | type RandomSubscriptionName { 22 | addUser (input: UserInput): User! 23 | } 24 | -------------------------------------------------------------------------------- /test/unit/fixtures/invalidNestedArgumentSchema.graphql: -------------------------------------------------------------------------------- 1 | interface Node { 2 | id: ID! 3 | } 4 | 5 | type Query { 6 | node(id: ID!): Node 7 | } 8 | 9 | type Mutation { 10 | nested: Nested 11 | } 12 | 13 | type Nested { 14 | nested(filterBy: [[String]]]): String 15 | } -------------------------------------------------------------------------------- /test/unit/fixtures/invalidSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "_schema": { 3 | "queryType": { 4 | "name": "Query" 5 | }, 6 | "mutationType": { 7 | "name": "Mutation" 8 | }, 9 | "subscriptionType": null, 10 | "types": [ 11 | { 12 | "kind": "OBJECT", 13 | "name": "Query", 14 | "description": null, 15 | "fields": [ 16 | { 17 | "name": "launches", 18 | "description": null, 19 | "args": [], 20 | "type": { 21 | "kind": "NON_NULL", 22 | "name": null, 23 | "ofType": { 24 | "kind": "LIST", 25 | "name": null, 26 | "ofType": { 27 | "kind": "OBJECT", 28 | "name": "Launch", 29 | "ofType": null 30 | } 31 | } 32 | }, 33 | "isDeprecated": false, 34 | "deprecationReason": null 35 | }, 36 | { 37 | "name": "launch", 38 | "description": null, 39 | "args": [ 40 | { 41 | "name": "id", 42 | "description": null, 43 | "type": { 44 | "kind": "NON_NULL", 45 | "name": null, 46 | "ofType": { 47 | "kind": "SCALAR", 48 | "name": "ID", 49 | "ofType": null 50 | } 51 | }, 52 | "defaultValue": null 53 | } 54 | ], 55 | "type": { 56 | "kind": "OBJECT", 57 | "name": "Launch", 58 | "ofType": null 59 | }, 60 | "isDeprecated": false, 61 | "deprecationReason": null 62 | }, 63 | { 64 | "name": "me", 65 | "description": null, 66 | "args": [], 67 | "type": { 68 | "kind": "OBJECT", 69 | "name": "User", 70 | "ofType": null 71 | }, 72 | "isDeprecated": false, 73 | "deprecationReason": null 74 | } 75 | ], 76 | "inputFields": null, 77 | "interfaces": [], 78 | "enumValues": null, 79 | "possibleTypes": null 80 | }, 81 | { 82 | "kind": "OBJECT", 83 | "name": "Launch", 84 | "description": null, 85 | "fields": [ 86 | { 87 | "name": "id", 88 | "description": null, 89 | "args": [], 90 | "type": { 91 | "kind": "NON_NULL", 92 | "name": null, 93 | "ofType": { 94 | "kind": "SCALAR", 95 | "name": "ID", 96 | "ofType": null 97 | } 98 | }, 99 | "isDeprecated": false, 100 | "deprecationReason": null 101 | }, 102 | { 103 | "name": "site", 104 | "description": null, 105 | "args": [], 106 | "type": { 107 | "kind": "SCALAR", 108 | "name": "String", 109 | "ofType": null 110 | }, 111 | "isDeprecated": false, 112 | "deprecationReason": null 113 | }, 114 | { 115 | "name": "mission", 116 | "description": null, 117 | "args": [], 118 | "type": { 119 | "kind": "OBJECT", 120 | "name": "Mission", 121 | "ofType": null 122 | }, 123 | "isDeprecated": false, 124 | "deprecationReason": null 125 | }, 126 | { 127 | "name": "rocket", 128 | "description": null, 129 | "args": [], 130 | "type": { 131 | "kind": "OBJECT", 132 | "name": "Rocket", 133 | "ofType": null 134 | }, 135 | "isDeprecated": false, 136 | "deprecationReason": null 137 | }, 138 | { 139 | "name": "isBooked", 140 | "description": null, 141 | "args": [], 142 | "type": { 143 | "kind": "NON_NULL", 144 | "name": null, 145 | "ofType": { 146 | "kind": "SCALAR", 147 | "name": "Boolean", 148 | "ofType": null 149 | } 150 | }, 151 | "isDeprecated": false, 152 | "deprecationReason": null 153 | } 154 | ], 155 | "inputFields": null, 156 | "interfaces": [], 157 | "enumValues": null, 158 | "possibleTypes": null 159 | }, 160 | { 161 | "kind": "SCALAR", 162 | "name": "ID", 163 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", 164 | "fields": null, 165 | "inputFields": null, 166 | "interfaces": null, 167 | "enumValues": null, 168 | "possibleTypes": null 169 | }, 170 | { 171 | "kind": "SCALAR", 172 | "name": "String", 173 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 174 | "fields": null, 175 | "inputFields": null, 176 | "interfaces": null, 177 | "enumValues": null, 178 | "possibleTypes": null 179 | }, 180 | { 181 | "kind": "OBJECT", 182 | "name": "Mission", 183 | "description": null, 184 | "fields": [ 185 | { 186 | "name": "name", 187 | "description": null, 188 | "args": [], 189 | "type": { 190 | "kind": "SCALAR", 191 | "name": "String", 192 | "ofType": null 193 | }, 194 | "isDeprecated": false, 195 | "deprecationReason": null 196 | }, 197 | { 198 | "name": "missionPatch", 199 | "description": null, 200 | "args": [ 201 | { 202 | "name": "size", 203 | "description": null, 204 | "type": { 205 | "kind": "ENUM", 206 | "name": "PatchSize", 207 | "ofType": null 208 | }, 209 | "defaultValue": null 210 | } 211 | ], 212 | "type": { 213 | "kind": "SCALAR", 214 | "name": "String", 215 | "ofType": null 216 | }, 217 | "isDeprecated": false, 218 | "deprecationReason": null 219 | } 220 | ], 221 | "inputFields": null, 222 | "interfaces": [], 223 | "enumValues": null, 224 | "possibleTypes": null 225 | }, 226 | { 227 | "kind": "ENUM", 228 | "name": "PatchSize", 229 | "description": null, 230 | "fields": null, 231 | "inputFields": null, 232 | "interfaces": null, 233 | "enumValues": [ 234 | { 235 | "name": "SMALL", 236 | "description": null, 237 | "isDeprecated": false, 238 | "deprecationReason": null 239 | }, 240 | { 241 | "name": "LARGE", 242 | "description": null, 243 | "isDeprecated": false, 244 | "deprecationReason": null 245 | } 246 | ], 247 | "possibleTypes": null 248 | }, 249 | { 250 | "kind": "OBJECT", 251 | "name": "Rocket", 252 | "description": null, 253 | "fields": [ 254 | { 255 | "name": "id", 256 | "description": null, 257 | "args": [], 258 | "type": { 259 | "kind": "NON_NULL", 260 | "name": null, 261 | "ofType": { 262 | "kind": "SCALAR", 263 | "name": "ID", 264 | "ofType": null 265 | } 266 | }, 267 | "isDeprecated": false, 268 | "deprecationReason": null 269 | }, 270 | { 271 | "name": "name", 272 | "description": null, 273 | "args": [], 274 | "type": { 275 | "kind": "SCALAR", 276 | "name": "String", 277 | "ofType": null 278 | }, 279 | "isDeprecated": false, 280 | "deprecationReason": null 281 | }, 282 | { 283 | "name": "type", 284 | "description": null, 285 | "args": [], 286 | "type": { 287 | "kind": "SCALAR", 288 | "name": "String", 289 | "ofType": null 290 | }, 291 | "isDeprecated": false, 292 | "deprecationReason": null 293 | } 294 | ], 295 | "inputFields": null, 296 | "interfaces": [], 297 | "enumValues": null, 298 | "possibleTypes": null 299 | }, 300 | { 301 | "kind": "SCALAR", 302 | "name": "Boolean", 303 | "description": "The `Boolean` scalar type represents `true` or `false`.", 304 | "fields": null, 305 | "inputFields": null, 306 | "interfaces": null, 307 | "enumValues": null, 308 | "possibleTypes": null 309 | }, 310 | { 311 | "kind": "OBJECT", 312 | "name": "User", 313 | "description": null, 314 | "fields": [ 315 | { 316 | "name": "id", 317 | "description": null, 318 | "args": [], 319 | "type": { 320 | "kind": "NON_NULL", 321 | "name": null, 322 | "ofType": { 323 | "kind": "SCALAR", 324 | "name": "ID", 325 | "ofType": null 326 | } 327 | }, 328 | "isDeprecated": false, 329 | "deprecationReason": null 330 | }, 331 | { 332 | "name": "email", 333 | "description": null, 334 | "args": [], 335 | "type": { 336 | "kind": "NON_NULL", 337 | "name": null, 338 | "ofType": { 339 | "kind": "SCALAR", 340 | "name": "String", 341 | "ofType": null 342 | } 343 | }, 344 | "isDeprecated": false, 345 | "deprecationReason": null 346 | }, 347 | { 348 | "name": "trips", 349 | "description": null, 350 | "args": [], 351 | "type": { 352 | "kind": "NON_NULL", 353 | "name": null, 354 | "ofType": { 355 | "kind": "LIST", 356 | "name": null, 357 | "ofType": { 358 | "kind": "OBJECT", 359 | "name": "Launch", 360 | "ofType": null 361 | } 362 | } 363 | }, 364 | "isDeprecated": false, 365 | "deprecationReason": null 366 | } 367 | ], 368 | "inputFields": null, 369 | "interfaces": [], 370 | "enumValues": null, 371 | "possibleTypes": null 372 | }, 373 | { 374 | "kind": "OBJECT", 375 | "name": "Mutation", 376 | "description": null, 377 | "fields": [ 378 | { 379 | "name": "bookTrips", 380 | "description": null, 381 | "args": [ 382 | { 383 | "name": "launchIds", 384 | "description": null, 385 | "type": { 386 | "kind": "NON_NULL", 387 | "name": null, 388 | "ofType": { 389 | "kind": "LIST", 390 | "name": null, 391 | "ofType": { 392 | "kind": "SCALAR", 393 | "name": "ID", 394 | "ofType": null 395 | } 396 | } 397 | }, 398 | "defaultValue": null 399 | } 400 | ], 401 | "type": { 402 | "kind": "NON_NULL", 403 | "name": null, 404 | "ofType": { 405 | "kind": "OBJECT", 406 | "name": "TripUpdateResponse", 407 | "ofType": null 408 | } 409 | }, 410 | "isDeprecated": false, 411 | "deprecationReason": null 412 | }, 413 | { 414 | "name": "cancelTrip", 415 | "description": null, 416 | "args": [ 417 | { 418 | "name": "launchId", 419 | "description": null, 420 | "type": { 421 | "kind": "NON_NULL", 422 | "name": null, 423 | "ofType": { 424 | "kind": "SCALAR", 425 | "name": "ID", 426 | "ofType": null 427 | } 428 | }, 429 | "defaultValue": null 430 | } 431 | ], 432 | "type": { 433 | "kind": "NON_NULL", 434 | "name": null, 435 | "ofType": { 436 | "kind": "OBJECT", 437 | "name": "TripUpdateResponse", 438 | "ofType": null 439 | } 440 | }, 441 | "isDeprecated": false, 442 | "deprecationReason": null 443 | }, 444 | { 445 | "name": "login", 446 | "description": null, 447 | "args": [ 448 | { 449 | "name": "email", 450 | "description": null, 451 | "type": { 452 | "kind": "SCALAR", 453 | "name": "String", 454 | "ofType": null 455 | }, 456 | "defaultValue": null 457 | } 458 | ], 459 | "type": { 460 | "kind": "SCALAR", 461 | "name": "String", 462 | "ofType": null 463 | }, 464 | "isDeprecated": false, 465 | "deprecationReason": null 466 | } 467 | ], 468 | "inputFields": null, 469 | "interfaces": [], 470 | "enumValues": null, 471 | "possibleTypes": null 472 | }, 473 | { 474 | "kind": "OBJECT", 475 | "name": "TripUpdateResponse", 476 | "description": null, 477 | "fields": [ 478 | { 479 | "name": "success", 480 | "description": null, 481 | "args": [], 482 | "type": { 483 | "kind": "NON_NULL", 484 | "name": null, 485 | "ofType": { 486 | "kind": "SCALAR", 487 | "name": "Boolean", 488 | "ofType": null 489 | } 490 | }, 491 | "isDeprecated": false, 492 | "deprecationReason": null 493 | }, 494 | { 495 | "name": "message", 496 | "description": null, 497 | "args": [], 498 | "type": { 499 | "kind": "SCALAR", 500 | "name": "String", 501 | "ofType": null 502 | }, 503 | "isDeprecated": false, 504 | "deprecationReason": null 505 | }, 506 | { 507 | "name": "launches", 508 | "description": null, 509 | "args": [], 510 | "type": { 511 | "kind": "LIST", 512 | "name": null, 513 | "ofType": { 514 | "kind": "OBJECT", 515 | "name": "Launch", 516 | "ofType": null 517 | } 518 | }, 519 | "isDeprecated": false, 520 | "deprecationReason": null 521 | } 522 | ], 523 | "inputFields": null, 524 | "interfaces": [], 525 | "enumValues": null, 526 | "possibleTypes": null 527 | }, 528 | { 529 | "kind": "OBJECT", 530 | "name": "__Schema", 531 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 532 | "fields": [ 533 | { 534 | "name": "types", 535 | "description": "A list of all types supported by this server.", 536 | "args": [], 537 | "type": { 538 | "kind": "NON_NULL", 539 | "name": null, 540 | "ofType": { 541 | "kind": "LIST", 542 | "name": null, 543 | "ofType": { 544 | "kind": "NON_NULL", 545 | "name": null, 546 | "ofType": { 547 | "kind": "OBJECT", 548 | "name": "__Type", 549 | "ofType": null 550 | } 551 | } 552 | } 553 | }, 554 | "isDeprecated": false, 555 | "deprecationReason": null 556 | }, 557 | { 558 | "name": "queryType", 559 | "description": "The type that query operations will be rooted at.", 560 | "args": [], 561 | "type": { 562 | "kind": "NON_NULL", 563 | "name": null, 564 | "ofType": { 565 | "kind": "OBJECT", 566 | "name": "__Type", 567 | "ofType": null 568 | } 569 | }, 570 | "isDeprecated": false, 571 | "deprecationReason": null 572 | }, 573 | { 574 | "name": "mutationType", 575 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 576 | "args": [], 577 | "type": { 578 | "kind": "OBJECT", 579 | "name": "__Type", 580 | "ofType": null 581 | }, 582 | "isDeprecated": false, 583 | "deprecationReason": null 584 | }, 585 | { 586 | "name": "subscriptionType", 587 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 588 | "args": [], 589 | "type": { 590 | "kind": "OBJECT", 591 | "name": "__Type", 592 | "ofType": null 593 | }, 594 | "isDeprecated": false, 595 | "deprecationReason": null 596 | }, 597 | { 598 | "name": "directives", 599 | "description": "A list of all directives supported by this server.", 600 | "args": [], 601 | "type": { 602 | "kind": "NON_NULL", 603 | "name": null, 604 | "ofType": { 605 | "kind": "LIST", 606 | "name": null, 607 | "ofType": { 608 | "kind": "NON_NULL", 609 | "name": null, 610 | "ofType": { 611 | "kind": "OBJECT", 612 | "name": "__Directive", 613 | "ofType": null 614 | } 615 | } 616 | } 617 | }, 618 | "isDeprecated": false, 619 | "deprecationReason": null 620 | } 621 | ], 622 | "inputFields": null, 623 | "interfaces": [], 624 | "enumValues": null, 625 | "possibleTypes": null 626 | }, 627 | { 628 | "kind": "OBJECT", 629 | "name": "__Type", 630 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 631 | "fields": [ 632 | { 633 | "name": "kind", 634 | "description": null, 635 | "args": [], 636 | "type": { 637 | "kind": "NON_NULL", 638 | "name": null, 639 | "ofType": { 640 | "kind": "ENUM", 641 | "name": "__TypeKind", 642 | "ofType": null 643 | } 644 | }, 645 | "isDeprecated": false, 646 | "deprecationReason": null 647 | }, 648 | { 649 | "name": "name", 650 | "description": null, 651 | "args": [], 652 | "type": { 653 | "kind": "SCALAR", 654 | "name": "String", 655 | "ofType": null 656 | }, 657 | "isDeprecated": false, 658 | "deprecationReason": null 659 | }, 660 | { 661 | "name": "description", 662 | "description": null, 663 | "args": [], 664 | "type": { 665 | "kind": "SCALAR", 666 | "name": "String", 667 | "ofType": null 668 | }, 669 | "isDeprecated": false, 670 | "deprecationReason": null 671 | }, 672 | { 673 | "name": "fields", 674 | "description": null, 675 | "args": [ 676 | { 677 | "name": "includeDeprecated", 678 | "description": null, 679 | "type": { 680 | "kind": "SCALAR", 681 | "name": "Boolean", 682 | "ofType": null 683 | }, 684 | "defaultValue": "false" 685 | } 686 | ], 687 | "type": { 688 | "kind": "LIST", 689 | "name": null, 690 | "ofType": { 691 | "kind": "NON_NULL", 692 | "name": null, 693 | "ofType": { 694 | "kind": "OBJECT", 695 | "name": "__Field", 696 | "ofType": null 697 | } 698 | } 699 | }, 700 | "isDeprecated": false, 701 | "deprecationReason": null 702 | }, 703 | { 704 | "name": "interfaces", 705 | "description": null, 706 | "args": [], 707 | "type": { 708 | "kind": "LIST", 709 | "name": null, 710 | "ofType": { 711 | "kind": "NON_NULL", 712 | "name": null, 713 | "ofType": { 714 | "kind": "OBJECT", 715 | "name": "__Type", 716 | "ofType": null 717 | } 718 | } 719 | }, 720 | "isDeprecated": false, 721 | "deprecationReason": null 722 | }, 723 | { 724 | "name": "possibleTypes", 725 | "description": null, 726 | "args": [], 727 | "type": { 728 | "kind": "LIST", 729 | "name": null, 730 | "ofType": { 731 | "kind": "NON_NULL", 732 | "name": null, 733 | "ofType": { 734 | "kind": "OBJECT", 735 | "name": "__Type", 736 | "ofType": null 737 | } 738 | } 739 | }, 740 | "isDeprecated": false, 741 | "deprecationReason": null 742 | }, 743 | { 744 | "name": "enumValues", 745 | "description": null, 746 | "args": [ 747 | { 748 | "name": "includeDeprecated", 749 | "description": null, 750 | "type": { 751 | "kind": "SCALAR", 752 | "name": "Boolean", 753 | "ofType": null 754 | }, 755 | "defaultValue": "false" 756 | } 757 | ], 758 | "type": { 759 | "kind": "LIST", 760 | "name": null, 761 | "ofType": { 762 | "kind": "NON_NULL", 763 | "name": null, 764 | "ofType": { 765 | "kind": "OBJECT", 766 | "name": "__EnumValue", 767 | "ofType": null 768 | } 769 | } 770 | }, 771 | "isDeprecated": false, 772 | "deprecationReason": null 773 | }, 774 | { 775 | "name": "inputFields", 776 | "description": null, 777 | "args": [], 778 | "type": { 779 | "kind": "LIST", 780 | "name": null, 781 | "ofType": { 782 | "kind": "NON_NULL", 783 | "name": null, 784 | "ofType": { 785 | "kind": "OBJECT", 786 | "name": "__InputValue", 787 | "ofType": null 788 | } 789 | } 790 | }, 791 | "isDeprecated": false, 792 | "deprecationReason": null 793 | }, 794 | { 795 | "name": "ofType", 796 | "description": null, 797 | "args": [], 798 | "type": { 799 | "kind": "OBJECT", 800 | "name": "__Type", 801 | "ofType": null 802 | }, 803 | "isDeprecated": false, 804 | "deprecationReason": null 805 | } 806 | ], 807 | "inputFields": null, 808 | "interfaces": [], 809 | "enumValues": null, 810 | "possibleTypes": null 811 | }, 812 | { 813 | "kind": "ENUM", 814 | "name": "__TypeKind", 815 | "description": "An enum describing what kind of type a given `__Type` is.", 816 | "fields": null, 817 | "inputFields": null, 818 | "interfaces": null, 819 | "enumValues": [ 820 | { 821 | "name": "SCALAR", 822 | "description": "Indicates this type is a scalar.", 823 | "isDeprecated": false, 824 | "deprecationReason": null 825 | }, 826 | { 827 | "name": "OBJECT", 828 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 829 | "isDeprecated": false, 830 | "deprecationReason": null 831 | }, 832 | { 833 | "name": "INTERFACE", 834 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 835 | "isDeprecated": false, 836 | "deprecationReason": null 837 | }, 838 | { 839 | "name": "UNION", 840 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 841 | "isDeprecated": false, 842 | "deprecationReason": null 843 | }, 844 | { 845 | "name": "ENUM", 846 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 847 | "isDeprecated": false, 848 | "deprecationReason": null 849 | }, 850 | { 851 | "name": "INPUT_OBJECT", 852 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 853 | "isDeprecated": false, 854 | "deprecationReason": null 855 | }, 856 | { 857 | "name": "LIST", 858 | "description": "Indicates this type is a list. `ofType` is a valid field.", 859 | "isDeprecated": false, 860 | "deprecationReason": null 861 | }, 862 | { 863 | "name": "NON_NULL", 864 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 865 | "isDeprecated": false, 866 | "deprecationReason": null 867 | } 868 | ], 869 | "possibleTypes": null 870 | }, 871 | { 872 | "kind": "OBJECT", 873 | "name": "__Field", 874 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 875 | "fields": [ 876 | { 877 | "name": "name", 878 | "description": null, 879 | "args": [], 880 | "type": { 881 | "kind": "NON_NULL", 882 | "name": null, 883 | "ofType": { 884 | "kind": "SCALAR", 885 | "name": "String", 886 | "ofType": null 887 | } 888 | }, 889 | "isDeprecated": false, 890 | "deprecationReason": null 891 | }, 892 | { 893 | "name": "description", 894 | "description": null, 895 | "args": [], 896 | "type": { 897 | "kind": "SCALAR", 898 | "name": "String", 899 | "ofType": null 900 | }, 901 | "isDeprecated": false, 902 | "deprecationReason": null 903 | }, 904 | { 905 | "name": "args", 906 | "description": null, 907 | "args": [], 908 | "type": { 909 | "kind": "NON_NULL", 910 | "name": null, 911 | "ofType": { 912 | "kind": "LIST", 913 | "name": null, 914 | "ofType": { 915 | "kind": "NON_NULL", 916 | "name": null, 917 | "ofType": { 918 | "kind": "OBJECT", 919 | "name": "__InputValue", 920 | "ofType": null 921 | } 922 | } 923 | } 924 | }, 925 | "isDeprecated": false, 926 | "deprecationReason": null 927 | }, 928 | { 929 | "name": "type", 930 | "description": null, 931 | "args": [], 932 | "type": { 933 | "kind": "NON_NULL", 934 | "name": null, 935 | "ofType": { 936 | "kind": "OBJECT", 937 | "name": "__Type", 938 | "ofType": null 939 | } 940 | }, 941 | "isDeprecated": false, 942 | "deprecationReason": null 943 | }, 944 | { 945 | "name": "isDeprecated", 946 | "description": null, 947 | "args": [], 948 | "type": { 949 | "kind": "NON_NULL", 950 | "name": null, 951 | "ofType": { 952 | "kind": "SCALAR", 953 | "name": "Boolean", 954 | "ofType": null 955 | } 956 | }, 957 | "isDeprecated": false, 958 | "deprecationReason": null 959 | }, 960 | { 961 | "name": "deprecationReason", 962 | "description": null, 963 | "args": [], 964 | "type": { 965 | "kind": "SCALAR", 966 | "name": "String", 967 | "ofType": null 968 | }, 969 | "isDeprecated": false, 970 | "deprecationReason": null 971 | } 972 | ], 973 | "inputFields": null, 974 | "interfaces": [], 975 | "enumValues": null, 976 | "possibleTypes": null 977 | }, 978 | { 979 | "kind": "OBJECT", 980 | "name": "__InputValue", 981 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 982 | "fields": [ 983 | { 984 | "name": "name", 985 | "description": null, 986 | "args": [], 987 | "type": { 988 | "kind": "NON_NULL", 989 | "name": null, 990 | "ofType": { 991 | "kind": "SCALAR", 992 | "name": "String", 993 | "ofType": null 994 | } 995 | }, 996 | "isDeprecated": false, 997 | "deprecationReason": null 998 | }, 999 | { 1000 | "name": "description", 1001 | "description": null, 1002 | "args": [], 1003 | "type": { 1004 | "kind": "SCALAR", 1005 | "name": "String", 1006 | "ofType": null 1007 | }, 1008 | "isDeprecated": false, 1009 | "deprecationReason": null 1010 | }, 1011 | { 1012 | "name": "type", 1013 | "description": null, 1014 | "args": [], 1015 | "type": { 1016 | "kind": "NON_NULL", 1017 | "name": null, 1018 | "ofType": { 1019 | "kind": "OBJECT", 1020 | "name": "__Type", 1021 | "ofType": null 1022 | } 1023 | }, 1024 | "isDeprecated": false, 1025 | "deprecationReason": null 1026 | }, 1027 | { 1028 | "name": "defaultValue", 1029 | "description": "A GraphQL-formatted string representing the default value for this input value.", 1030 | "args": [], 1031 | "type": { 1032 | "kind": "SCALAR", 1033 | "name": "String", 1034 | "ofType": null 1035 | }, 1036 | "isDeprecated": false, 1037 | "deprecationReason": null 1038 | } 1039 | ], 1040 | "inputFields": null, 1041 | "interfaces": [], 1042 | "enumValues": null, 1043 | "possibleTypes": null 1044 | }, 1045 | { 1046 | "kind": "OBJECT", 1047 | "name": "__EnumValue", 1048 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 1049 | "fields": [ 1050 | { 1051 | "name": "name", 1052 | "description": null, 1053 | "args": [], 1054 | "type": { 1055 | "kind": "NON_NULL", 1056 | "name": null, 1057 | "ofType": { 1058 | "kind": "SCALAR", 1059 | "name": "String", 1060 | "ofType": null 1061 | } 1062 | }, 1063 | "isDeprecated": false, 1064 | "deprecationReason": null 1065 | }, 1066 | { 1067 | "name": "description", 1068 | "description": null, 1069 | "args": [], 1070 | "type": { 1071 | "kind": "SCALAR", 1072 | "name": "String", 1073 | "ofType": null 1074 | }, 1075 | "isDeprecated": false, 1076 | "deprecationReason": null 1077 | }, 1078 | { 1079 | "name": "isDeprecated", 1080 | "description": null, 1081 | "args": [], 1082 | "type": { 1083 | "kind": "NON_NULL", 1084 | "name": null, 1085 | "ofType": { 1086 | "kind": "SCALAR", 1087 | "name": "Boolean", 1088 | "ofType": null 1089 | } 1090 | }, 1091 | "isDeprecated": false, 1092 | "deprecationReason": null 1093 | }, 1094 | { 1095 | "name": "deprecationReason", 1096 | "description": null, 1097 | "args": [], 1098 | "type": { 1099 | "kind": "SCALAR", 1100 | "name": "String", 1101 | "ofType": null 1102 | }, 1103 | "isDeprecated": false, 1104 | "deprecationReason": null 1105 | } 1106 | ], 1107 | "inputFields": null, 1108 | "interfaces": [], 1109 | "enumValues": null, 1110 | "possibleTypes": null 1111 | }, 1112 | { 1113 | "kind": "OBJECT", 1114 | "name": "__Directive", 1115 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 1116 | "fields": [ 1117 | { 1118 | "name": "name", 1119 | "description": null, 1120 | "args": [], 1121 | "type": { 1122 | "kind": "NON_NULL", 1123 | "name": null, 1124 | "ofType": { 1125 | "kind": "SCALAR", 1126 | "name": "String", 1127 | "ofType": null 1128 | } 1129 | }, 1130 | "isDeprecated": false, 1131 | "deprecationReason": null 1132 | }, 1133 | { 1134 | "name": "description", 1135 | "description": null, 1136 | "args": [], 1137 | "type": { 1138 | "kind": "SCALAR", 1139 | "name": "String", 1140 | "ofType": null 1141 | }, 1142 | "isDeprecated": false, 1143 | "deprecationReason": null 1144 | }, 1145 | { 1146 | "name": "locations", 1147 | "description": null, 1148 | "args": [], 1149 | "type": { 1150 | "kind": "NON_NULL", 1151 | "name": null, 1152 | "ofType": { 1153 | "kind": "LIST", 1154 | "name": null, 1155 | "ofType": { 1156 | "kind": "NON_NULL", 1157 | "name": null, 1158 | "ofType": { 1159 | "kind": "ENUM", 1160 | "name": "__DirectiveLocation", 1161 | "ofType": null 1162 | } 1163 | } 1164 | } 1165 | }, 1166 | "isDeprecated": false, 1167 | "deprecationReason": null 1168 | }, 1169 | { 1170 | "name": "args", 1171 | "description": null, 1172 | "args": [], 1173 | "type": { 1174 | "kind": "NON_NULL", 1175 | "name": null, 1176 | "ofType": { 1177 | "kind": "LIST", 1178 | "name": null, 1179 | "ofType": { 1180 | "kind": "NON_NULL", 1181 | "name": null, 1182 | "ofType": { 1183 | "kind": "OBJECT", 1184 | "name": "__InputValue", 1185 | "ofType": null 1186 | } 1187 | } 1188 | } 1189 | }, 1190 | "isDeprecated": false, 1191 | "deprecationReason": null 1192 | } 1193 | ], 1194 | "inputFields": null, 1195 | "interfaces": [], 1196 | "enumValues": null, 1197 | "possibleTypes": null 1198 | }, 1199 | { 1200 | "kind": "ENUM", 1201 | "name": "__DirectiveLocation", 1202 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", 1203 | "fields": null, 1204 | "inputFields": null, 1205 | "interfaces": null, 1206 | "enumValues": [ 1207 | { 1208 | "name": "QUERY", 1209 | "description": "Location adjacent to a query operation.", 1210 | "isDeprecated": false, 1211 | "deprecationReason": null 1212 | }, 1213 | { 1214 | "name": "MUTATION", 1215 | "description": "Location adjacent to a mutation operation.", 1216 | "isDeprecated": false, 1217 | "deprecationReason": null 1218 | }, 1219 | { 1220 | "name": "SUBSCRIPTION", 1221 | "description": "Location adjacent to a subscription operation.", 1222 | "isDeprecated": false, 1223 | "deprecationReason": null 1224 | }, 1225 | { 1226 | "name": "FIELD", 1227 | "description": "Location adjacent to a field.", 1228 | "isDeprecated": false, 1229 | "deprecationReason": null 1230 | }, 1231 | { 1232 | "name": "FRAGMENT_DEFINITION", 1233 | "description": "Location adjacent to a fragment definition.", 1234 | "isDeprecated": false, 1235 | "deprecationReason": null 1236 | }, 1237 | { 1238 | "name": "FRAGMENT_SPREAD", 1239 | "description": "Location adjacent to a fragment spread.", 1240 | "isDeprecated": false, 1241 | "deprecationReason": null 1242 | }, 1243 | { 1244 | "name": "INLINE_FRAGMENT", 1245 | "description": "Location adjacent to an inline fragment.", 1246 | "isDeprecated": false, 1247 | "deprecationReason": null 1248 | }, 1249 | { 1250 | "name": "VARIABLE_DEFINITION", 1251 | "description": "Location adjacent to a variable definition.", 1252 | "isDeprecated": false, 1253 | "deprecationReason": null 1254 | }, 1255 | { 1256 | "name": "SCHEMA", 1257 | "description": "Location adjacent to a schema definition.", 1258 | "isDeprecated": false, 1259 | "deprecationReason": null 1260 | }, 1261 | { 1262 | "name": "SCALAR", 1263 | "description": "Location adjacent to a scalar definition.", 1264 | "isDeprecated": false, 1265 | "deprecationReason": null 1266 | }, 1267 | { 1268 | "name": "OBJECT", 1269 | "description": "Location adjacent to an object type definition.", 1270 | "isDeprecated": false, 1271 | "deprecationReason": null 1272 | }, 1273 | { 1274 | "name": "FIELD_DEFINITION", 1275 | "description": "Location adjacent to a field definition.", 1276 | "isDeprecated": false, 1277 | "deprecationReason": null 1278 | }, 1279 | { 1280 | "name": "ARGUMENT_DEFINITION", 1281 | "description": "Location adjacent to an argument definition.", 1282 | "isDeprecated": false, 1283 | "deprecationReason": null 1284 | }, 1285 | { 1286 | "name": "INTERFACE", 1287 | "description": "Location adjacent to an interface definition.", 1288 | "isDeprecated": false, 1289 | "deprecationReason": null 1290 | }, 1291 | { 1292 | "name": "UNION", 1293 | "description": "Location adjacent to a union definition.", 1294 | "isDeprecated": false, 1295 | "deprecationReason": null 1296 | }, 1297 | { 1298 | "name": "ENUM", 1299 | "description": "Location adjacent to an enum definition.", 1300 | "isDeprecated": false, 1301 | "deprecationReason": null 1302 | }, 1303 | { 1304 | "name": "ENUM_VALUE", 1305 | "description": "Location adjacent to an enum value definition.", 1306 | "isDeprecated": false, 1307 | "deprecationReason": null 1308 | }, 1309 | { 1310 | "name": "INPUT_OBJECT", 1311 | "description": "Location adjacent to an input object type definition.", 1312 | "isDeprecated": false, 1313 | "deprecationReason": null 1314 | }, 1315 | { 1316 | "name": "INPUT_FIELD_DEFINITION", 1317 | "description": "Location adjacent to an input object field definition.", 1318 | "isDeprecated": false, 1319 | "deprecationReason": null 1320 | } 1321 | ], 1322 | "possibleTypes": null 1323 | } 1324 | ], 1325 | "directives": [ 1326 | { 1327 | "name": "skip", 1328 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 1329 | "locations": [ 1330 | "FIELD", 1331 | "FRAGMENT_SPREAD", 1332 | "INLINE_FRAGMENT" 1333 | ], 1334 | "args": [ 1335 | { 1336 | "name": "if", 1337 | "description": "Skipped when true.", 1338 | "type": { 1339 | "kind": "NON_NULL", 1340 | "name": null, 1341 | "ofType": { 1342 | "kind": "SCALAR", 1343 | "name": "Boolean", 1344 | "ofType": null 1345 | } 1346 | }, 1347 | "defaultValue": null 1348 | } 1349 | ] 1350 | }, 1351 | { 1352 | "name": "include", 1353 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 1354 | "locations": [ 1355 | "FIELD", 1356 | "FRAGMENT_SPREAD", 1357 | "INLINE_FRAGMENT" 1358 | ], 1359 | "args": [ 1360 | { 1361 | "name": "if", 1362 | "description": "Included when true.", 1363 | "type": { 1364 | "kind": "NON_NULL", 1365 | "name": null, 1366 | "ofType": { 1367 | "kind": "SCALAR", 1368 | "name": "Boolean", 1369 | "ofType": null 1370 | } 1371 | }, 1372 | "defaultValue": null 1373 | } 1374 | ] 1375 | }, 1376 | { 1377 | "name": "deprecated", 1378 | "description": "Marks an element of a GraphQL schema as no longer supported.", 1379 | "locations": [ 1380 | "FIELD_DEFINITION", 1381 | "ENUM_VALUE" 1382 | ], 1383 | "args": [ 1384 | { 1385 | "name": "reason", 1386 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).", 1387 | "type": { 1388 | "kind": "SCALAR", 1389 | "name": "String", 1390 | "ofType": null 1391 | }, 1392 | "defaultValue": "\"No longer supported\"" 1393 | } 1394 | ] 1395 | } 1396 | ] 1397 | } 1398 | } -------------------------------------------------------------------------------- /test/unit/fixtures/invalidSchemaSDL.graphql: -------------------------------------------------------------------------------- 1 | type Rocket { 2 | id: ID! 3 | name: String 4 | type: String 5 | } 6 | 7 | type User { 8 | id: ID! 9 | email: String! 10 | trips: [Launch]! 11 | } 12 | 13 | type Mission { 14 | name: String 15 | missionPatch(size: PatchSize): String 16 | } 17 | 18 | enum PatchSize { 19 | SMALL 20 | LARGE 21 | } 22 | 23 | type Mutation { 24 | # if false, booking trips failed -- check errors 25 | 26 | bookTrips(launchIds: [ID]!): TripUpdateResponse! 27 | 28 | # if false, cancellation failed -- check errors 29 | cancelTrip(launchId: ID!): TripUpdateResponse! 30 | 31 | login(email: String): String # login token 32 | } 33 | 34 | type TripUpdateResponse { 35 | success: Boolean! 36 | message: String 37 | launches: [Launch] 38 | } 39 | 40 | """Launch Type is missing.""" -------------------------------------------------------------------------------- /test/unit/fixtures/issue#10.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | mutation: Mutation 4 | } 5 | input UserInput { 6 | name: String! 7 | email: String! 8 | } 9 | type User { 10 | id: String! 11 | name: String 12 | } 13 | type Query { 14 | user (id: String): User 15 | } 16 | type Mutation { 17 | addUser (input: UserInput): User! 18 | } -------------------------------------------------------------------------------- /test/unit/fixtures/seflRefDepthUnionSchema.graphql: -------------------------------------------------------------------------------- 1 | # A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. 2 | scalar DateTime 3 | 4 | type Organization { 5 | id: ID! 6 | createdBy: User! 7 | updatedBy: User! 8 | name: String! 9 | avatarUrl: String 10 | owner: String! 11 | } 12 | 13 | # User 14 | type User { 15 | id: ID! 16 | organization: OrganizationOrStringUnion 17 | idpId: String! 18 | firstName: String! 19 | lastName: String! 20 | createdBy: UserOrStringUnion 21 | updatedBy: UserOrStringUnion 22 | } 23 | 24 | union OrganizationOrStringUnion = Organization 25 | 26 | union UserOrStringUnion = User 27 | 28 | type Query { 29 | User(id: String!): User! 30 | } -------------------------------------------------------------------------------- /test/unit/fixtures/selfRefUnionTypeExample.graphql: -------------------------------------------------------------------------------- 1 | type Rocket { 2 | id: ID! 3 | name: String 4 | type: String 5 | } 6 | 7 | type User { 8 | id: ID! 9 | email: String! 10 | trips: [Launch]! 11 | } 12 | 13 | type Mission { 14 | name: String 15 | missionPatch(size: PatchSize): String 16 | } 17 | 18 | enum PatchSize { 19 | SMALL 20 | LARGE 21 | } 22 | 23 | type TripUpdateResponse { 24 | success: Boolean! 25 | message: String 26 | launches: [Launch] 27 | } 28 | type Query { 29 | unifiedLaunch(unifiedLaunchId: String!): BestLaunchers! 30 | } 31 | 32 | union BestLaunchers = BlueOrigin | SpaceX 33 | 34 | type BlueOrigin { 35 | newLaunchId(newLaunchId: String!): BestLaunchers! 36 | validate: [String!]! 37 | rockets: String! 38 | } 39 | 40 | type SpaceX { 41 | reusableRockets: String! 42 | dogeCoins: String! 43 | } 44 | 45 | type Launch { 46 | id: ID! 47 | site: String 48 | mission: Mission 49 | rocket: Rocket 50 | isBooked: Boolean! 51 | } -------------------------------------------------------------------------------- /test/unit/fixtures/validNestedArgumentSchema.graphql: -------------------------------------------------------------------------------- 1 | interface Node { 2 | id: ID! 3 | } 4 | 5 | type Query { 6 | node(id: ID!): Node 7 | } 8 | 9 | type Mutation { 10 | nested: Nested 11 | } 12 | 13 | type Nested { 14 | nested(filterBy: [[[String]]]): String 15 | } -------------------------------------------------------------------------------- /test/unit/fixtures/validNestedSchema.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | mutation: Mutation 3 | } 4 | type User { 5 | id: String! 6 | name: String 7 | } 8 | type Mutation { 9 | addUser (User: [[User]]!): User! 10 | } -------------------------------------------------------------------------------- /test/unit/fixtures/validSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schema": { 3 | "queryType": { 4 | "name": "Query" 5 | }, 6 | "mutationType": { 7 | "name": "Mutation" 8 | }, 9 | "subscriptionType": null, 10 | "types": [ 11 | { 12 | "kind": "OBJECT", 13 | "name": "Query", 14 | "description": null, 15 | "fields": [ 16 | { 17 | "name": "launches", 18 | "description": null, 19 | "args": [], 20 | "type": { 21 | "kind": "NON_NULL", 22 | "name": null, 23 | "ofType": { 24 | "kind": "LIST", 25 | "name": null, 26 | "ofType": { 27 | "kind": "OBJECT", 28 | "name": "Launch", 29 | "ofType": null 30 | } 31 | } 32 | }, 33 | "isDeprecated": false, 34 | "deprecationReason": null 35 | }, 36 | { 37 | "name": "launch", 38 | "description": null, 39 | "args": [ 40 | { 41 | "name": "id", 42 | "description": null, 43 | "type": { 44 | "kind": "NON_NULL", 45 | "name": null, 46 | "ofType": { 47 | "kind": "SCALAR", 48 | "name": "ID", 49 | "ofType": null 50 | } 51 | }, 52 | "defaultValue": null 53 | } 54 | ], 55 | "type": { 56 | "kind": "OBJECT", 57 | "name": "Launch", 58 | "ofType": null 59 | }, 60 | "isDeprecated": false, 61 | "deprecationReason": null 62 | }, 63 | { 64 | "name": "me", 65 | "description": null, 66 | "args": [], 67 | "type": { 68 | "kind": "OBJECT", 69 | "name": "User", 70 | "ofType": null 71 | }, 72 | "isDeprecated": false, 73 | "deprecationReason": null 74 | } 75 | ], 76 | "inputFields": null, 77 | "interfaces": [], 78 | "enumValues": null, 79 | "possibleTypes": null 80 | }, 81 | { 82 | "kind": "OBJECT", 83 | "name": "Launch", 84 | "description": null, 85 | "fields": [ 86 | { 87 | "name": "id", 88 | "description": null, 89 | "args": [], 90 | "type": { 91 | "kind": "NON_NULL", 92 | "name": null, 93 | "ofType": { 94 | "kind": "SCALAR", 95 | "name": "ID", 96 | "ofType": null 97 | } 98 | }, 99 | "isDeprecated": false, 100 | "deprecationReason": null 101 | }, 102 | { 103 | "name": "site", 104 | "description": null, 105 | "args": [], 106 | "type": { 107 | "kind": "SCALAR", 108 | "name": "String", 109 | "ofType": null 110 | }, 111 | "isDeprecated": false, 112 | "deprecationReason": null 113 | }, 114 | { 115 | "name": "mission", 116 | "description": null, 117 | "args": [], 118 | "type": { 119 | "kind": "OBJECT", 120 | "name": "Mission", 121 | "ofType": null 122 | }, 123 | "isDeprecated": false, 124 | "deprecationReason": null 125 | }, 126 | { 127 | "name": "rocket", 128 | "description": null, 129 | "args": [], 130 | "type": { 131 | "kind": "OBJECT", 132 | "name": "Rocket", 133 | "ofType": null 134 | }, 135 | "isDeprecated": false, 136 | "deprecationReason": null 137 | }, 138 | { 139 | "name": "isBooked", 140 | "description": null, 141 | "args": [], 142 | "type": { 143 | "kind": "NON_NULL", 144 | "name": null, 145 | "ofType": { 146 | "kind": "SCALAR", 147 | "name": "Boolean", 148 | "ofType": null 149 | } 150 | }, 151 | "isDeprecated": false, 152 | "deprecationReason": null 153 | } 154 | ], 155 | "inputFields": null, 156 | "interfaces": [], 157 | "enumValues": null, 158 | "possibleTypes": null 159 | }, 160 | { 161 | "kind": "SCALAR", 162 | "name": "ID", 163 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", 164 | "fields": null, 165 | "inputFields": null, 166 | "interfaces": null, 167 | "enumValues": null, 168 | "possibleTypes": null 169 | }, 170 | { 171 | "kind": "SCALAR", 172 | "name": "String", 173 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 174 | "fields": null, 175 | "inputFields": null, 176 | "interfaces": null, 177 | "enumValues": null, 178 | "possibleTypes": null 179 | }, 180 | { 181 | "kind": "OBJECT", 182 | "name": "Mission", 183 | "description": null, 184 | "fields": [ 185 | { 186 | "name": "name", 187 | "description": null, 188 | "args": [], 189 | "type": { 190 | "kind": "SCALAR", 191 | "name": "String", 192 | "ofType": null 193 | }, 194 | "isDeprecated": false, 195 | "deprecationReason": null 196 | }, 197 | { 198 | "name": "missionPatch", 199 | "description": null, 200 | "args": [ 201 | { 202 | "name": "size", 203 | "description": null, 204 | "type": { 205 | "kind": "ENUM", 206 | "name": "PatchSize", 207 | "ofType": null 208 | }, 209 | "defaultValue": null 210 | } 211 | ], 212 | "type": { 213 | "kind": "SCALAR", 214 | "name": "String", 215 | "ofType": null 216 | }, 217 | "isDeprecated": false, 218 | "deprecationReason": null 219 | } 220 | ], 221 | "inputFields": null, 222 | "interfaces": [], 223 | "enumValues": null, 224 | "possibleTypes": null 225 | }, 226 | { 227 | "kind": "ENUM", 228 | "name": "PatchSize", 229 | "description": null, 230 | "fields": null, 231 | "inputFields": null, 232 | "interfaces": null, 233 | "enumValues": [ 234 | { 235 | "name": "SMALL", 236 | "description": null, 237 | "isDeprecated": false, 238 | "deprecationReason": null 239 | }, 240 | { 241 | "name": "LARGE", 242 | "description": null, 243 | "isDeprecated": false, 244 | "deprecationReason": null 245 | } 246 | ], 247 | "possibleTypes": null 248 | }, 249 | { 250 | "kind": "OBJECT", 251 | "name": "Rocket", 252 | "description": null, 253 | "fields": [ 254 | { 255 | "name": "id", 256 | "description": null, 257 | "args": [], 258 | "type": { 259 | "kind": "NON_NULL", 260 | "name": null, 261 | "ofType": { 262 | "kind": "SCALAR", 263 | "name": "ID", 264 | "ofType": null 265 | } 266 | }, 267 | "isDeprecated": false, 268 | "deprecationReason": null 269 | }, 270 | { 271 | "name": "name", 272 | "description": null, 273 | "args": [], 274 | "type": { 275 | "kind": "SCALAR", 276 | "name": "String", 277 | "ofType": null 278 | }, 279 | "isDeprecated": false, 280 | "deprecationReason": null 281 | }, 282 | { 283 | "name": "type", 284 | "description": null, 285 | "args": [], 286 | "type": { 287 | "kind": "SCALAR", 288 | "name": "String", 289 | "ofType": null 290 | }, 291 | "isDeprecated": false, 292 | "deprecationReason": null 293 | } 294 | ], 295 | "inputFields": null, 296 | "interfaces": [], 297 | "enumValues": null, 298 | "possibleTypes": null 299 | }, 300 | { 301 | "kind": "SCALAR", 302 | "name": "Boolean", 303 | "description": "The `Boolean` scalar type represents `true` or `false`.", 304 | "fields": null, 305 | "inputFields": null, 306 | "interfaces": null, 307 | "enumValues": null, 308 | "possibleTypes": null 309 | }, 310 | { 311 | "kind": "OBJECT", 312 | "name": "User", 313 | "description": null, 314 | "fields": [ 315 | { 316 | "name": "id", 317 | "description": null, 318 | "args": [], 319 | "type": { 320 | "kind": "NON_NULL", 321 | "name": null, 322 | "ofType": { 323 | "kind": "SCALAR", 324 | "name": "ID", 325 | "ofType": null 326 | } 327 | }, 328 | "isDeprecated": false, 329 | "deprecationReason": null 330 | }, 331 | { 332 | "name": "email", 333 | "description": null, 334 | "args": [], 335 | "type": { 336 | "kind": "NON_NULL", 337 | "name": null, 338 | "ofType": { 339 | "kind": "SCALAR", 340 | "name": "String", 341 | "ofType": null 342 | } 343 | }, 344 | "isDeprecated": false, 345 | "deprecationReason": null 346 | }, 347 | { 348 | "name": "trips", 349 | "description": null, 350 | "args": [], 351 | "type": { 352 | "kind": "NON_NULL", 353 | "name": null, 354 | "ofType": { 355 | "kind": "LIST", 356 | "name": null, 357 | "ofType": { 358 | "kind": "OBJECT", 359 | "name": "Launch", 360 | "ofType": null 361 | } 362 | } 363 | }, 364 | "isDeprecated": false, 365 | "deprecationReason": null 366 | } 367 | ], 368 | "inputFields": null, 369 | "interfaces": [], 370 | "enumValues": null, 371 | "possibleTypes": null 372 | }, 373 | { 374 | "kind": "OBJECT", 375 | "name": "Mutation", 376 | "description": null, 377 | "fields": [ 378 | { 379 | "name": "bookTrips", 380 | "description": null, 381 | "args": [ 382 | { 383 | "name": "launchIds", 384 | "description": null, 385 | "type": { 386 | "kind": "NON_NULL", 387 | "name": null, 388 | "ofType": { 389 | "kind": "LIST", 390 | "name": null, 391 | "ofType": { 392 | "kind": "SCALAR", 393 | "name": "ID", 394 | "ofType": null 395 | } 396 | } 397 | }, 398 | "defaultValue": null 399 | } 400 | ], 401 | "type": { 402 | "kind": "NON_NULL", 403 | "name": null, 404 | "ofType": { 405 | "kind": "OBJECT", 406 | "name": "TripUpdateResponse", 407 | "ofType": null 408 | } 409 | }, 410 | "isDeprecated": false, 411 | "deprecationReason": null 412 | }, 413 | { 414 | "name": "cancelTrip", 415 | "description": null, 416 | "args": [ 417 | { 418 | "name": "launchId", 419 | "description": null, 420 | "type": { 421 | "kind": "NON_NULL", 422 | "name": null, 423 | "ofType": { 424 | "kind": "SCALAR", 425 | "name": "ID", 426 | "ofType": null 427 | } 428 | }, 429 | "defaultValue": null 430 | } 431 | ], 432 | "type": { 433 | "kind": "NON_NULL", 434 | "name": null, 435 | "ofType": { 436 | "kind": "OBJECT", 437 | "name": "TripUpdateResponse", 438 | "ofType": null 439 | } 440 | }, 441 | "isDeprecated": false, 442 | "deprecationReason": null 443 | }, 444 | { 445 | "name": "login", 446 | "description": null, 447 | "args": [ 448 | { 449 | "name": "email", 450 | "description": null, 451 | "type": { 452 | "kind": "SCALAR", 453 | "name": "String", 454 | "ofType": null 455 | }, 456 | "defaultValue": null 457 | } 458 | ], 459 | "type": { 460 | "kind": "SCALAR", 461 | "name": "String", 462 | "ofType": null 463 | }, 464 | "isDeprecated": false, 465 | "deprecationReason": null 466 | } 467 | ], 468 | "inputFields": null, 469 | "interfaces": [], 470 | "enumValues": null, 471 | "possibleTypes": null 472 | }, 473 | { 474 | "kind": "OBJECT", 475 | "name": "TripUpdateResponse", 476 | "description": null, 477 | "fields": [ 478 | { 479 | "name": "success", 480 | "description": null, 481 | "args": [], 482 | "type": { 483 | "kind": "NON_NULL", 484 | "name": null, 485 | "ofType": { 486 | "kind": "SCALAR", 487 | "name": "Boolean", 488 | "ofType": null 489 | } 490 | }, 491 | "isDeprecated": false, 492 | "deprecationReason": null 493 | }, 494 | { 495 | "name": "message", 496 | "description": null, 497 | "args": [], 498 | "type": { 499 | "kind": "SCALAR", 500 | "name": "String", 501 | "ofType": null 502 | }, 503 | "isDeprecated": false, 504 | "deprecationReason": null 505 | }, 506 | { 507 | "name": "launches", 508 | "description": null, 509 | "args": [], 510 | "type": { 511 | "kind": "LIST", 512 | "name": null, 513 | "ofType": { 514 | "kind": "OBJECT", 515 | "name": "Launch", 516 | "ofType": null 517 | } 518 | }, 519 | "isDeprecated": false, 520 | "deprecationReason": null 521 | } 522 | ], 523 | "inputFields": null, 524 | "interfaces": [], 525 | "enumValues": null, 526 | "possibleTypes": null 527 | }, 528 | { 529 | "kind": "OBJECT", 530 | "name": "__Schema", 531 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 532 | "fields": [ 533 | { 534 | "name": "types", 535 | "description": "A list of all types supported by this server.", 536 | "args": [], 537 | "type": { 538 | "kind": "NON_NULL", 539 | "name": null, 540 | "ofType": { 541 | "kind": "LIST", 542 | "name": null, 543 | "ofType": { 544 | "kind": "NON_NULL", 545 | "name": null, 546 | "ofType": { 547 | "kind": "OBJECT", 548 | "name": "__Type", 549 | "ofType": null 550 | } 551 | } 552 | } 553 | }, 554 | "isDeprecated": false, 555 | "deprecationReason": null 556 | }, 557 | { 558 | "name": "queryType", 559 | "description": "The type that query operations will be rooted at.", 560 | "args": [], 561 | "type": { 562 | "kind": "NON_NULL", 563 | "name": null, 564 | "ofType": { 565 | "kind": "OBJECT", 566 | "name": "__Type", 567 | "ofType": null 568 | } 569 | }, 570 | "isDeprecated": false, 571 | "deprecationReason": null 572 | }, 573 | { 574 | "name": "mutationType", 575 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 576 | "args": [], 577 | "type": { 578 | "kind": "OBJECT", 579 | "name": "__Type", 580 | "ofType": null 581 | }, 582 | "isDeprecated": false, 583 | "deprecationReason": null 584 | }, 585 | { 586 | "name": "subscriptionType", 587 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 588 | "args": [], 589 | "type": { 590 | "kind": "OBJECT", 591 | "name": "__Type", 592 | "ofType": null 593 | }, 594 | "isDeprecated": false, 595 | "deprecationReason": null 596 | }, 597 | { 598 | "name": "directives", 599 | "description": "A list of all directives supported by this server.", 600 | "args": [], 601 | "type": { 602 | "kind": "NON_NULL", 603 | "name": null, 604 | "ofType": { 605 | "kind": "LIST", 606 | "name": null, 607 | "ofType": { 608 | "kind": "NON_NULL", 609 | "name": null, 610 | "ofType": { 611 | "kind": "OBJECT", 612 | "name": "__Directive", 613 | "ofType": null 614 | } 615 | } 616 | } 617 | }, 618 | "isDeprecated": false, 619 | "deprecationReason": null 620 | } 621 | ], 622 | "inputFields": null, 623 | "interfaces": [], 624 | "enumValues": null, 625 | "possibleTypes": null 626 | }, 627 | { 628 | "kind": "OBJECT", 629 | "name": "__Type", 630 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 631 | "fields": [ 632 | { 633 | "name": "kind", 634 | "description": null, 635 | "args": [], 636 | "type": { 637 | "kind": "NON_NULL", 638 | "name": null, 639 | "ofType": { 640 | "kind": "ENUM", 641 | "name": "__TypeKind", 642 | "ofType": null 643 | } 644 | }, 645 | "isDeprecated": false, 646 | "deprecationReason": null 647 | }, 648 | { 649 | "name": "name", 650 | "description": null, 651 | "args": [], 652 | "type": { 653 | "kind": "SCALAR", 654 | "name": "String", 655 | "ofType": null 656 | }, 657 | "isDeprecated": false, 658 | "deprecationReason": null 659 | }, 660 | { 661 | "name": "description", 662 | "description": null, 663 | "args": [], 664 | "type": { 665 | "kind": "SCALAR", 666 | "name": "String", 667 | "ofType": null 668 | }, 669 | "isDeprecated": false, 670 | "deprecationReason": null 671 | }, 672 | { 673 | "name": "fields", 674 | "description": null, 675 | "args": [ 676 | { 677 | "name": "includeDeprecated", 678 | "description": null, 679 | "type": { 680 | "kind": "SCALAR", 681 | "name": "Boolean", 682 | "ofType": null 683 | }, 684 | "defaultValue": "false" 685 | } 686 | ], 687 | "type": { 688 | "kind": "LIST", 689 | "name": null, 690 | "ofType": { 691 | "kind": "NON_NULL", 692 | "name": null, 693 | "ofType": { 694 | "kind": "OBJECT", 695 | "name": "__Field", 696 | "ofType": null 697 | } 698 | } 699 | }, 700 | "isDeprecated": false, 701 | "deprecationReason": null 702 | }, 703 | { 704 | "name": "interfaces", 705 | "description": null, 706 | "args": [], 707 | "type": { 708 | "kind": "LIST", 709 | "name": null, 710 | "ofType": { 711 | "kind": "NON_NULL", 712 | "name": null, 713 | "ofType": { 714 | "kind": "OBJECT", 715 | "name": "__Type", 716 | "ofType": null 717 | } 718 | } 719 | }, 720 | "isDeprecated": false, 721 | "deprecationReason": null 722 | }, 723 | { 724 | "name": "possibleTypes", 725 | "description": null, 726 | "args": [], 727 | "type": { 728 | "kind": "LIST", 729 | "name": null, 730 | "ofType": { 731 | "kind": "NON_NULL", 732 | "name": null, 733 | "ofType": { 734 | "kind": "OBJECT", 735 | "name": "__Type", 736 | "ofType": null 737 | } 738 | } 739 | }, 740 | "isDeprecated": false, 741 | "deprecationReason": null 742 | }, 743 | { 744 | "name": "enumValues", 745 | "description": null, 746 | "args": [ 747 | { 748 | "name": "includeDeprecated", 749 | "description": null, 750 | "type": { 751 | "kind": "SCALAR", 752 | "name": "Boolean", 753 | "ofType": null 754 | }, 755 | "defaultValue": "false" 756 | } 757 | ], 758 | "type": { 759 | "kind": "LIST", 760 | "name": null, 761 | "ofType": { 762 | "kind": "NON_NULL", 763 | "name": null, 764 | "ofType": { 765 | "kind": "OBJECT", 766 | "name": "__EnumValue", 767 | "ofType": null 768 | } 769 | } 770 | }, 771 | "isDeprecated": false, 772 | "deprecationReason": null 773 | }, 774 | { 775 | "name": "inputFields", 776 | "description": null, 777 | "args": [], 778 | "type": { 779 | "kind": "LIST", 780 | "name": null, 781 | "ofType": { 782 | "kind": "NON_NULL", 783 | "name": null, 784 | "ofType": { 785 | "kind": "OBJECT", 786 | "name": "__InputValue", 787 | "ofType": null 788 | } 789 | } 790 | }, 791 | "isDeprecated": false, 792 | "deprecationReason": null 793 | }, 794 | { 795 | "name": "ofType", 796 | "description": null, 797 | "args": [], 798 | "type": { 799 | "kind": "OBJECT", 800 | "name": "__Type", 801 | "ofType": null 802 | }, 803 | "isDeprecated": false, 804 | "deprecationReason": null 805 | } 806 | ], 807 | "inputFields": null, 808 | "interfaces": [], 809 | "enumValues": null, 810 | "possibleTypes": null 811 | }, 812 | { 813 | "kind": "ENUM", 814 | "name": "__TypeKind", 815 | "description": "An enum describing what kind of type a given `__Type` is.", 816 | "fields": null, 817 | "inputFields": null, 818 | "interfaces": null, 819 | "enumValues": [ 820 | { 821 | "name": "SCALAR", 822 | "description": "Indicates this type is a scalar.", 823 | "isDeprecated": false, 824 | "deprecationReason": null 825 | }, 826 | { 827 | "name": "OBJECT", 828 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 829 | "isDeprecated": false, 830 | "deprecationReason": null 831 | }, 832 | { 833 | "name": "INTERFACE", 834 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 835 | "isDeprecated": false, 836 | "deprecationReason": null 837 | }, 838 | { 839 | "name": "UNION", 840 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 841 | "isDeprecated": false, 842 | "deprecationReason": null 843 | }, 844 | { 845 | "name": "ENUM", 846 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 847 | "isDeprecated": false, 848 | "deprecationReason": null 849 | }, 850 | { 851 | "name": "INPUT_OBJECT", 852 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 853 | "isDeprecated": false, 854 | "deprecationReason": null 855 | }, 856 | { 857 | "name": "LIST", 858 | "description": "Indicates this type is a list. `ofType` is a valid field.", 859 | "isDeprecated": false, 860 | "deprecationReason": null 861 | }, 862 | { 863 | "name": "NON_NULL", 864 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 865 | "isDeprecated": false, 866 | "deprecationReason": null 867 | } 868 | ], 869 | "possibleTypes": null 870 | }, 871 | { 872 | "kind": "OBJECT", 873 | "name": "__Field", 874 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 875 | "fields": [ 876 | { 877 | "name": "name", 878 | "description": null, 879 | "args": [], 880 | "type": { 881 | "kind": "NON_NULL", 882 | "name": null, 883 | "ofType": { 884 | "kind": "SCALAR", 885 | "name": "String", 886 | "ofType": null 887 | } 888 | }, 889 | "isDeprecated": false, 890 | "deprecationReason": null 891 | }, 892 | { 893 | "name": "description", 894 | "description": null, 895 | "args": [], 896 | "type": { 897 | "kind": "SCALAR", 898 | "name": "String", 899 | "ofType": null 900 | }, 901 | "isDeprecated": false, 902 | "deprecationReason": null 903 | }, 904 | { 905 | "name": "args", 906 | "description": null, 907 | "args": [], 908 | "type": { 909 | "kind": "NON_NULL", 910 | "name": null, 911 | "ofType": { 912 | "kind": "LIST", 913 | "name": null, 914 | "ofType": { 915 | "kind": "NON_NULL", 916 | "name": null, 917 | "ofType": { 918 | "kind": "OBJECT", 919 | "name": "__InputValue", 920 | "ofType": null 921 | } 922 | } 923 | } 924 | }, 925 | "isDeprecated": false, 926 | "deprecationReason": null 927 | }, 928 | { 929 | "name": "type", 930 | "description": null, 931 | "args": [], 932 | "type": { 933 | "kind": "NON_NULL", 934 | "name": null, 935 | "ofType": { 936 | "kind": "OBJECT", 937 | "name": "__Type", 938 | "ofType": null 939 | } 940 | }, 941 | "isDeprecated": false, 942 | "deprecationReason": null 943 | }, 944 | { 945 | "name": "isDeprecated", 946 | "description": null, 947 | "args": [], 948 | "type": { 949 | "kind": "NON_NULL", 950 | "name": null, 951 | "ofType": { 952 | "kind": "SCALAR", 953 | "name": "Boolean", 954 | "ofType": null 955 | } 956 | }, 957 | "isDeprecated": false, 958 | "deprecationReason": null 959 | }, 960 | { 961 | "name": "deprecationReason", 962 | "description": null, 963 | "args": [], 964 | "type": { 965 | "kind": "SCALAR", 966 | "name": "String", 967 | "ofType": null 968 | }, 969 | "isDeprecated": false, 970 | "deprecationReason": null 971 | } 972 | ], 973 | "inputFields": null, 974 | "interfaces": [], 975 | "enumValues": null, 976 | "possibleTypes": null 977 | }, 978 | { 979 | "kind": "OBJECT", 980 | "name": "__InputValue", 981 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 982 | "fields": [ 983 | { 984 | "name": "name", 985 | "description": null, 986 | "args": [], 987 | "type": { 988 | "kind": "NON_NULL", 989 | "name": null, 990 | "ofType": { 991 | "kind": "SCALAR", 992 | "name": "String", 993 | "ofType": null 994 | } 995 | }, 996 | "isDeprecated": false, 997 | "deprecationReason": null 998 | }, 999 | { 1000 | "name": "description", 1001 | "description": null, 1002 | "args": [], 1003 | "type": { 1004 | "kind": "SCALAR", 1005 | "name": "String", 1006 | "ofType": null 1007 | }, 1008 | "isDeprecated": false, 1009 | "deprecationReason": null 1010 | }, 1011 | { 1012 | "name": "type", 1013 | "description": null, 1014 | "args": [], 1015 | "type": { 1016 | "kind": "NON_NULL", 1017 | "name": null, 1018 | "ofType": { 1019 | "kind": "OBJECT", 1020 | "name": "__Type", 1021 | "ofType": null 1022 | } 1023 | }, 1024 | "isDeprecated": false, 1025 | "deprecationReason": null 1026 | }, 1027 | { 1028 | "name": "defaultValue", 1029 | "description": "A GraphQL-formatted string representing the default value for this input value.", 1030 | "args": [], 1031 | "type": { 1032 | "kind": "SCALAR", 1033 | "name": "String", 1034 | "ofType": null 1035 | }, 1036 | "isDeprecated": false, 1037 | "deprecationReason": null 1038 | } 1039 | ], 1040 | "inputFields": null, 1041 | "interfaces": [], 1042 | "enumValues": null, 1043 | "possibleTypes": null 1044 | }, 1045 | { 1046 | "kind": "OBJECT", 1047 | "name": "__EnumValue", 1048 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 1049 | "fields": [ 1050 | { 1051 | "name": "name", 1052 | "description": null, 1053 | "args": [], 1054 | "type": { 1055 | "kind": "NON_NULL", 1056 | "name": null, 1057 | "ofType": { 1058 | "kind": "SCALAR", 1059 | "name": "String", 1060 | "ofType": null 1061 | } 1062 | }, 1063 | "isDeprecated": false, 1064 | "deprecationReason": null 1065 | }, 1066 | { 1067 | "name": "description", 1068 | "description": null, 1069 | "args": [], 1070 | "type": { 1071 | "kind": "SCALAR", 1072 | "name": "String", 1073 | "ofType": null 1074 | }, 1075 | "isDeprecated": false, 1076 | "deprecationReason": null 1077 | }, 1078 | { 1079 | "name": "isDeprecated", 1080 | "description": null, 1081 | "args": [], 1082 | "type": { 1083 | "kind": "NON_NULL", 1084 | "name": null, 1085 | "ofType": { 1086 | "kind": "SCALAR", 1087 | "name": "Boolean", 1088 | "ofType": null 1089 | } 1090 | }, 1091 | "isDeprecated": false, 1092 | "deprecationReason": null 1093 | }, 1094 | { 1095 | "name": "deprecationReason", 1096 | "description": null, 1097 | "args": [], 1098 | "type": { 1099 | "kind": "SCALAR", 1100 | "name": "String", 1101 | "ofType": null 1102 | }, 1103 | "isDeprecated": false, 1104 | "deprecationReason": null 1105 | } 1106 | ], 1107 | "inputFields": null, 1108 | "interfaces": [], 1109 | "enumValues": null, 1110 | "possibleTypes": null 1111 | }, 1112 | { 1113 | "kind": "OBJECT", 1114 | "name": "__Directive", 1115 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 1116 | "fields": [ 1117 | { 1118 | "name": "name", 1119 | "description": null, 1120 | "args": [], 1121 | "type": { 1122 | "kind": "NON_NULL", 1123 | "name": null, 1124 | "ofType": { 1125 | "kind": "SCALAR", 1126 | "name": "String", 1127 | "ofType": null 1128 | } 1129 | }, 1130 | "isDeprecated": false, 1131 | "deprecationReason": null 1132 | }, 1133 | { 1134 | "name": "description", 1135 | "description": null, 1136 | "args": [], 1137 | "type": { 1138 | "kind": "SCALAR", 1139 | "name": "String", 1140 | "ofType": null 1141 | }, 1142 | "isDeprecated": false, 1143 | "deprecationReason": null 1144 | }, 1145 | { 1146 | "name": "locations", 1147 | "description": null, 1148 | "args": [], 1149 | "type": { 1150 | "kind": "NON_NULL", 1151 | "name": null, 1152 | "ofType": { 1153 | "kind": "LIST", 1154 | "name": null, 1155 | "ofType": { 1156 | "kind": "NON_NULL", 1157 | "name": null, 1158 | "ofType": { 1159 | "kind": "ENUM", 1160 | "name": "__DirectiveLocation", 1161 | "ofType": null 1162 | } 1163 | } 1164 | } 1165 | }, 1166 | "isDeprecated": false, 1167 | "deprecationReason": null 1168 | }, 1169 | { 1170 | "name": "args", 1171 | "description": null, 1172 | "args": [], 1173 | "type": { 1174 | "kind": "NON_NULL", 1175 | "name": null, 1176 | "ofType": { 1177 | "kind": "LIST", 1178 | "name": null, 1179 | "ofType": { 1180 | "kind": "NON_NULL", 1181 | "name": null, 1182 | "ofType": { 1183 | "kind": "OBJECT", 1184 | "name": "__InputValue", 1185 | "ofType": null 1186 | } 1187 | } 1188 | } 1189 | }, 1190 | "isDeprecated": false, 1191 | "deprecationReason": null 1192 | } 1193 | ], 1194 | "inputFields": null, 1195 | "interfaces": [], 1196 | "enumValues": null, 1197 | "possibleTypes": null 1198 | }, 1199 | { 1200 | "kind": "ENUM", 1201 | "name": "__DirectiveLocation", 1202 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", 1203 | "fields": null, 1204 | "inputFields": null, 1205 | "interfaces": null, 1206 | "enumValues": [ 1207 | { 1208 | "name": "QUERY", 1209 | "description": "Location adjacent to a query operation.", 1210 | "isDeprecated": false, 1211 | "deprecationReason": null 1212 | }, 1213 | { 1214 | "name": "MUTATION", 1215 | "description": "Location adjacent to a mutation operation.", 1216 | "isDeprecated": false, 1217 | "deprecationReason": null 1218 | }, 1219 | { 1220 | "name": "SUBSCRIPTION", 1221 | "description": "Location adjacent to a subscription operation.", 1222 | "isDeprecated": false, 1223 | "deprecationReason": null 1224 | }, 1225 | { 1226 | "name": "FIELD", 1227 | "description": "Location adjacent to a field.", 1228 | "isDeprecated": false, 1229 | "deprecationReason": null 1230 | }, 1231 | { 1232 | "name": "FRAGMENT_DEFINITION", 1233 | "description": "Location adjacent to a fragment definition.", 1234 | "isDeprecated": false, 1235 | "deprecationReason": null 1236 | }, 1237 | { 1238 | "name": "FRAGMENT_SPREAD", 1239 | "description": "Location adjacent to a fragment spread.", 1240 | "isDeprecated": false, 1241 | "deprecationReason": null 1242 | }, 1243 | { 1244 | "name": "INLINE_FRAGMENT", 1245 | "description": "Location adjacent to an inline fragment.", 1246 | "isDeprecated": false, 1247 | "deprecationReason": null 1248 | }, 1249 | { 1250 | "name": "VARIABLE_DEFINITION", 1251 | "description": "Location adjacent to a variable definition.", 1252 | "isDeprecated": false, 1253 | "deprecationReason": null 1254 | }, 1255 | { 1256 | "name": "SCHEMA", 1257 | "description": "Location adjacent to a schema definition.", 1258 | "isDeprecated": false, 1259 | "deprecationReason": null 1260 | }, 1261 | { 1262 | "name": "SCALAR", 1263 | "description": "Location adjacent to a scalar definition.", 1264 | "isDeprecated": false, 1265 | "deprecationReason": null 1266 | }, 1267 | { 1268 | "name": "OBJECT", 1269 | "description": "Location adjacent to an object type definition.", 1270 | "isDeprecated": false, 1271 | "deprecationReason": null 1272 | }, 1273 | { 1274 | "name": "FIELD_DEFINITION", 1275 | "description": "Location adjacent to a field definition.", 1276 | "isDeprecated": false, 1277 | "deprecationReason": null 1278 | }, 1279 | { 1280 | "name": "ARGUMENT_DEFINITION", 1281 | "description": "Location adjacent to an argument definition.", 1282 | "isDeprecated": false, 1283 | "deprecationReason": null 1284 | }, 1285 | { 1286 | "name": "INTERFACE", 1287 | "description": "Location adjacent to an interface definition.", 1288 | "isDeprecated": false, 1289 | "deprecationReason": null 1290 | }, 1291 | { 1292 | "name": "UNION", 1293 | "description": "Location adjacent to a union definition.", 1294 | "isDeprecated": false, 1295 | "deprecationReason": null 1296 | }, 1297 | { 1298 | "name": "ENUM", 1299 | "description": "Location adjacent to an enum definition.", 1300 | "isDeprecated": false, 1301 | "deprecationReason": null 1302 | }, 1303 | { 1304 | "name": "ENUM_VALUE", 1305 | "description": "Location adjacent to an enum value definition.", 1306 | "isDeprecated": false, 1307 | "deprecationReason": null 1308 | }, 1309 | { 1310 | "name": "INPUT_OBJECT", 1311 | "description": "Location adjacent to an input object type definition.", 1312 | "isDeprecated": false, 1313 | "deprecationReason": null 1314 | }, 1315 | { 1316 | "name": "INPUT_FIELD_DEFINITION", 1317 | "description": "Location adjacent to an input object field definition.", 1318 | "isDeprecated": false, 1319 | "deprecationReason": null 1320 | } 1321 | ], 1322 | "possibleTypes": null 1323 | } 1324 | ], 1325 | "directives": [ 1326 | { 1327 | "name": "skip", 1328 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 1329 | "locations": [ 1330 | "FIELD", 1331 | "FRAGMENT_SPREAD", 1332 | "INLINE_FRAGMENT" 1333 | ], 1334 | "args": [ 1335 | { 1336 | "name": "if", 1337 | "description": "Skipped when true.", 1338 | "type": { 1339 | "kind": "NON_NULL", 1340 | "name": null, 1341 | "ofType": { 1342 | "kind": "SCALAR", 1343 | "name": "Boolean", 1344 | "ofType": null 1345 | } 1346 | }, 1347 | "defaultValue": null 1348 | } 1349 | ] 1350 | }, 1351 | { 1352 | "name": "include", 1353 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 1354 | "locations": [ 1355 | "FIELD", 1356 | "FRAGMENT_SPREAD", 1357 | "INLINE_FRAGMENT" 1358 | ], 1359 | "args": [ 1360 | { 1361 | "name": "if", 1362 | "description": "Included when true.", 1363 | "type": { 1364 | "kind": "NON_NULL", 1365 | "name": null, 1366 | "ofType": { 1367 | "kind": "SCALAR", 1368 | "name": "Boolean", 1369 | "ofType": null 1370 | } 1371 | }, 1372 | "defaultValue": null 1373 | } 1374 | ] 1375 | }, 1376 | { 1377 | "name": "deprecated", 1378 | "description": "Marks an element of a GraphQL schema as no longer supported.", 1379 | "locations": [ 1380 | "FIELD_DEFINITION", 1381 | "ENUM_VALUE" 1382 | ], 1383 | "args": [ 1384 | { 1385 | "name": "reason", 1386 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).", 1387 | "type": { 1388 | "kind": "SCALAR", 1389 | "name": "String", 1390 | "ofType": null 1391 | }, 1392 | "defaultValue": "\"No longer supported\"" 1393 | } 1394 | ] 1395 | } 1396 | ] 1397 | } 1398 | } -------------------------------------------------------------------------------- /test/unit/fixtures/validSchemaSDL.graphql: -------------------------------------------------------------------------------- 1 | type Rocket { 2 | id: ID! 3 | name: String 4 | type: String 5 | } 6 | 7 | type User { 8 | id: ID! 9 | email: String! 10 | trips: [Launch]! 11 | } 12 | 13 | type Mission { 14 | name: String 15 | missionPatch(size: PatchSize): String 16 | } 17 | 18 | enum PatchSize { 19 | SMALL 20 | LARGE 21 | } 22 | 23 | type Mutation { 24 | # if false, booking trips failed -- check errors 25 | bookTrips(launchIds: [ID]!): TripUpdateResponse! 26 | 27 | # if false, cancellation failed -- check errors 28 | cancelTrip(launchId: ID!): TripUpdateResponse! 29 | 30 | login(email: String): String # login token 31 | } 32 | 33 | type TripUpdateResponse { 34 | success: Boolean! 35 | message: String 36 | launches: [Launch] 37 | } 38 | type Query { 39 | launches: [Launch]! 40 | launch(id: ID!): Launch 41 | # Queries for the current user 42 | me: User 43 | } 44 | 45 | type Launch { 46 | id: ID! 47 | site: String 48 | mission: Mission 49 | rocket: Rocket 50 | isBooked: Boolean! 51 | } -------------------------------------------------------------------------------- /test/unit/gql-generator.test.js: -------------------------------------------------------------------------------- 1 | const schemaToQuery = require('../../lib/assets/gql-generator').schemaToQuery, 2 | fs = require('fs'), 3 | path = require('path'), 4 | graphql = require('graphql'), 5 | validSchemaSDL = fs.readFileSync(path.join(__dirname, './fixtures/validSchemaSDL.graphql')).toString(), 6 | expect = require('chai').expect; 7 | 8 | describe('gql-generator tests', function () { 9 | it('should not throw type error for non defined elements in GQL schema', function (done) { 10 | const data = validSchemaSDL, 11 | gqlSchemaObj = graphql.buildSchema(data); 12 | 13 | // Set specific property as null to test behaviour for non defined nodes. 14 | gqlSchemaObj._typeMap.PatchSize = undefined; 15 | 16 | try { 17 | schemaToQuery(gqlSchemaObj, { depth: 5 }); 18 | } 19 | catch (e) { 20 | expect(e).to.be.undefined; 21 | } 22 | 23 | done(); 24 | }); 25 | 26 | it('should not throw type error for some of elements that are defined non-object in GQL schema', function (done) { 27 | const data = validSchemaSDL, 28 | gqlSchemaObj = graphql.buildSchema(data); 29 | 30 | // Set specific property as null to test behaviour for non defined nodes. 31 | gqlSchemaObj._mutationType._fields = undefined; 32 | 33 | try { 34 | schemaToQuery(gqlSchemaObj, { depth: 5 }); 35 | } 36 | catch (e) { 37 | expect(e).to.be.undefined; 38 | } 39 | 40 | done(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/plugin.test.js: -------------------------------------------------------------------------------- 1 | var path = '../../', 2 | expect = require('chai').expect, 3 | package = require(path), 4 | packageJson = require(path + '/package.json'); 5 | 6 | /* global describe, it */ 7 | describe(packageJson.name, function () { 8 | it('should contain all com_postman_plugin attributes', function (done) { 9 | expect(packageJson.com_postman_plugin).to.have.property('plugin_type'); 10 | expect(packageJson.com_postman_plugin).to.have.property('name'); 11 | expect(packageJson.com_postman_plugin).to.have.property('source_format'); 12 | expect(packageJson.com_postman_plugin).to.have.property('source_format_name'); 13 | done(); 14 | }); 15 | 16 | it('should expose the required functions', function (done) { 17 | expect(typeof package.validate).to.equal('function'); 18 | expect(typeof package.convert).to.equal('function'); 19 | done(); 20 | }); 21 | }); 22 | --------------------------------------------------------------------------------