├── LICENSE ├── README.md ├── benchmark.cr ├── graphiql ├── LICENSE ├── graphiql.css ├── graphiql.min.js └── index.html ├── public ├── shard.yml ├── spec ├── benchmark.cr ├── kemal-graphql_spec.cr └── spec_helper.cr └── src ├── hello_world_schema.cr ├── kemal-graphql.cr └── schema.cr /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kemal-graphql 2 | 3 | Coupling of [kemal](https://github.com/kemalcr/kemal), [grapqhiql](https://github.com/graphql/graphiql) and [graphql-crystal](https://github.com/ziprandom/graphql-crystal). 4 | 5 | ## Installation 6 | 7 | Clone the git repository, enter the repository directory and run : 8 | 9 | ```sh 10 | shards 11 | ``` 12 | 13 | to install all the dependencies. 14 | 15 | ## Usage 16 | 17 | build the project with 18 | 19 | ```sh 20 | crystal build --release src/kemal-graphql.cr 21 | ``` 22 | 23 | and start the server with 24 | 25 | ```sh 26 | ./kemal-graphql 27 | ``` 28 | 29 | or run it directly with (use the release flag to see the best performance) 30 | 31 | ```sh 32 | crystal run [--release] src/kemal-graphql.cr 33 | ``` 34 | 35 | now you can open up a browser and go to [http://localhost:3000/index.html](http://localhost:3000/index.html?query=%7B%0A%20%20posts%20%7B%0A%20%20%20%20title%0A%20%20%20%20author%20%7B%0A%20%20%20%20%20%20id%0A%20%20%20%20%20%20fullName%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D) to explore the graphql schema. 36 | 37 | ## Development 38 | 39 | run specs with 40 | 41 | ```sh 42 | KEMAL_ENV=test crystal spec 43 | ``` 44 | 45 | ## Contributing 46 | 47 | 1. Fork it ( https://github.com/ziprandom/kemal-graphql/fork ) 48 | 2. Create your feature branch (git checkout -b my-new-feature) 49 | 3. Commit your changes (git commit -am 'Add some feature') 50 | 4. Push to the branch (git push origin my-new-feature) 51 | 5. Create a new Pull Request 52 | 53 | ## Contributors 54 | 55 | - [[ziprandom]](https://github.com/ziprandom) - creator, maintainer 56 | - [[Facebook GraphQL]](https://github.com/graphql) - creator of graphiql 57 | -------------------------------------------------------------------------------- /benchmark.cr: -------------------------------------------------------------------------------- 1 | require "benchmark" 2 | require "./src/go_graphql_test_schema.cr" 3 | 4 | puts "running the query once to make sure the server works:" 5 | query = %{ 6 | query Example($size: Int) { 7 | a, 8 | b, 9 | x: c 10 | ...c 11 | f 12 | ...on DataType { 13 | pic(size: $size) 14 | promise { 15 | a 16 | } 17 | } 18 | deep { 19 | a 20 | b 21 | c 22 | deeper { 23 | a 24 | b 25 | } 26 | } 27 | } 28 | fragment c on DataType { 29 | d 30 | e 31 | } 32 | } 33 | 34 | puts Kemal::GraphQL::GO_GRAPHQL_TEST_SCHEMA.execute(query, {"size" => 50}).to_pretty_json 35 | 36 | Benchmark.ips do |x| 37 | x.report("running go_graphqls benchmark schema") do 38 | query = %{ 39 | query Example($size: Int) { 40 | a, 41 | b, 42 | x: c 43 | ...c 44 | f 45 | ...on DataType { 46 | pic(size: $size) 47 | promise { 48 | a 49 | } 50 | } 51 | deep { 52 | a 53 | b 54 | c 55 | deeper { 56 | a 57 | b 58 | } 59 | } 60 | } 61 | fragment c on DataType { 62 | d 63 | e 64 | } 65 | } 66 | 67 | Kemal::GraphQL::GO_GRAPHQL_TEST_SCHEMA.execute(query, {"size" => 50}) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /graphiql/LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE AGREEMENT For GraphiQL software 2 | 3 | Facebook, Inc. (“Facebook”) owns all right, title and interest, including all 4 | intellectual property and other proprietary rights, in and to the GraphiQL 5 | software. Subject to your compliance with these terms, you are hereby granted a 6 | non-exclusive, worldwide, royalty-free copyright license to (1) use and copy the 7 | GraphiQL software; and (2) reproduce and distribute the GraphiQL software as 8 | part of your own software (“Your Software”). Facebook reserves all rights not 9 | expressly granted to you in this license agreement. 10 | 11 | THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS OR 12 | IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 13 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. IN NO 14 | EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICES, DIRECTORS OR EMPLOYEES BE 15 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 16 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 17 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 18 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 19 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 20 | THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21 | 22 | You will include in Your Software (e.g., in the file(s), documentation or other 23 | materials accompanying your software): (1) the disclaimer set forth above; (2) 24 | this sentence; and (3) the following copyright notice: 25 | 26 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 27 | -------------------------------------------------------------------------------- /graphiql/graphiql.css: -------------------------------------------------------------------------------- 1 | .graphiql-container, 2 | .graphiql-container button, 3 | .graphiql-container input { 4 | color: #141823; 5 | font-family: 6 | system, 7 | -apple-system, 8 | 'San Francisco', 9 | '.SFNSDisplay-Regular', 10 | 'Segoe UI', 11 | Segoe, 12 | 'Segoe WP', 13 | 'Helvetica Neue', 14 | helvetica, 15 | 'Lucida Grande', 16 | arial, 17 | sans-serif; 18 | font-size: 14px; 19 | } 20 | 21 | .graphiql-container { 22 | display: -webkit-box; 23 | display: -ms-flexbox; 24 | display: flex; 25 | -webkit-box-orient: horizontal; 26 | -webkit-box-direction: normal; 27 | -ms-flex-direction: row; 28 | flex-direction: row; 29 | height: 100%; 30 | margin: 0; 31 | overflow: hidden; 32 | width: 100%; 33 | } 34 | 35 | .graphiql-container .editorWrap { 36 | display: -webkit-box; 37 | display: -ms-flexbox; 38 | display: flex; 39 | -webkit-box-orient: vertical; 40 | -webkit-box-direction: normal; 41 | -ms-flex-direction: column; 42 | flex-direction: column; 43 | -webkit-box-flex: 1; 44 | -ms-flex: 1; 45 | flex: 1; 46 | } 47 | 48 | .graphiql-container .title { 49 | font-size: 18px; 50 | } 51 | 52 | .graphiql-container .title em { 53 | font-family: georgia; 54 | font-size: 19px; 55 | } 56 | 57 | .graphiql-container .topBarWrap { 58 | display: -webkit-box; 59 | display: -ms-flexbox; 60 | display: flex; 61 | -webkit-box-orient: horizontal; 62 | -webkit-box-direction: normal; 63 | -ms-flex-direction: row; 64 | flex-direction: row; 65 | } 66 | 67 | .graphiql-container .topBar { 68 | -webkit-box-align: center; 69 | -ms-flex-align: center; 70 | align-items: center; 71 | background: linear-gradient(#f7f7f7, #e2e2e2); 72 | border-bottom: 1px solid #d0d0d0; 73 | cursor: default; 74 | display: -webkit-box; 75 | display: -ms-flexbox; 76 | display: flex; 77 | -webkit-box-orient: horizontal; 78 | -webkit-box-direction: normal; 79 | -ms-flex-direction: row; 80 | flex-direction: row; 81 | -webkit-box-flex: 1; 82 | -ms-flex: 1; 83 | flex: 1; 84 | height: 34px; 85 | padding: 7px 14px 6px; 86 | -webkit-user-select: none; 87 | -moz-user-select: none; 88 | -ms-user-select: none; 89 | user-select: none; 90 | } 91 | 92 | .graphiql-container .toolbar { 93 | overflow-x: visible; 94 | display: -webkit-box; 95 | display: -ms-flexbox; 96 | display: flex; 97 | } 98 | 99 | .graphiql-container .docExplorerShow, 100 | .graphiql-container .historyShow { 101 | background: linear-gradient(#f7f7f7, #e2e2e2); 102 | border-bottom: 1px solid #d0d0d0; 103 | border-right: none; 104 | border-top: none; 105 | color: #3B5998; 106 | cursor: pointer; 107 | font-size: 14px; 108 | margin: 0; 109 | outline: 0; 110 | padding: 2px 20px 0 18px; 111 | } 112 | 113 | .graphiql-container .docExplorerShow { 114 | border-left: 1px solid rgba(0, 0, 0, 0.2); 115 | } 116 | 117 | .graphiql-container .historyShow { 118 | border-right: 1px solid rgba(0, 0, 0, 0.2); 119 | border-left: 0; 120 | } 121 | 122 | .graphiql-container .docExplorerShow:before { 123 | border-left: 2px solid #3B5998; 124 | border-top: 2px solid #3B5998; 125 | content: ''; 126 | display: inline-block; 127 | height: 9px; 128 | margin: 0 3px -1px 0; 129 | position: relative; 130 | -webkit-transform: rotate(-45deg); 131 | transform: rotate(-45deg); 132 | width: 9px; 133 | } 134 | 135 | .graphiql-container .editorBar { 136 | display: -webkit-box; 137 | display: -ms-flexbox; 138 | display: flex; 139 | -webkit-box-orient: horizontal; 140 | -webkit-box-direction: normal; 141 | -ms-flex-direction: row; 142 | flex-direction: row; 143 | -webkit-box-flex: 1; 144 | -ms-flex: 1; 145 | flex: 1; 146 | } 147 | 148 | .graphiql-container .queryWrap { 149 | display: -webkit-box; 150 | display: -ms-flexbox; 151 | display: flex; 152 | -webkit-box-orient: vertical; 153 | -webkit-box-direction: normal; 154 | -ms-flex-direction: column; 155 | flex-direction: column; 156 | -webkit-box-flex: 1; 157 | -ms-flex: 1; 158 | flex: 1; 159 | } 160 | 161 | .graphiql-container .resultWrap { 162 | border-left: solid 1px #e0e0e0; 163 | display: -webkit-box; 164 | display: -ms-flexbox; 165 | display: flex; 166 | -webkit-box-orient: vertical; 167 | -webkit-box-direction: normal; 168 | -ms-flex-direction: column; 169 | flex-direction: column; 170 | -webkit-box-flex: 1; 171 | -ms-flex: 1; 172 | flex: 1; 173 | position: relative; 174 | } 175 | 176 | .graphiql-container .docExplorerWrap, 177 | .graphiql-container .historyPaneWrap { 178 | background: white; 179 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); 180 | position: relative; 181 | z-index: 3; 182 | } 183 | 184 | .graphiql-container .historyPaneWrap { 185 | min-width: 230px; 186 | z-index: 5; 187 | } 188 | 189 | .graphiql-container .docExplorerResizer { 190 | cursor: col-resize; 191 | height: 100%; 192 | left: -5px; 193 | position: absolute; 194 | top: 0; 195 | width: 10px; 196 | z-index: 10; 197 | } 198 | 199 | .graphiql-container .docExplorerHide { 200 | cursor: pointer; 201 | font-size: 18px; 202 | margin: -7px -8px -6px 0; 203 | padding: 18px 16px 15px 12px; 204 | } 205 | 206 | .graphiql-container .query-editor { 207 | -webkit-box-flex: 1; 208 | -ms-flex: 1; 209 | flex: 1; 210 | position: relative; 211 | } 212 | 213 | .graphiql-container .variable-editor { 214 | display: -webkit-box; 215 | display: -ms-flexbox; 216 | display: flex; 217 | -webkit-box-orient: vertical; 218 | -webkit-box-direction: normal; 219 | -ms-flex-direction: column; 220 | flex-direction: column; 221 | height: 29px; 222 | position: relative; 223 | } 224 | 225 | .graphiql-container .variable-editor-title { 226 | background: #eeeeee; 227 | border-bottom: 1px solid #d6d6d6; 228 | border-top: 1px solid #e0e0e0; 229 | color: #777; 230 | font-variant: small-caps; 231 | font-weight: bold; 232 | letter-spacing: 1px; 233 | line-height: 14px; 234 | padding: 6px 0 8px 43px; 235 | text-transform: lowercase; 236 | -webkit-user-select: none; 237 | -moz-user-select: none; 238 | -ms-user-select: none; 239 | user-select: none; 240 | } 241 | 242 | .graphiql-container .codemirrorWrap { 243 | -webkit-box-flex: 1; 244 | -ms-flex: 1; 245 | flex: 1; 246 | height: 100%; 247 | position: relative; 248 | } 249 | 250 | .graphiql-container .result-window { 251 | -webkit-box-flex: 1; 252 | -ms-flex: 1; 253 | flex: 1; 254 | height: 100%; 255 | position: relative; 256 | } 257 | 258 | .graphiql-container .footer { 259 | background: #f6f7f8; 260 | border-left: 1px solid #e0e0e0; 261 | border-top: 1px solid #e0e0e0; 262 | margin-left: 12px; 263 | position: relative; 264 | } 265 | 266 | .graphiql-container .footer:before { 267 | background: #eeeeee; 268 | bottom: 0; 269 | content: " "; 270 | left: -13px; 271 | position: absolute; 272 | top: -1px; 273 | width: 12px; 274 | } 275 | 276 | /* No `.graphiql-container` here so themes can overwrite */ 277 | .result-window .CodeMirror { 278 | background: #f6f7f8; 279 | } 280 | 281 | .graphiql-container .result-window .CodeMirror-gutters { 282 | background-color: #eeeeee; 283 | border-color: #e0e0e0; 284 | cursor: col-resize; 285 | } 286 | 287 | .graphiql-container .result-window .CodeMirror-foldgutter, 288 | .graphiql-container .result-window .CodeMirror-foldgutter-open:after, 289 | .graphiql-container .result-window .CodeMirror-foldgutter-folded:after { 290 | padding-left: 3px; 291 | } 292 | 293 | .graphiql-container .toolbar-button { 294 | background: #fdfdfd; 295 | background: linear-gradient(#f9f9f9, #ececec); 296 | border-radius: 3px; 297 | box-shadow: 298 | inset 0 0 0 1px rgba(0,0,0,0.20), 299 | 0 1px 0 rgba(255,255,255, 0.7), 300 | inset 0 1px #fff; 301 | color: #555; 302 | cursor: pointer; 303 | display: inline-block; 304 | margin: 0 5px; 305 | padding: 3px 11px 5px; 306 | text-decoration: none; 307 | text-overflow: ellipsis; 308 | white-space: nowrap; 309 | max-width: 150px; 310 | } 311 | 312 | .graphiql-container .toolbar-button:active { 313 | background: linear-gradient(#ececec, #d5d5d5); 314 | box-shadow: 315 | 0 1px 0 rgba(255, 255, 255, 0.7), 316 | inset 0 0 0 1px rgba(0,0,0,0.10), 317 | inset 0 1px 1px 1px rgba(0, 0, 0, 0.12), 318 | inset 0 0 5px rgba(0, 0, 0, 0.1); 319 | } 320 | 321 | .graphiql-container .toolbar-button.error { 322 | background: linear-gradient(#fdf3f3, #e6d6d7); 323 | color: #b00; 324 | } 325 | 326 | .graphiql-container .toolbar-button-group { 327 | margin: 0 5px; 328 | white-space: nowrap; 329 | } 330 | 331 | .graphiql-container .toolbar-button-group > * { 332 | margin: 0; 333 | } 334 | 335 | .graphiql-container .toolbar-button-group > *:not(:last-child) { 336 | border-top-right-radius: 0; 337 | border-bottom-right-radius: 0; 338 | } 339 | 340 | .graphiql-container .toolbar-button-group > *:not(:first-child) { 341 | border-top-left-radius: 0; 342 | border-bottom-left-radius: 0; 343 | margin-left: -1px; 344 | } 345 | 346 | .graphiql-container .execute-button-wrap { 347 | height: 34px; 348 | margin: 0 14px 0 28px; 349 | position: relative; 350 | } 351 | 352 | .graphiql-container .execute-button { 353 | background: linear-gradient(#fdfdfd, #d2d3d6); 354 | border-radius: 17px; 355 | border: 1px solid rgba(0,0,0,0.25); 356 | box-shadow: 0 1px 0 #fff; 357 | cursor: pointer; 358 | fill: #444; 359 | height: 34px; 360 | margin: 0; 361 | padding: 0; 362 | width: 34px; 363 | } 364 | 365 | .graphiql-container .execute-button svg { 366 | pointer-events: none; 367 | } 368 | 369 | .graphiql-container .execute-button:active { 370 | background: linear-gradient(#e6e6e6, #c3c3c3); 371 | box-shadow: 372 | 0 1px 0 #fff, 373 | inset 0 0 2px rgba(0, 0, 0, 0.2), 374 | inset 0 0 6px rgba(0, 0, 0, 0.1); 375 | } 376 | 377 | .graphiql-container .execute-button:focus { 378 | outline: 0; 379 | } 380 | 381 | .graphiql-container .toolbar-menu, 382 | .graphiql-container .toolbar-select { 383 | position: relative; 384 | } 385 | 386 | .graphiql-container .execute-options, 387 | .graphiql-container .toolbar-menu-items, 388 | .graphiql-container .toolbar-select-options { 389 | background: #fff; 390 | box-shadow: 391 | 0 0 0 1px rgba(0,0,0,0.1), 392 | 0 2px 4px rgba(0,0,0,0.25); 393 | margin: 0; 394 | padding: 6px 0; 395 | position: absolute; 396 | z-index: 100; 397 | } 398 | 399 | .graphiql-container .execute-options { 400 | min-width: 100px; 401 | top: 37px; 402 | left: -1px; 403 | } 404 | 405 | .graphiql-container .toolbar-menu-items { 406 | left: 1px; 407 | margin-top: -1px; 408 | min-width: 110%; 409 | top: 100%; 410 | visibility: hidden; 411 | } 412 | 413 | .graphiql-container .toolbar-menu-items.open { 414 | visibility: visible; 415 | } 416 | 417 | .graphiql-container .toolbar-select-options { 418 | left: 0; 419 | min-width: 100%; 420 | top: -5px; 421 | visibility: hidden; 422 | } 423 | 424 | .graphiql-container .toolbar-select-options.open { 425 | visibility: visible; 426 | } 427 | 428 | .graphiql-container .execute-options > li, 429 | .graphiql-container .toolbar-menu-items > li, 430 | .graphiql-container .toolbar-select-options > li { 431 | cursor: pointer; 432 | display: block; 433 | margin: none; 434 | max-width: 300px; 435 | overflow: hidden; 436 | padding: 2px 20px 4px 11px; 437 | text-overflow: ellipsis; 438 | white-space: nowrap; 439 | } 440 | 441 | .graphiql-container .execute-options > li.selected, 442 | .graphiql-container .toolbar-menu-items > li.hover, 443 | .graphiql-container .toolbar-menu-items > li:active, 444 | .graphiql-container .toolbar-menu-items > li:hover, 445 | .graphiql-container .toolbar-select-options > li.hover, 446 | .graphiql-container .toolbar-select-options > li:active, 447 | .graphiql-container .toolbar-select-options > li:hover, 448 | .graphiql-container .history-contents > p:hover, 449 | .graphiql-container .history-contents > p:active { 450 | background: #e10098; 451 | color: #fff; 452 | } 453 | 454 | .graphiql-container .toolbar-select-options > li > svg { 455 | display: inline; 456 | fill: #666; 457 | margin: 0 -6px 0 6px; 458 | pointer-events: none; 459 | vertical-align: middle; 460 | } 461 | 462 | .graphiql-container .toolbar-select-options > li.hover > svg, 463 | .graphiql-container .toolbar-select-options > li:active > svg, 464 | .graphiql-container .toolbar-select-options > li:hover > svg { 465 | fill: #fff; 466 | } 467 | 468 | .graphiql-container .CodeMirror-scroll { 469 | overflow-scrolling: touch; 470 | } 471 | 472 | .graphiql-container .CodeMirror { 473 | color: #141823; 474 | font-family: 475 | 'Consolas', 476 | 'Inconsolata', 477 | 'Droid Sans Mono', 478 | 'Monaco', 479 | monospace; 480 | font-size: 13px; 481 | height: 100%; 482 | left: 0; 483 | position: absolute; 484 | top: 0; 485 | width: 100%; 486 | } 487 | 488 | .graphiql-container .CodeMirror-lines { 489 | padding: 20px 0; 490 | } 491 | 492 | .CodeMirror-hint-information .content { 493 | box-orient: vertical; 494 | color: #141823; 495 | display: -webkit-box; 496 | display: -ms-flexbox; 497 | display: flex; 498 | font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Segoe UI', Segoe, 'Segoe WP', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif; 499 | font-size: 13px; 500 | line-clamp: 3; 501 | line-height: 16px; 502 | max-height: 48px; 503 | overflow: hidden; 504 | text-overflow: -o-ellipsis-lastline; 505 | } 506 | 507 | .CodeMirror-hint-information .content p:first-child { 508 | margin-top: 0; 509 | } 510 | 511 | .CodeMirror-hint-information .content p:last-child { 512 | margin-bottom: 0; 513 | } 514 | 515 | .CodeMirror-hint-information .infoType { 516 | color: #CA9800; 517 | cursor: pointer; 518 | display: inline; 519 | margin-right: 0.5em; 520 | } 521 | 522 | .autoInsertedLeaf.cm-property { 523 | -webkit-animation-duration: 6s; 524 | animation-duration: 6s; 525 | -webkit-animation-name: insertionFade; 526 | animation-name: insertionFade; 527 | border-bottom: 2px solid rgba(255, 255, 255, 0); 528 | border-radius: 2px; 529 | margin: -2px -4px -1px; 530 | padding: 2px 4px 1px; 531 | } 532 | 533 | @-webkit-keyframes insertionFade { 534 | from, to { 535 | background: rgba(255, 255, 255, 0); 536 | border-color: rgba(255, 255, 255, 0); 537 | } 538 | 539 | 15%, 85% { 540 | background: #fbffc9; 541 | border-color: #f0f3c0; 542 | } 543 | } 544 | 545 | @keyframes insertionFade { 546 | from, to { 547 | background: rgba(255, 255, 255, 0); 548 | border-color: rgba(255, 255, 255, 0); 549 | } 550 | 551 | 15%, 85% { 552 | background: #fbffc9; 553 | border-color: #f0f3c0; 554 | } 555 | } 556 | 557 | div.CodeMirror-lint-tooltip { 558 | background-color: white; 559 | border-radius: 2px; 560 | border: 0; 561 | color: #141823; 562 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 563 | font-family: 564 | system, 565 | -apple-system, 566 | 'San Francisco', 567 | '.SFNSDisplay-Regular', 568 | 'Segoe UI', 569 | Segoe, 570 | 'Segoe WP', 571 | 'Helvetica Neue', 572 | helvetica, 573 | 'Lucida Grande', 574 | arial, 575 | sans-serif; 576 | font-size: 13px; 577 | line-height: 16px; 578 | max-width: 430px; 579 | opacity: 0; 580 | padding: 8px 10px; 581 | transition: opacity 0.15s; 582 | white-space: pre-wrap; 583 | } 584 | 585 | div.CodeMirror-lint-tooltip > * { 586 | padding-left: 23px; 587 | } 588 | 589 | div.CodeMirror-lint-tooltip > * + * { 590 | margin-top: 12px; 591 | } 592 | 593 | /* COLORS */ 594 | 595 | .graphiql-container .CodeMirror-foldmarker { 596 | border-radius: 4px; 597 | background: #08f; 598 | background: linear-gradient(#43A8FF, #0F83E8); 599 | box-shadow: 600 | 0 1px 1px rgba(0, 0, 0, 0.2), 601 | inset 0 0 0 1px rgba(0, 0, 0, 0.1); 602 | color: white; 603 | font-family: arial; 604 | font-size: 12px; 605 | line-height: 0; 606 | margin: 0 3px; 607 | padding: 0px 4px 1px; 608 | text-shadow: 0 -1px rgba(0, 0, 0, 0.1); 609 | } 610 | 611 | .graphiql-container div.CodeMirror span.CodeMirror-matchingbracket { 612 | color: #555; 613 | text-decoration: underline; 614 | } 615 | 616 | .graphiql-container div.CodeMirror span.CodeMirror-nonmatchingbracket { 617 | color: #f00; 618 | } 619 | 620 | /* Comment */ 621 | .cm-comment { 622 | color: #999; 623 | } 624 | 625 | /* Punctuation */ 626 | .cm-punctuation { 627 | color: #555; 628 | } 629 | 630 | /* Keyword */ 631 | .cm-keyword { 632 | color: #B11A04; 633 | } 634 | 635 | /* OperationName, FragmentName */ 636 | .cm-def { 637 | color: #D2054E; 638 | } 639 | 640 | /* FieldName */ 641 | .cm-property { 642 | color: #1F61A0; 643 | } 644 | 645 | /* FieldAlias */ 646 | .cm-qualifier { 647 | color: #1C92A9; 648 | } 649 | 650 | /* ArgumentName and ObjectFieldName */ 651 | .cm-attribute { 652 | color: #8B2BB9; 653 | } 654 | 655 | /* Number */ 656 | .cm-number { 657 | color: #2882F9; 658 | } 659 | 660 | /* String */ 661 | .cm-string { 662 | color: #D64292; 663 | } 664 | 665 | /* Boolean */ 666 | .cm-builtin { 667 | color: #D47509; 668 | } 669 | 670 | /* EnumValue */ 671 | .cm-string-2 { 672 | color: #0B7FC7; 673 | } 674 | 675 | /* Variable */ 676 | .cm-variable { 677 | color: #397D13; 678 | } 679 | 680 | /* Directive */ 681 | .cm-meta { 682 | color: #B33086; 683 | } 684 | 685 | /* Type */ 686 | .cm-atom { 687 | color: #CA9800; 688 | } 689 | /* BASICS */ 690 | 691 | .CodeMirror { 692 | /* Set height, width, borders, and global font properties here */ 693 | color: black; 694 | font-family: monospace; 695 | height: 300px; 696 | } 697 | 698 | /* PADDING */ 699 | 700 | .CodeMirror-lines { 701 | padding: 4px 0; /* Vertical padding around content */ 702 | } 703 | .CodeMirror pre { 704 | padding: 0 4px; /* Horizontal padding of content */ 705 | } 706 | 707 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 708 | background-color: white; /* The little square between H and V scrollbars */ 709 | } 710 | 711 | /* GUTTER */ 712 | 713 | .CodeMirror-gutters { 714 | border-right: 1px solid #ddd; 715 | background-color: #f7f7f7; 716 | white-space: nowrap; 717 | } 718 | .CodeMirror-linenumbers {} 719 | .CodeMirror-linenumber { 720 | color: #999; 721 | min-width: 20px; 722 | padding: 0 3px 0 5px; 723 | text-align: right; 724 | white-space: nowrap; 725 | } 726 | 727 | .CodeMirror-guttermarker { color: black; } 728 | .CodeMirror-guttermarker-subtle { color: #999; } 729 | 730 | /* CURSOR */ 731 | 732 | .CodeMirror div.CodeMirror-cursor { 733 | border-left: 1px solid black; 734 | } 735 | /* Shown when moving in bi-directional text */ 736 | .CodeMirror div.CodeMirror-secondarycursor { 737 | border-left: 1px solid silver; 738 | } 739 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 740 | background: #7e7; 741 | border: 0; 742 | width: auto; 743 | } 744 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 745 | z-index: 1; 746 | } 747 | 748 | .cm-animate-fat-cursor { 749 | -webkit-animation: blink 1.06s steps(1) infinite; 750 | animation: blink 1.06s steps(1) infinite; 751 | border: 0; 752 | width: auto; 753 | } 754 | @-webkit-keyframes blink { 755 | 0% { background: #7e7; } 756 | 50% { background: none; } 757 | 100% { background: #7e7; } 758 | } 759 | @keyframes blink { 760 | 0% { background: #7e7; } 761 | 50% { background: none; } 762 | 100% { background: #7e7; } 763 | } 764 | 765 | /* Can style cursor different in overwrite (non-insert) mode */ 766 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 767 | 768 | .cm-tab { display: inline-block; text-decoration: inherit; } 769 | 770 | .CodeMirror-ruler { 771 | border-left: 1px solid #ccc; 772 | position: absolute; 773 | } 774 | 775 | /* DEFAULT THEME */ 776 | 777 | .cm-s-default .cm-keyword {color: #708;} 778 | .cm-s-default .cm-atom {color: #219;} 779 | .cm-s-default .cm-number {color: #164;} 780 | .cm-s-default .cm-def {color: #00f;} 781 | .cm-s-default .cm-variable, 782 | .cm-s-default .cm-punctuation, 783 | .cm-s-default .cm-property, 784 | .cm-s-default .cm-operator {} 785 | .cm-s-default .cm-variable-2 {color: #05a;} 786 | .cm-s-default .cm-variable-3 {color: #085;} 787 | .cm-s-default .cm-comment {color: #a50;} 788 | .cm-s-default .cm-string {color: #a11;} 789 | .cm-s-default .cm-string-2 {color: #f50;} 790 | .cm-s-default .cm-meta {color: #555;} 791 | .cm-s-default .cm-qualifier {color: #555;} 792 | .cm-s-default .cm-builtin {color: #30a;} 793 | .cm-s-default .cm-bracket {color: #997;} 794 | .cm-s-default .cm-tag {color: #170;} 795 | .cm-s-default .cm-attribute {color: #00c;} 796 | .cm-s-default .cm-header {color: blue;} 797 | .cm-s-default .cm-quote {color: #090;} 798 | .cm-s-default .cm-hr {color: #999;} 799 | .cm-s-default .cm-link {color: #00c;} 800 | 801 | .cm-negative {color: #d44;} 802 | .cm-positive {color: #292;} 803 | .cm-header, .cm-strong {font-weight: bold;} 804 | .cm-em {font-style: italic;} 805 | .cm-link {text-decoration: underline;} 806 | .cm-strikethrough {text-decoration: line-through;} 807 | 808 | .cm-s-default .cm-error {color: #f00;} 809 | .cm-invalidchar {color: #f00;} 810 | 811 | .CodeMirror-composing { border-bottom: 2px solid; } 812 | 813 | /* Default styles for common addons */ 814 | 815 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 816 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 817 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 818 | .CodeMirror-activeline-background {background: #e8f2ff;} 819 | 820 | /* STOP */ 821 | 822 | /* The rest of this file contains styles related to the mechanics of 823 | the editor. You probably shouldn't touch them. */ 824 | 825 | .CodeMirror { 826 | background: white; 827 | overflow: hidden; 828 | position: relative; 829 | } 830 | 831 | .CodeMirror-scroll { 832 | height: 100%; 833 | /* 30px is the magic margin used to hide the element's real scrollbars */ 834 | /* See overflow: hidden in .CodeMirror */ 835 | margin-bottom: -30px; margin-right: -30px; 836 | outline: none; /* Prevent dragging from highlighting the element */ 837 | overflow: scroll !important; /* Things will break if this is overridden */ 838 | padding-bottom: 30px; 839 | position: relative; 840 | } 841 | .CodeMirror-sizer { 842 | border-right: 30px solid transparent; 843 | position: relative; 844 | } 845 | 846 | /* The fake, visible scrollbars. Used to force redraw during scrolling 847 | before actual scrolling happens, thus preventing shaking and 848 | flickering artifacts. */ 849 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 850 | display: none; 851 | position: absolute; 852 | z-index: 6; 853 | } 854 | .CodeMirror-vscrollbar { 855 | overflow-x: hidden; 856 | overflow-y: scroll; 857 | right: 0; top: 0; 858 | } 859 | .CodeMirror-hscrollbar { 860 | bottom: 0; left: 0; 861 | overflow-x: scroll; 862 | overflow-y: hidden; 863 | } 864 | .CodeMirror-scrollbar-filler { 865 | right: 0; bottom: 0; 866 | } 867 | .CodeMirror-gutter-filler { 868 | left: 0; bottom: 0; 869 | } 870 | 871 | .CodeMirror-gutters { 872 | min-height: 100%; 873 | position: absolute; left: 0; top: 0; 874 | z-index: 3; 875 | } 876 | .CodeMirror-gutter { 877 | display: inline-block; 878 | height: 100%; 879 | margin-bottom: -30px; 880 | vertical-align: top; 881 | white-space: normal; 882 | /* Hack to make IE7 behave */ 883 | *zoom:1; 884 | *display:inline; 885 | } 886 | .CodeMirror-gutter-wrapper { 887 | background: none !important; 888 | border: none !important; 889 | position: absolute; 890 | z-index: 4; 891 | } 892 | .CodeMirror-gutter-background { 893 | position: absolute; 894 | top: 0; bottom: 0; 895 | z-index: 4; 896 | } 897 | .CodeMirror-gutter-elt { 898 | cursor: default; 899 | position: absolute; 900 | z-index: 4; 901 | } 902 | .CodeMirror-gutter-wrapper { 903 | -webkit-user-select: none; 904 | -moz-user-select: none; 905 | -ms-user-select: none; 906 | user-select: none; 907 | } 908 | 909 | .CodeMirror-lines { 910 | cursor: text; 911 | min-height: 1px; /* prevents collapsing before first draw */ 912 | } 913 | .CodeMirror pre { 914 | -webkit-tap-highlight-color: transparent; 915 | /* Reset some styles that the rest of the page might have set */ 916 | background: transparent; 917 | border-radius: 0; 918 | border-width: 0; 919 | color: inherit; 920 | font-family: inherit; 921 | font-size: inherit; 922 | -webkit-font-variant-ligatures: none; 923 | font-variant-ligatures: none; 924 | line-height: inherit; 925 | margin: 0; 926 | overflow: visible; 927 | position: relative; 928 | white-space: pre; 929 | word-wrap: normal; 930 | z-index: 2; 931 | } 932 | .CodeMirror-wrap pre { 933 | word-wrap: break-word; 934 | white-space: pre-wrap; 935 | word-break: normal; 936 | } 937 | 938 | .CodeMirror-linebackground { 939 | position: absolute; 940 | left: 0; right: 0; top: 0; bottom: 0; 941 | z-index: 0; 942 | } 943 | 944 | .CodeMirror-linewidget { 945 | overflow: auto; 946 | position: relative; 947 | z-index: 2; 948 | } 949 | 950 | .CodeMirror-widget {} 951 | 952 | .CodeMirror-code { 953 | outline: none; 954 | } 955 | 956 | /* Force content-box sizing for the elements where we expect it */ 957 | .CodeMirror-scroll, 958 | .CodeMirror-sizer, 959 | .CodeMirror-gutter, 960 | .CodeMirror-gutters, 961 | .CodeMirror-linenumber { 962 | box-sizing: content-box; 963 | } 964 | 965 | .CodeMirror-measure { 966 | height: 0; 967 | overflow: hidden; 968 | position: absolute; 969 | visibility: hidden; 970 | width: 100%; 971 | } 972 | 973 | .CodeMirror-cursor { position: absolute; } 974 | .CodeMirror-measure pre { position: static; } 975 | 976 | div.CodeMirror-cursors { 977 | position: relative; 978 | visibility: hidden; 979 | z-index: 3; 980 | } 981 | div.CodeMirror-dragcursors { 982 | visibility: visible; 983 | } 984 | 985 | .CodeMirror-focused div.CodeMirror-cursors { 986 | visibility: visible; 987 | } 988 | 989 | .CodeMirror-selected { background: #d9d9d9; } 990 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 991 | .CodeMirror-crosshair { cursor: crosshair; } 992 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 993 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 994 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 995 | 996 | .cm-searching { 997 | background: #ffa; 998 | background: rgba(255, 255, 0, .4); 999 | } 1000 | 1001 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 1002 | .CodeMirror span { *vertical-align: text-bottom; } 1003 | 1004 | /* Used to force a border model for a node */ 1005 | .cm-force-border { padding-right: .1px; } 1006 | 1007 | @media print { 1008 | /* Hide the cursor when printing */ 1009 | .CodeMirror div.CodeMirror-cursors { 1010 | visibility: hidden; 1011 | } 1012 | } 1013 | 1014 | /* See issue #2901 */ 1015 | .cm-tab-wrap-hack:after { content: ''; } 1016 | 1017 | /* Help users use markselection to safely style text background */ 1018 | span.CodeMirror-selectedtext { background: none; } 1019 | 1020 | .CodeMirror-dialog { 1021 | background: inherit; 1022 | color: inherit; 1023 | left: 0; right: 0; 1024 | overflow: hidden; 1025 | padding: .1em .8em; 1026 | position: absolute; 1027 | z-index: 15; 1028 | } 1029 | 1030 | .CodeMirror-dialog-top { 1031 | border-bottom: 1px solid #eee; 1032 | top: 0; 1033 | } 1034 | 1035 | .CodeMirror-dialog-bottom { 1036 | border-top: 1px solid #eee; 1037 | bottom: 0; 1038 | } 1039 | 1040 | .CodeMirror-dialog input { 1041 | background: transparent; 1042 | border: 1px solid #d3d6db; 1043 | color: inherit; 1044 | font-family: monospace; 1045 | outline: none; 1046 | width: 20em; 1047 | } 1048 | 1049 | .CodeMirror-dialog button { 1050 | font-size: 70%; 1051 | } 1052 | .graphiql-container .doc-explorer { 1053 | background: white; 1054 | } 1055 | 1056 | .graphiql-container .doc-explorer-title-bar, 1057 | .graphiql-container .history-title-bar { 1058 | cursor: default; 1059 | display: -webkit-box; 1060 | display: -ms-flexbox; 1061 | display: flex; 1062 | height: 34px; 1063 | line-height: 14px; 1064 | padding: 8px 8px 5px; 1065 | position: relative; 1066 | -webkit-user-select: none; 1067 | -moz-user-select: none; 1068 | -ms-user-select: none; 1069 | user-select: none; 1070 | } 1071 | 1072 | .graphiql-container .doc-explorer-title, 1073 | .graphiql-container .history-title { 1074 | -webkit-box-flex: 1; 1075 | -ms-flex: 1; 1076 | flex: 1; 1077 | font-weight: bold; 1078 | overflow-x: hidden; 1079 | padding: 10px 0 10px 10px; 1080 | text-align: center; 1081 | text-overflow: ellipsis; 1082 | -webkit-user-select: initial; 1083 | -moz-user-select: initial; 1084 | -ms-user-select: initial; 1085 | user-select: initial; 1086 | white-space: nowrap; 1087 | } 1088 | 1089 | .graphiql-container .doc-explorer-back { 1090 | color: #3B5998; 1091 | cursor: pointer; 1092 | margin: -7px 0 -6px -8px; 1093 | overflow-x: hidden; 1094 | padding: 17px 12px 16px 16px; 1095 | text-overflow: ellipsis; 1096 | white-space: nowrap; 1097 | } 1098 | 1099 | .doc-explorer-narrow .doc-explorer-back { 1100 | width: 0; 1101 | } 1102 | 1103 | .graphiql-container .doc-explorer-back:before { 1104 | border-left: 2px solid #3B5998; 1105 | border-top: 2px solid #3B5998; 1106 | content: ''; 1107 | display: inline-block; 1108 | height: 9px; 1109 | margin: 0 3px -1px 0; 1110 | position: relative; 1111 | -webkit-transform: rotate(-45deg); 1112 | transform: rotate(-45deg); 1113 | width: 9px; 1114 | } 1115 | 1116 | .graphiql-container .doc-explorer-rhs { 1117 | position: relative; 1118 | } 1119 | 1120 | .graphiql-container .doc-explorer-contents, 1121 | .graphiql-container .history-contents { 1122 | background-color: #ffffff; 1123 | border-top: 1px solid #d6d6d6; 1124 | bottom: 0; 1125 | left: 0; 1126 | overflow-y: auto; 1127 | padding: 20px 15px; 1128 | position: absolute; 1129 | right: 0; 1130 | top: 47px; 1131 | } 1132 | 1133 | .graphiql-container .doc-explorer-contents { 1134 | min-width: 300px; 1135 | } 1136 | 1137 | .graphiql-container .doc-type-description p:first-child , 1138 | .graphiql-container .doc-type-description blockquote:first-child { 1139 | margin-top: 0; 1140 | } 1141 | 1142 | .graphiql-container .doc-explorer-contents a { 1143 | cursor: pointer; 1144 | text-decoration: none; 1145 | } 1146 | 1147 | .graphiql-container .doc-explorer-contents a:hover { 1148 | text-decoration: underline; 1149 | } 1150 | 1151 | .graphiql-container .doc-value-description > :first-child { 1152 | margin-top: 4px; 1153 | } 1154 | 1155 | .graphiql-container .doc-value-description > :last-child { 1156 | margin-bottom: 4px; 1157 | } 1158 | 1159 | .graphiql-container .doc-category { 1160 | margin: 20px 0; 1161 | } 1162 | 1163 | .graphiql-container .doc-category-title { 1164 | border-bottom: 1px solid #e0e0e0; 1165 | color: #777; 1166 | cursor: default; 1167 | font-size: 14px; 1168 | font-variant: small-caps; 1169 | font-weight: bold; 1170 | letter-spacing: 1px; 1171 | margin: 0 -15px 10px 0; 1172 | padding: 10px 0; 1173 | -webkit-user-select: none; 1174 | -moz-user-select: none; 1175 | -ms-user-select: none; 1176 | user-select: none; 1177 | } 1178 | 1179 | .graphiql-container .doc-category-item { 1180 | margin: 12px 0; 1181 | color: #555; 1182 | } 1183 | 1184 | .graphiql-container .keyword { 1185 | color: #B11A04; 1186 | } 1187 | 1188 | .graphiql-container .type-name { 1189 | color: #CA9800; 1190 | } 1191 | 1192 | .graphiql-container .field-name { 1193 | color: #1F61A0; 1194 | } 1195 | 1196 | .graphiql-container .field-short-description { 1197 | color: #999; 1198 | margin-left: 5px; 1199 | overflow: hidden; 1200 | text-overflow: ellipsis; 1201 | white-space: nowrap; 1202 | } 1203 | 1204 | .graphiql-container .enum-value { 1205 | color: #0B7FC7; 1206 | } 1207 | 1208 | .graphiql-container .arg-name { 1209 | color: #8B2BB9; 1210 | } 1211 | 1212 | .graphiql-container .arg { 1213 | display: block; 1214 | margin-left: 1em; 1215 | } 1216 | 1217 | .graphiql-container .arg:first-child:last-child, 1218 | .graphiql-container .arg:first-child:nth-last-child(2), 1219 | .graphiql-container .arg:first-child:nth-last-child(2) ~ .arg { 1220 | display: inherit; 1221 | margin: inherit; 1222 | } 1223 | 1224 | .graphiql-container .arg:first-child:nth-last-child(2):after { 1225 | content: ', '; 1226 | } 1227 | 1228 | .graphiql-container .arg-default-value { 1229 | color: #0B7FC7; 1230 | } 1231 | 1232 | .graphiql-container .doc-deprecation { 1233 | background: #fffae8; 1234 | box-shadow: inset 0 0 1px #bfb063; 1235 | color: #867F70; 1236 | line-height: 16px; 1237 | margin: 8px -8px; 1238 | max-height: 80px; 1239 | overflow: hidden; 1240 | padding: 8px; 1241 | border-radius: 3px; 1242 | } 1243 | 1244 | .graphiql-container .doc-deprecation:before { 1245 | content: 'Deprecated:'; 1246 | color: #c79b2e; 1247 | cursor: default; 1248 | display: block; 1249 | font-size: 9px; 1250 | font-weight: bold; 1251 | letter-spacing: 1px; 1252 | line-height: 1; 1253 | padding-bottom: 5px; 1254 | text-transform: uppercase; 1255 | -webkit-user-select: none; 1256 | -moz-user-select: none; 1257 | -ms-user-select: none; 1258 | user-select: none; 1259 | } 1260 | 1261 | .graphiql-container .doc-deprecation > :first-child { 1262 | margin-top: 0; 1263 | } 1264 | 1265 | .graphiql-container .doc-deprecation > :last-child { 1266 | margin-bottom: 0; 1267 | } 1268 | 1269 | .graphiql-container .show-btn { 1270 | -webkit-appearance: initial; 1271 | display: block; 1272 | border-radius: 3px; 1273 | border: solid 1px #ccc; 1274 | text-align: center; 1275 | padding: 8px 12px 10px; 1276 | width: 100%; 1277 | box-sizing: border-box; 1278 | background: #fbfcfc; 1279 | color: #555; 1280 | cursor: pointer; 1281 | } 1282 | 1283 | .graphiql-container .search-box { 1284 | border-bottom: 1px solid #d3d6db; 1285 | display: block; 1286 | font-size: 14px; 1287 | margin: -15px -15px 12px 0; 1288 | position: relative; 1289 | } 1290 | 1291 | .graphiql-container .search-box:before { 1292 | content: '\26b2'; 1293 | cursor: pointer; 1294 | display: block; 1295 | font-size: 24px; 1296 | position: absolute; 1297 | top: -2px; 1298 | -webkit-transform: rotate(-45deg); 1299 | transform: rotate(-45deg); 1300 | -webkit-user-select: none; 1301 | -moz-user-select: none; 1302 | -ms-user-select: none; 1303 | user-select: none; 1304 | } 1305 | 1306 | .graphiql-container .search-box .search-box-clear { 1307 | background-color: #d0d0d0; 1308 | border-radius: 12px; 1309 | color: #fff; 1310 | cursor: pointer; 1311 | font-size: 11px; 1312 | padding: 1px 5px 2px; 1313 | position: absolute; 1314 | right: 3px; 1315 | top: 8px; 1316 | -webkit-user-select: none; 1317 | -moz-user-select: none; 1318 | -ms-user-select: none; 1319 | user-select: none; 1320 | } 1321 | 1322 | .graphiql-container .search-box .search-box-clear:hover { 1323 | background-color: #b9b9b9; 1324 | } 1325 | 1326 | .graphiql-container .search-box > input { 1327 | border: none; 1328 | box-sizing: border-box; 1329 | font-size: 14px; 1330 | outline: none; 1331 | padding: 6px 24px 8px 20px; 1332 | width: 100%; 1333 | } 1334 | 1335 | .graphiql-container .error-container { 1336 | font-weight: bold; 1337 | left: 0; 1338 | letter-spacing: 1px; 1339 | opacity: 0.5; 1340 | position: absolute; 1341 | right: 0; 1342 | text-align: center; 1343 | text-transform: uppercase; 1344 | top: 50%; 1345 | -webkit-transform: translate(0, -50%); 1346 | transform: translate(0, -50%); 1347 | } 1348 | .CodeMirror-foldmarker { 1349 | color: blue; 1350 | cursor: pointer; 1351 | font-family: arial; 1352 | line-height: .3; 1353 | text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; 1354 | } 1355 | .CodeMirror-foldgutter { 1356 | width: .7em; 1357 | } 1358 | .CodeMirror-foldgutter-open, 1359 | .CodeMirror-foldgutter-folded { 1360 | cursor: pointer; 1361 | } 1362 | .CodeMirror-foldgutter-open:after { 1363 | content: "\25BE"; 1364 | } 1365 | .CodeMirror-foldgutter-folded:after { 1366 | content: "\25B8"; 1367 | } 1368 | .graphiql-container .history-contents { 1369 | font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; 1370 | padding: 0; 1371 | } 1372 | 1373 | .graphiql-container .history-contents p { 1374 | font-size: 12px; 1375 | overflow: hidden; 1376 | text-overflow: ellipsis; 1377 | white-space: nowrap; 1378 | margin: 0; 1379 | padding: 8px; 1380 | border-bottom: 1px solid #e0e0e0; 1381 | } 1382 | 1383 | .graphiql-container .history-contents p:hover { 1384 | cursor: pointer; 1385 | } 1386 | .CodeMirror-info { 1387 | background: white; 1388 | border-radius: 2px; 1389 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1390 | box-sizing: border-box; 1391 | color: #555; 1392 | font-family: 1393 | system, 1394 | -apple-system, 1395 | 'San Francisco', 1396 | '.SFNSDisplay-Regular', 1397 | 'Segoe UI', 1398 | Segoe, 1399 | 'Segoe WP', 1400 | 'Helvetica Neue', 1401 | helvetica, 1402 | 'Lucida Grande', 1403 | arial, 1404 | sans-serif; 1405 | font-size: 13px; 1406 | line-height: 16px; 1407 | margin: 8px -8px; 1408 | max-width: 400px; 1409 | opacity: 0; 1410 | overflow: hidden; 1411 | padding: 8px 8px; 1412 | position: fixed; 1413 | transition: opacity 0.15s; 1414 | z-index: 50; 1415 | } 1416 | 1417 | .CodeMirror-info :first-child { 1418 | margin-top: 0; 1419 | } 1420 | 1421 | .CodeMirror-info :last-child { 1422 | margin-bottom: 0; 1423 | } 1424 | 1425 | .CodeMirror-info p { 1426 | margin: 1em 0; 1427 | } 1428 | 1429 | .CodeMirror-info .info-description { 1430 | color: #777; 1431 | line-height: 16px; 1432 | margin-top: 1em; 1433 | max-height: 80px; 1434 | overflow: hidden; 1435 | } 1436 | 1437 | .CodeMirror-info .info-deprecation { 1438 | background: #fffae8; 1439 | box-shadow: inset 0 1px 1px -1px #bfb063; 1440 | color: #867F70; 1441 | line-height: 16px; 1442 | margin: -8px; 1443 | margin-top: 8px; 1444 | max-height: 80px; 1445 | overflow: hidden; 1446 | padding: 8px; 1447 | } 1448 | 1449 | .CodeMirror-info .info-deprecation-label { 1450 | color: #c79b2e; 1451 | cursor: default; 1452 | display: block; 1453 | font-size: 9px; 1454 | font-weight: bold; 1455 | letter-spacing: 1px; 1456 | line-height: 1; 1457 | padding-bottom: 5px; 1458 | text-transform: uppercase; 1459 | -webkit-user-select: none; 1460 | -moz-user-select: none; 1461 | -ms-user-select: none; 1462 | user-select: none; 1463 | } 1464 | 1465 | .CodeMirror-info .info-deprecation-label + * { 1466 | margin-top: 0; 1467 | } 1468 | 1469 | .CodeMirror-info a { 1470 | text-decoration: none; 1471 | } 1472 | 1473 | .CodeMirror-info a:hover { 1474 | text-decoration: underline; 1475 | } 1476 | 1477 | .CodeMirror-info .type-name { 1478 | color: #CA9800; 1479 | } 1480 | 1481 | .CodeMirror-info .field-name { 1482 | color: #1F61A0; 1483 | } 1484 | 1485 | .CodeMirror-info .enum-value { 1486 | color: #0B7FC7; 1487 | } 1488 | 1489 | .CodeMirror-info .arg-name { 1490 | color: #8B2BB9; 1491 | } 1492 | 1493 | .CodeMirror-info .directive-name { 1494 | color: #B33086; 1495 | } 1496 | .CodeMirror-jump-token { 1497 | text-decoration: underline; 1498 | cursor: pointer; 1499 | } 1500 | /* The lint marker gutter */ 1501 | .CodeMirror-lint-markers { 1502 | width: 16px; 1503 | } 1504 | 1505 | .CodeMirror-lint-tooltip { 1506 | background-color: infobackground; 1507 | border-radius: 4px 4px 4px 4px; 1508 | border: 1px solid black; 1509 | color: infotext; 1510 | font-family: monospace; 1511 | font-size: 10pt; 1512 | max-width: 600px; 1513 | opacity: 0; 1514 | overflow: hidden; 1515 | padding: 2px 5px; 1516 | position: fixed; 1517 | transition: opacity .4s; 1518 | white-space: pre-wrap; 1519 | z-index: 100; 1520 | } 1521 | 1522 | .CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { 1523 | background-position: left bottom; 1524 | background-repeat: repeat-x; 1525 | } 1526 | 1527 | .CodeMirror-lint-mark-error { 1528 | background-image: 1529 | url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") 1530 | ; 1531 | } 1532 | 1533 | .CodeMirror-lint-mark-warning { 1534 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); 1535 | } 1536 | 1537 | .CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { 1538 | background-position: center center; 1539 | background-repeat: no-repeat; 1540 | cursor: pointer; 1541 | display: inline-block; 1542 | height: 16px; 1543 | position: relative; 1544 | vertical-align: middle; 1545 | width: 16px; 1546 | } 1547 | 1548 | .CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { 1549 | background-position: top left; 1550 | background-repeat: no-repeat; 1551 | padding-left: 18px; 1552 | } 1553 | 1554 | .CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { 1555 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); 1556 | } 1557 | 1558 | .CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { 1559 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); 1560 | } 1561 | 1562 | .CodeMirror-lint-marker-multiple { 1563 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); 1564 | background-position: right bottom; 1565 | background-repeat: no-repeat; 1566 | width: 100%; height: 100%; 1567 | } 1568 | .graphiql-container .spinner-container { 1569 | height: 36px; 1570 | left: 50%; 1571 | position: absolute; 1572 | top: 50%; 1573 | -webkit-transform: translate(-50%, -50%); 1574 | transform: translate(-50%, -50%); 1575 | width: 36px; 1576 | z-index: 10; 1577 | } 1578 | 1579 | .graphiql-container .spinner { 1580 | -webkit-animation: rotation .6s infinite linear; 1581 | animation: rotation .6s infinite linear; 1582 | border-bottom: 6px solid rgba(150, 150, 150, .15); 1583 | border-left: 6px solid rgba(150, 150, 150, .15); 1584 | border-radius: 100%; 1585 | border-right: 6px solid rgba(150, 150, 150, .15); 1586 | border-top: 6px solid rgba(150, 150, 150, .8); 1587 | display: inline-block; 1588 | height: 24px; 1589 | position: absolute; 1590 | vertical-align: middle; 1591 | width: 24px; 1592 | } 1593 | 1594 | @-webkit-keyframes rotation { 1595 | from { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 1596 | to { -webkit-transform: rotate(359deg); transform: rotate(359deg); } 1597 | } 1598 | 1599 | @keyframes rotation { 1600 | from { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 1601 | to { -webkit-transform: rotate(359deg); transform: rotate(359deg); } 1602 | } 1603 | .CodeMirror-hints { 1604 | background: white; 1605 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1606 | font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; 1607 | font-size: 13px; 1608 | list-style: none; 1609 | margin-left: -6px; 1610 | margin: 0; 1611 | max-height: 14.5em; 1612 | overflow-y: auto; 1613 | overflow: hidden; 1614 | padding: 0; 1615 | position: absolute; 1616 | z-index: 10; 1617 | } 1618 | 1619 | .CodeMirror-hint { 1620 | border-top: solid 1px #f7f7f7; 1621 | color: #141823; 1622 | cursor: pointer; 1623 | margin: 0; 1624 | max-width: 300px; 1625 | overflow: hidden; 1626 | padding: 2px 6px; 1627 | white-space: pre; 1628 | } 1629 | 1630 | li.CodeMirror-hint-active { 1631 | background-color: #08f; 1632 | border-top-color: white; 1633 | color: white; 1634 | } 1635 | 1636 | .CodeMirror-hint-information { 1637 | border-top: solid 1px #c0c0c0; 1638 | max-width: 300px; 1639 | padding: 4px 6px; 1640 | position: relative; 1641 | z-index: 1; 1642 | } 1643 | 1644 | .CodeMirror-hint-information:first-child { 1645 | border-bottom: solid 1px #c0c0c0; 1646 | border-top: none; 1647 | margin-bottom: -1px; 1648 | } 1649 | 1650 | .CodeMirror-hint-deprecation { 1651 | background: #fffae8; 1652 | box-shadow: inset 0 1px 1px -1px #bfb063; 1653 | color: #867F70; 1654 | font-family: 1655 | system, 1656 | -apple-system, 1657 | 'San Francisco', 1658 | '.SFNSDisplay-Regular', 1659 | 'Segoe UI', 1660 | Segoe, 1661 | 'Segoe WP', 1662 | 'Helvetica Neue', 1663 | helvetica, 1664 | 'Lucida Grande', 1665 | arial, 1666 | sans-serif; 1667 | font-size: 13px; 1668 | line-height: 16px; 1669 | margin-top: 4px; 1670 | max-height: 80px; 1671 | overflow: hidden; 1672 | padding: 6px; 1673 | } 1674 | 1675 | .CodeMirror-hint-deprecation .deprecation-label { 1676 | color: #c79b2e; 1677 | cursor: default; 1678 | display: block; 1679 | font-size: 9px; 1680 | font-weight: bold; 1681 | letter-spacing: 1px; 1682 | line-height: 1; 1683 | padding-bottom: 5px; 1684 | text-transform: uppercase; 1685 | -webkit-user-select: none; 1686 | -moz-user-select: none; 1687 | -ms-user-select: none; 1688 | user-select: none; 1689 | } 1690 | 1691 | .CodeMirror-hint-deprecation .deprecation-label + * { 1692 | margin-top: 0; 1693 | } 1694 | 1695 | .CodeMirror-hint-deprecation :last-child { 1696 | margin-bottom: 0; 1697 | } 1698 | -------------------------------------------------------------------------------- /graphiql/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 |
Loading...
47 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /public: -------------------------------------------------------------------------------- 1 | graphiql -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: kemal-graphql 2 | version: 0.1.0 3 | 4 | authors: 5 | - ziprandom - 6 | 7 | targets: 8 | kemal-graphql: 9 | main: src/kemal-graphql.cr 10 | 11 | dependencies: 12 | kemal: 13 | github: kemalcr/kemal 14 | branch: master 15 | spec-kemal: 16 | github: kemalcr/spec-kemal 17 | branch: master 18 | graphql-crystal: 19 | github: ziprandom/graphql-crystal 20 | branch: master 21 | 22 | license: MIT 23 | -------------------------------------------------------------------------------- /spec/benchmark.cr: -------------------------------------------------------------------------------- 1 | require "spec-kemal" 2 | require "benchmark" 3 | require "../src/kemal-graphql.cr" 4 | 5 | payload = { 6 | "payload" => { 7 | "postId" => Kemal::GraphQL::POSTS.last.id, 8 | "body" => "would be the most wonderful thing", 9 | "authorId" => Kemal::GraphQL::USERS[1].id 10 | } 11 | }.to_json 12 | 13 | query_string = <<-query 14 | mutation CreateComment($payload: CommentInput) { 15 | comment(payload: $payload) { 16 | author { first_name } 17 | body 18 | } 19 | } 20 | query 21 | 22 | puts JSON.parse( 23 | get( 24 | "/schema?query=#{query_string}", nil, payload 25 | ).body 26 | ).to_pretty_json 27 | -------------------------------------------------------------------------------- /spec/kemal-graphql_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec-kemal" 2 | require "./spec_helper" 3 | require "benchmark" 4 | 5 | describe Kemal::GraphQL do 6 | 7 | payload = { 8 | "payload" => { 9 | "postId" => Kemal::GraphQL::POSTS.last.id, 10 | "body" => "would be the most wonderful thing", 11 | "authorId" => Kemal::GraphQL::USERS[1].id 12 | } 13 | } 14 | 15 | query_string = <<-query 16 | mutation CreateComment($payload: CommentInput) { 17 | comment(payload: $payload) { 18 | body 19 | } 20 | } 21 | query 22 | 23 | it "serves the schema on query params" do 24 | get("/api_query?query=#{URI.escape(query_string)}&variables=#{URI.escape(payload.to_json)}", nil) 25 | response.body.should eq "{\"data\":{\"comment\":{\"body\":\"would be the most wonderful thing\"}}}" 26 | end 27 | 28 | it "serves the schema on json params" do 29 | body = {"query" => query_string, "variables" => payload}.to_json 30 | post("/graphql", headers: HTTP::Headers{"Content-Type" => "application/json"}, body: body) 31 | response.body.should eq "{\"data\":{\"comment\":{\"body\":\"would be the most wonderful thing\"}}}" 32 | end 33 | 34 | it "passes the username header down to the callback" do 35 | body = {"query" => query_string, "variables" => payload}.to_json 36 | post("/graphql", headers: HTTP::Headers{"Content-Type" => "application/json", "USERNAME" => "tom"}, body: body) 37 | response.body.should eq "{\"data\":{\"comment\":{\"body\":\"would be the most wonderful thing\"}}}" 38 | 39 | post("/graphql", headers: HTTP::Headers{"Content-Type" => "application/json", "USERNAME" => "ada"}, body: body) 40 | response.body.should eq "{\"data\":{\"comment\":{\"body\":\"would be the most wonderful thing\"}}}" 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/*" 3 | -------------------------------------------------------------------------------- /src/hello_world_schema.cr: -------------------------------------------------------------------------------- 1 | require "graphql-crystal" 2 | 3 | module Kemal::GraphQL 4 | HELLO_WORLD_SCHEMA = ::GraphQL::Schema.from_schema( 5 | %{ 6 | schema { 7 | query: RootQuery 8 | } 9 | 10 | type RootQuery { 11 | hello: String 12 | } 13 | } 14 | ).resolve do 15 | query "hello" { "world" } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/kemal-graphql.cr: -------------------------------------------------------------------------------- 1 | require "kemal" 2 | require "json" 3 | require "./schema" 4 | require "graphql-crystal" 5 | require "./hello_world_schema" 6 | 7 | module Kemal::GraphQL 8 | 9 | class AppContext < ::GraphQL::Schema::Context 10 | 11 | def initialize(@username : String, *rest) 12 | super(*rest) 13 | end 14 | 15 | def username 16 | @username 17 | end 18 | 19 | end 20 | 21 | private def self.extract_graphql_payload(type : Symbol, env) 22 | case type 23 | when :query 24 | query_string = env.params.query["query"] 25 | query_params = env.params.query.has_key?("variables") ? 26 | JSON.parse(env.params.query["variables"]).as_h : nil 27 | when :json 28 | payload = env.params.json 29 | query_string = payload["query"].as(String) 30 | query_params = payload["variables"]?.as Hash(String, JSON::Any)? 31 | end 32 | context = AppContext.new( 33 | env.request.headers["USERNAME"]? || "anonymous", Kemal::GraphQL::SCHEMA, 10 34 | ) 35 | raise "no query provided!" unless query_string 36 | { query_string, query_params, nil, context } 37 | end 38 | 39 | # 40 | # read query and variables from the request uri 41 | # 42 | get "/api_query" do |env| 43 | env.response.content_type = "application/json" 44 | Kemal::GraphQL::SCHEMA.execute( 45 | *extract_graphql_payload(:query, env) 46 | ).to_json 47 | end 48 | 49 | # 50 | # read query and variable from the json request body 51 | # 52 | post "/graphql" do |env| 53 | env.response.content_type = "application/json" 54 | Kemal::GraphQL::SCHEMA.execute( 55 | *extract_graphql_payload(:json, env) 56 | ).to_json 57 | end 58 | 59 | Kemal.config do 60 | host_binding = "0.0.0.0" 61 | end 62 | 63 | Kemal.run 64 | end 65 | -------------------------------------------------------------------------------- /src/schema.cr: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require "graphql-crystal" 3 | require "uuid" 4 | 5 | module Kemal::GraphQL 6 | 7 | enum UserRole 8 | Author 9 | Reader 10 | Admin 11 | end 12 | 13 | # 14 | # Lets create a simple Blog Scenario where there exist Users, Posts and Comments 15 | # 16 | # First we define 4 classes to represent our Model: User, Content, Post < Content & Comment < Content 17 | # 18 | 19 | class User 20 | getter :id, :first_name, :last_name, :role 21 | def initialize(@id : String, @first_name : String, @last_name : String, @role : UserRole); end 22 | end 23 | 24 | abstract class Content 25 | # 26 | # due to https://github.com/crystal-lang/crystal/issues/4580 27 | # we have to include the ObjectType module at the first definition of Content 28 | # in order for the field macro to work on child classes. Once this is fixed the 29 | # arbitrary classes can declared as GraphQL Object types easily via monkey Patching 30 | include ::GraphQL::ObjectType 31 | @id: String 32 | @body: String 33 | @author: User 34 | def initialize(@id, @body, @author); end 35 | end 36 | 37 | class Post < Content 38 | getter :id, :author, :title, :body 39 | def initialize(@id : String, @author : User, @title : String, @body : String); end 40 | end 41 | 42 | class Comment < Content 43 | getter :id, :author, :post, :body 44 | def initialize(@id : String, @author : User, @post : Post, @body : String); end 45 | end 46 | 47 | # 48 | # and create some fixtures to work with 49 | # 50 | USERS = [ 51 | {id: UUID.random.to_s, first_name: "Bob", last_name: "Bobson", role: UserRole::Author}, 52 | {id: UUID.random.to_s, first_name: "Alice", last_name: "Alicen", role: UserRole::Admin}, 53 | {id: UUID.random.to_s, first_name: "Grace", last_name: "Graham", role: UserRole::Reader} 54 | ].map { |args| User.new **args } 55 | 56 | POSTS = [ 57 | {id: UUID.random.to_s, author: USERS[0], title: "GraphQL for Dummies", body: "GraphQL is pretty simple."}, 58 | {id: UUID.random.to_s, author: USERS[0], title: "REST vs. GraphQL", body: "GraphQL has certain advantages over REST."}, 59 | {id: UUID.random.to_s, author: USERS[1], title: "The Crystal Programming Language ", body: "The nicest syntax on the planet now comes with typesafety, performance and parallelisation support(ójala!)"} 60 | ].map { |args| Post.new **args } 61 | 62 | COMMENTS = [ 63 | {id: UUID.random.to_s, author: USERS[2], post: POSTS[1], body: "I like rest more!"}, 64 | {id: UUID.random.to_s, author: USERS[2], post: POSTS[1], body: "But think of all the possibilities with GraphQL!"}, 65 | {id: UUID.random.to_s, author: USERS[1], post: POSTS[2], body: "When will I finally have static compilation support?"} 66 | ].map { |args| Comment.new **args } 67 | 68 | 69 | # 70 | # Now we define our Schema 71 | # 72 | 73 | SCHEMA_STRING = <<-graphql_schema 74 | schema { 75 | query: QueryType, 76 | mutation: MutationType 77 | } 78 | 79 | type QueryType { 80 | # retrieve a user by id 81 | user(id: ID!): User 82 | # retrieve a post by id 83 | post(id: ID!): Post 84 | # get all posts 85 | posts: [Post!] 86 | } 87 | 88 | type MutationType { 89 | # create a new post 90 | post(payload: PostInput!): Post 91 | # create a new comment 92 | comment(payload: CommentInput!): Comment 93 | } 94 | 95 | # Input format for 96 | # new Posts 97 | input PostInput { 98 | # title for the new post 99 | title: String! 100 | # body for the new post 101 | body: String! 102 | # id of the posts author 103 | authorId: ID! 104 | } 105 | 106 | # Input format for 107 | # new Comments 108 | input CommentInput { 109 | # id of the post on 110 | # which is being commented 111 | postId: ID! 112 | # id of the comments author 113 | authorId: ID! 114 | # the comments text 115 | body: String! 116 | } 117 | 118 | # Possible roles 119 | # for users in the system 120 | enum UserRole { 121 | # A user with 122 | # readonly access to 123 | # the Content of the system 124 | Reader 125 | # A user with read 126 | # & write access 127 | Author @deprecated 128 | # A administrator 129 | # of the system 130 | Admin 131 | } 132 | 133 | # Types identified by a 134 | # unique ID 135 | interface UniqueId { 136 | # the unique idenfifier 137 | # for this entity 138 | id: ID! 139 | } 140 | 141 | # A User 142 | type User implements UniqueId { 143 | # users first name 144 | firstName: String! 145 | # users last name 146 | lastName: String! 147 | # full name string for the user 148 | fullName: String! @deprecated(reason: "no need to construct this serverside..") 149 | # users role 150 | role: UserRole! 151 | # posts published 152 | # by this user 153 | posts: [Post!] 154 | # total number of posts 155 | # published by this user 156 | postsCount: Int! 157 | } 158 | 159 | # Text content 160 | interface Content { 161 | # text body of this entity 162 | body: String! 163 | # author of this entity 164 | author: User! 165 | } 166 | 167 | # A post in the system 168 | type Post implements UniqueId, Content { 169 | # title of this post 170 | title: String! 171 | } 172 | 173 | # A comment on a post 174 | type Comment implements UniqueId, Content { 175 | # post on which this 176 | # comment was made 177 | post: Post! 178 | } 179 | graphql_schema 180 | 181 | # load it 182 | SCHEMA = ::GraphQL::Schema.from_schema(SCHEMA_STRING) 183 | 184 | module UniqueId 185 | macro included 186 | field :id 187 | end 188 | end 189 | 190 | # Here we reopen the classes 191 | # of our application model and 192 | # enhance them to act as GraphQL Object Types via the GraphQL::ObjectType 193 | # and define the available fields via the field macro. fields resolve to an 194 | # instanc emethod of the same name unless stated otherwise 195 | abstract class Content 196 | # this doesn't work here due to https://github.com/crystal-lang/crystal/issues/4580 197 | # so we included the module at the first declaration of the Content class above 198 | # include GraphQL::ObjectType 199 | field :body 200 | field :author 201 | include UniqueId 202 | end 203 | 204 | # you see it works nicely with inheritance 205 | class Post 206 | field :title 207 | end 208 | 209 | class Comment 210 | field :post 211 | end 212 | 213 | # 214 | # Here we make use of custom callbacks 215 | # to convert snake_case to camelCase 216 | # and add virtual accessors 217 | # 218 | class User 219 | include ::GraphQL::ObjectType 220 | include UniqueId 221 | field :firstName { first_name } 222 | field :lastName { last_name } 223 | field :fullName { "#{@first_name} #{@last_name}" } 224 | field :posts { POSTS.select &.author.==(self)} 225 | field :postsCount { POSTS.select( &.author.==(self) ).size } 226 | field :role 227 | end 228 | 229 | 230 | module QueryType 231 | include ::GraphQL::ObjectType 232 | extend self 233 | 234 | field :posts { POSTS } 235 | 236 | field :user do |args| 237 | USERS.find( &.id.==(args["id"]) ) 238 | end 239 | 240 | field :post do |args| 241 | POSTS.find( &.id.==(args["id"]) ) 242 | end 243 | 244 | end 245 | 246 | module MutationType 247 | include ::GraphQL::ObjectType 248 | extend self 249 | 250 | field :post do |args| 251 | payload = args["payload"].as(Hash) 252 | 253 | author = USERS.find( &.id.==(payload["authorId"]) ) 254 | raise "authorId doesn't exist!" unless author 255 | 256 | post = Post.new( 257 | id: UUID.random.to_s, author: author, 258 | title: payload["title"].as(String), body: payload["body"].as(String) 259 | ) 260 | 261 | POSTS << post 262 | post 263 | end 264 | 265 | field :comment do |args| 266 | 267 | # if username == "tom" 268 | # raise "tom, you are not allowed to post!" 269 | # end 270 | 271 | payload = args["payload"].as(Hash) 272 | 273 | author = USERS.find( &.id.==(payload["authorId"]) ) 274 | raise "authorId doesn't exist!" unless author 275 | 276 | post = POSTS.find( &.id.==(payload["postId"]) ) 277 | raise "postId doesn't exist!" unless post 278 | 279 | comment = Comment.new( 280 | id: UUID.random.to_s, author: author, 281 | post: post, body: payload["body"].as(String) 282 | ) 283 | COMMENTS << comment 284 | comment 285 | end 286 | 287 | end 288 | 289 | # 290 | # finally we define the top level resolve callbacks 291 | # for the root Queries fields and the root mutation fields 292 | # 293 | SCHEMA.query_resolver = QueryType 294 | SCHEMA.mutation_resolver = MutationType 295 | end 296 | --------------------------------------------------------------------------------