├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples └── books │ ├── README.md │ └── main.rs └── src ├── lib.rs ├── query.rs ├── schema.rs ├── server.rs └── types ├── list.rs ├── mod.rs ├── resource.rs └── scalars.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | unpublished/ 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.13" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "async-trait" 14 | version = "0.1.30" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "da71fef07bc806586090247e971229289f64c210a278ee5ae419314eb386b31d" 17 | dependencies = [ 18 | "proc-macro2", 19 | "quote", 20 | "syn", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi 0.3.8", 32 | ] 33 | 34 | [[package]] 35 | name = "autocfg" 36 | version = "1.0.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.2.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 45 | 46 | [[package]] 47 | name = "bytes" 48 | version = "0.5.4" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 51 | 52 | [[package]] 53 | name = "cfg-if" 54 | version = "0.1.10" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 57 | 58 | [[package]] 59 | name = "cloudabi" 60 | version = "0.0.3" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 63 | dependencies = [ 64 | "bitflags", 65 | ] 66 | 67 | [[package]] 68 | name = "dtoa" 69 | version = "0.4.5" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" 72 | 73 | [[package]] 74 | name = "env_logger" 75 | version = "0.7.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 78 | dependencies = [ 79 | "atty", 80 | "humantime", 81 | "log", 82 | "regex", 83 | "termcolor", 84 | ] 85 | 86 | [[package]] 87 | name = "fnv" 88 | version = "1.0.6" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 91 | 92 | [[package]] 93 | name = "fuchsia-zircon" 94 | version = "0.3.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 97 | dependencies = [ 98 | "bitflags", 99 | "fuchsia-zircon-sys", 100 | ] 101 | 102 | [[package]] 103 | name = "fuchsia-zircon-sys" 104 | version = "0.3.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 107 | 108 | [[package]] 109 | name = "futures" 110 | version = "0.3.4" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" 113 | dependencies = [ 114 | "futures-channel", 115 | "futures-core", 116 | "futures-executor", 117 | "futures-io", 118 | "futures-sink", 119 | "futures-task", 120 | "futures-util", 121 | ] 122 | 123 | [[package]] 124 | name = "futures-channel" 125 | version = "0.3.4" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" 128 | dependencies = [ 129 | "futures-core", 130 | "futures-sink", 131 | ] 132 | 133 | [[package]] 134 | name = "futures-core" 135 | version = "0.3.4" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" 138 | 139 | [[package]] 140 | name = "futures-executor" 141 | version = "0.3.4" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" 144 | dependencies = [ 145 | "futures-core", 146 | "futures-task", 147 | "futures-util", 148 | ] 149 | 150 | [[package]] 151 | name = "futures-io" 152 | version = "0.3.4" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" 155 | 156 | [[package]] 157 | name = "futures-macro" 158 | version = "0.3.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" 161 | dependencies = [ 162 | "proc-macro-hack", 163 | "proc-macro2", 164 | "quote", 165 | "syn", 166 | ] 167 | 168 | [[package]] 169 | name = "futures-sink" 170 | version = "0.3.4" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" 173 | 174 | [[package]] 175 | name = "futures-task" 176 | version = "0.3.4" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" 179 | 180 | [[package]] 181 | name = "futures-util" 182 | version = "0.3.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" 185 | dependencies = [ 186 | "futures-channel", 187 | "futures-core", 188 | "futures-io", 189 | "futures-macro", 190 | "futures-sink", 191 | "futures-task", 192 | "memchr", 193 | "pin-utils", 194 | "proc-macro-hack", 195 | "proc-macro-nested", 196 | "slab", 197 | ] 198 | 199 | [[package]] 200 | name = "h2" 201 | version = "0.2.4" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42" 204 | dependencies = [ 205 | "bytes", 206 | "fnv", 207 | "futures-core", 208 | "futures-sink", 209 | "futures-util", 210 | "http", 211 | "indexmap", 212 | "log", 213 | "slab", 214 | "tokio", 215 | "tokio-util", 216 | ] 217 | 218 | [[package]] 219 | name = "h2" 220 | version = "0.2.5" 221 | source = "git+https://github.com/polc/h2#b01259de194485390fe416911c018c184f28b3a6" 222 | dependencies = [ 223 | "bytes", 224 | "fnv", 225 | "futures-core", 226 | "futures-sink", 227 | "futures-util", 228 | "http", 229 | "indexmap", 230 | "log", 231 | "slab", 232 | "tokio", 233 | "tokio-util", 234 | ] 235 | 236 | [[package]] 237 | name = "hermit-abi" 238 | version = "0.1.14" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" 241 | dependencies = [ 242 | "libc", 243 | ] 244 | 245 | [[package]] 246 | name = "http" 247 | version = "0.2.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" 250 | dependencies = [ 251 | "bytes", 252 | "fnv", 253 | "itoa", 254 | ] 255 | 256 | [[package]] 257 | name = "http-body" 258 | version = "0.3.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 261 | dependencies = [ 262 | "bytes", 263 | "http", 264 | ] 265 | 266 | [[package]] 267 | name = "httparse" 268 | version = "1.3.4" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 271 | 272 | [[package]] 273 | name = "humantime" 274 | version = "1.3.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 277 | dependencies = [ 278 | "quick-error", 279 | ] 280 | 281 | [[package]] 282 | name = "hyper" 283 | version = "0.13.5" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" 286 | dependencies = [ 287 | "bytes", 288 | "futures-channel", 289 | "futures-core", 290 | "futures-util", 291 | "h2 0.2.4", 292 | "http", 293 | "http-body", 294 | "httparse", 295 | "itoa", 296 | "log", 297 | "net2", 298 | "pin-project", 299 | "time", 300 | "tokio", 301 | "tower-service", 302 | "want", 303 | ] 304 | 305 | [[package]] 306 | name = "indexmap" 307 | version = "1.3.2" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 310 | dependencies = [ 311 | "autocfg", 312 | "serde", 313 | ] 314 | 315 | [[package]] 316 | name = "iovec" 317 | version = "0.1.4" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 320 | dependencies = [ 321 | "libc", 322 | ] 323 | 324 | [[package]] 325 | name = "itoa" 326 | version = "0.4.5" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 329 | 330 | [[package]] 331 | name = "kernel32-sys" 332 | version = "0.2.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 335 | dependencies = [ 336 | "winapi 0.2.8", 337 | "winapi-build", 338 | ] 339 | 340 | [[package]] 341 | name = "lazy_static" 342 | version = "1.4.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 345 | 346 | [[package]] 347 | name = "libc" 348 | version = "0.2.69" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 351 | 352 | [[package]] 353 | name = "linked-hash-map" 354 | version = "0.5.2" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" 357 | 358 | [[package]] 359 | name = "lock_api" 360 | version = "0.3.4" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 363 | dependencies = [ 364 | "scopeguard", 365 | ] 366 | 367 | [[package]] 368 | name = "log" 369 | version = "0.4.8" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 372 | dependencies = [ 373 | "cfg-if", 374 | ] 375 | 376 | [[package]] 377 | name = "memchr" 378 | version = "2.3.3" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 381 | 382 | [[package]] 383 | name = "mio" 384 | version = "0.6.21" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" 387 | dependencies = [ 388 | "cfg-if", 389 | "fuchsia-zircon", 390 | "fuchsia-zircon-sys", 391 | "iovec", 392 | "kernel32-sys", 393 | "libc", 394 | "log", 395 | "miow", 396 | "net2", 397 | "slab", 398 | "winapi 0.2.8", 399 | ] 400 | 401 | [[package]] 402 | name = "miow" 403 | version = "0.2.1" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 406 | dependencies = [ 407 | "kernel32-sys", 408 | "net2", 409 | "winapi 0.2.8", 410 | "ws2_32-sys", 411 | ] 412 | 413 | [[package]] 414 | name = "net2" 415 | version = "0.2.33" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 418 | dependencies = [ 419 | "cfg-if", 420 | "libc", 421 | "winapi 0.3.8", 422 | ] 423 | 424 | [[package]] 425 | name = "openapiv3" 426 | version = "0.3.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "c8776c7d6a58a03d30ba278adfd54d923eb0a24e26cbf39d2b97648306935d65" 429 | dependencies = [ 430 | "indexmap", 431 | "serde", 432 | "serde_json", 433 | "serde_yaml", 434 | ] 435 | 436 | [[package]] 437 | name = "parking_lot" 438 | version = "0.10.2" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" 441 | dependencies = [ 442 | "lock_api", 443 | "parking_lot_core", 444 | ] 445 | 446 | [[package]] 447 | name = "parking_lot_core" 448 | version = "0.7.2" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" 451 | dependencies = [ 452 | "cfg-if", 453 | "cloudabi", 454 | "libc", 455 | "redox_syscall", 456 | "smallvec", 457 | "winapi 0.3.8", 458 | ] 459 | 460 | [[package]] 461 | name = "pin-project" 462 | version = "0.4.9" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "6f6a7f5eee6292c559c793430c55c00aea9d3b3d1905e855806ca4d7253426a2" 465 | dependencies = [ 466 | "pin-project-internal", 467 | ] 468 | 469 | [[package]] 470 | name = "pin-project-internal" 471 | version = "0.4.9" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "8988430ce790d8682672117bc06dda364c0be32d3abd738234f19f3240bad99a" 474 | dependencies = [ 475 | "proc-macro2", 476 | "quote", 477 | "syn", 478 | ] 479 | 480 | [[package]] 481 | name = "pin-project-lite" 482 | version = "0.1.4" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" 485 | 486 | [[package]] 487 | name = "pin-utils" 488 | version = "0.1.0-alpha.4" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" 491 | 492 | [[package]] 493 | name = "proc-macro-hack" 494 | version = "0.5.15" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" 497 | 498 | [[package]] 499 | name = "proc-macro-nested" 500 | version = "0.1.4" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" 503 | 504 | [[package]] 505 | name = "proc-macro2" 506 | version = "1.0.10" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" 509 | dependencies = [ 510 | "unicode-xid", 511 | ] 512 | 513 | [[package]] 514 | name = "quick-error" 515 | version = "1.2.3" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 518 | 519 | [[package]] 520 | name = "quote" 521 | version = "1.0.3" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 524 | dependencies = [ 525 | "proc-macro2", 526 | ] 527 | 528 | [[package]] 529 | name = "redox_syscall" 530 | version = "0.1.56" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 533 | 534 | [[package]] 535 | name = "regex" 536 | version = "1.3.9" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 539 | dependencies = [ 540 | "aho-corasick", 541 | "memchr", 542 | "regex-syntax", 543 | "thread_local", 544 | ] 545 | 546 | [[package]] 547 | name = "regex-syntax" 548 | version = "0.6.18" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 551 | 552 | [[package]] 553 | name = "rest-rs" 554 | version = "0.1.0" 555 | dependencies = [ 556 | "async-trait", 557 | "env_logger", 558 | "fnv", 559 | "futures", 560 | "h2 0.2.5", 561 | "http", 562 | "hyper", 563 | "indexmap", 564 | "openapiv3", 565 | "parking_lot", 566 | "route-recognizer", 567 | "serde", 568 | "serde_json", 569 | "tokio", 570 | ] 571 | 572 | [[package]] 573 | name = "route-recognizer" 574 | version = "0.1.13" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "ea509065eb0b3c446acdd0102f0d46567dc30902dc0be91d6552035d92b0f4f8" 577 | 578 | [[package]] 579 | name = "ryu" 580 | version = "1.0.3" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" 583 | 584 | [[package]] 585 | name = "scopeguard" 586 | version = "1.1.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 589 | 590 | [[package]] 591 | name = "serde" 592 | version = "1.0.106" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" 595 | dependencies = [ 596 | "serde_derive", 597 | ] 598 | 599 | [[package]] 600 | name = "serde_derive" 601 | version = "1.0.106" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" 604 | dependencies = [ 605 | "proc-macro2", 606 | "quote", 607 | "syn", 608 | ] 609 | 610 | [[package]] 611 | name = "serde_json" 612 | version = "1.0.51" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" 615 | dependencies = [ 616 | "itoa", 617 | "ryu", 618 | "serde", 619 | ] 620 | 621 | [[package]] 622 | name = "serde_yaml" 623 | version = "0.8.11" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" 626 | dependencies = [ 627 | "dtoa", 628 | "linked-hash-map", 629 | "serde", 630 | "yaml-rust", 631 | ] 632 | 633 | [[package]] 634 | name = "slab" 635 | version = "0.4.2" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 638 | 639 | [[package]] 640 | name = "smallvec" 641 | version = "1.4.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" 644 | 645 | [[package]] 646 | name = "syn" 647 | version = "1.0.17" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" 650 | dependencies = [ 651 | "proc-macro2", 652 | "quote", 653 | "unicode-xid", 654 | ] 655 | 656 | [[package]] 657 | name = "termcolor" 658 | version = "1.1.0" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 661 | dependencies = [ 662 | "winapi-util", 663 | ] 664 | 665 | [[package]] 666 | name = "thread_local" 667 | version = "1.0.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 670 | dependencies = [ 671 | "lazy_static", 672 | ] 673 | 674 | [[package]] 675 | name = "time" 676 | version = "0.1.42" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 679 | dependencies = [ 680 | "libc", 681 | "redox_syscall", 682 | "winapi 0.3.8", 683 | ] 684 | 685 | [[package]] 686 | name = "tokio" 687 | version = "0.2.18" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713" 690 | dependencies = [ 691 | "bytes", 692 | "fnv", 693 | "futures-core", 694 | "iovec", 695 | "lazy_static", 696 | "memchr", 697 | "mio", 698 | "pin-project-lite", 699 | "slab", 700 | "tokio-macros", 701 | ] 702 | 703 | [[package]] 704 | name = "tokio-macros" 705 | version = "0.2.5" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" 708 | dependencies = [ 709 | "proc-macro2", 710 | "quote", 711 | "syn", 712 | ] 713 | 714 | [[package]] 715 | name = "tokio-util" 716 | version = "0.3.1" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" 719 | dependencies = [ 720 | "bytes", 721 | "futures-core", 722 | "futures-sink", 723 | "log", 724 | "pin-project-lite", 725 | "tokio", 726 | ] 727 | 728 | [[package]] 729 | name = "tower-service" 730 | version = "0.3.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 733 | 734 | [[package]] 735 | name = "try-lock" 736 | version = "0.2.2" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 739 | 740 | [[package]] 741 | name = "unicode-xid" 742 | version = "0.2.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 745 | 746 | [[package]] 747 | name = "want" 748 | version = "0.3.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 751 | dependencies = [ 752 | "log", 753 | "try-lock", 754 | ] 755 | 756 | [[package]] 757 | name = "winapi" 758 | version = "0.2.8" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 761 | 762 | [[package]] 763 | name = "winapi" 764 | version = "0.3.8" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 767 | dependencies = [ 768 | "winapi-i686-pc-windows-gnu", 769 | "winapi-x86_64-pc-windows-gnu", 770 | ] 771 | 772 | [[package]] 773 | name = "winapi-build" 774 | version = "0.1.1" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 777 | 778 | [[package]] 779 | name = "winapi-i686-pc-windows-gnu" 780 | version = "0.4.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 783 | 784 | [[package]] 785 | name = "winapi-util" 786 | version = "0.1.5" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 789 | dependencies = [ 790 | "winapi 0.3.8", 791 | ] 792 | 793 | [[package]] 794 | name = "winapi-x86_64-pc-windows-gnu" 795 | version = "0.4.0" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 798 | 799 | [[package]] 800 | name = "ws2_32-sys" 801 | version = "0.2.1" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 804 | dependencies = [ 805 | "winapi 0.2.8", 806 | "winapi-build", 807 | ] 808 | 809 | [[package]] 810 | name = "yaml-rust" 811 | version = "0.4.3" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" 814 | dependencies = [ 815 | "linked-hash-map", 816 | ] 817 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rest-rs" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2018" 6 | 7 | [dependencies] 8 | hyper = "0.13" 9 | tokio = { version = "0.2", features = ["macros", "stream", "dns"] } 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | futures = "0.3" 13 | async-trait = "0.1.29" 14 | http = "0.2" 15 | parking_lot = "0.10.2" 16 | openapiv3 = "0.3.0" 17 | indexmap = "1.3.2" 18 | fnv = "1.0.3" 19 | route-recognizer = "0.1.13" 20 | h2 = { git = "https://github.com/polc/h2" } 21 | env_logger = "0.7.1" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rest-rs (wip) 2 | 3 | A web framework to create client-driven REST APIs. 4 | 5 | ## Features 6 | 7 | * Allow defining resources as a graph like a GraphQL server 8 | * Allow querying a subset of the resource graph (for now by building a `query::NodeSelection` by hand) 9 | 10 | ## Pitch 11 | 12 | `rest-rs` is for people who want to build standard and correct RESTful APIs. 13 | You will likely not get the best raw performances (reqs/s), but it will feel faster to clients taking full advantage of Vulcain. 14 | 15 | `rest-rs` is best suited for use-case where GraphQL was considered : Dashboards, SPAs, and any clients consuming large graph of resources. 16 | 17 | `rest-rs` give developers the freedom of designing their APIs as they want, even in the smallest details : 18 | * Resources of any shapes : object, list, string, number, ... 19 | * URLs of any shapes (`/author-books/1`, `/authors/1/books/1`, ...) 20 | 21 | ## What it will look like 22 | 23 | Here is how I would like the final code to look like. If you prefer a working example in the current state of the code, checkout [examples/books](./examples/books). 24 | 25 | First we define a `Root` resource which will be the home page (`/`) and just contain a link to the authors (`/authors`). 26 | 27 | Notice the `async fn authors(&self) -> Link>` function, which allow to fetch all authors in case a `Preload: /authors` header is present. 28 | 29 | ```rust 30 | // 31 | // Defining the Root resource 32 | // 33 | 34 | #[derive(Debug, Clone, Copy)] 35 | struct Root {} 36 | 37 | rest_rs::route!(struct RootRoute = "/"); 38 | 39 | #[async_trait::async_trait] 40 | impl Resource for Root { 41 | type Route = RootRoute; 42 | 43 | fn route(&self) -> Self::Route { 44 | RootRoute {} 45 | } 46 | 47 | async fn fetch(route: Self::Route) -> Option { 48 | Some(Self {}) 49 | } 50 | } 51 | 52 | #[rest_rs::resource] 53 | impl Root { 54 | async fn authors(&self) -> Link> { 55 | let authors = ... // fetch all authors 56 | 57 | Link(authors) 58 | } 59 | } 60 | ``` 61 | 62 | Then we define a `Collection` (`/authors`) and a `Author` (`/authors/some-id`) resources. 63 | 64 | ```rust 65 | #[derive(Debug, Clone, Copy)] 66 | struct Author { 67 | id: u32, 68 | name: String, 69 | } 70 | 71 | // 72 | // Defining the AuthorCollection resource 73 | // 74 | 75 | rest_rs::route!(struct AuthorCollectionRoute = "/authors"); 76 | 77 | #[async_trait::async_trait] 78 | impl Resource for Collection { 79 | type Route = AuthorCollectionRoute; 80 | 81 | fn route(&self) -> Self::Route { 82 | AuthorCollectionRoute {} 83 | } 84 | 85 | async fn fetch(route: Self::Route) -> Option { 86 | let authors = ... // fetch all authors 87 | 88 | Some(authors) 89 | } 90 | } 91 | 92 | // 93 | // Defining the Author resource 94 | // 95 | 96 | rest_rs::route!(struct AuthorRoute = "/author/:id"); 97 | 98 | #[async_trait::async_trait] 99 | impl Resource for Author { 100 | type Route = AuthorRoute; 101 | 102 | fn route(&self) -> Self::Route { 103 | AuthorRoute { 104 | id: self.id.into(), 105 | } 106 | } 107 | 108 | async fn fetch(route: Self::Route) -> Option { 109 | let author_id = route.id; 110 | let author = ... // fetch author by id 111 | 112 | Some(author) 113 | } 114 | } 115 | 116 | #[rest_rs::resource] 117 | impl Author { 118 | async fn name(&self) -> String { 119 | self.name 120 | } 121 | 122 | // not called if filtered by a Fields header 123 | async fn expensive_field(&self) -> u32 { 124 | 42 125 | } 126 | } 127 | ``` 128 | 129 | Finally we create a schema from the `Root` resource, and start a new server. 130 | 131 | ```rust 132 | #[tokio::main] 133 | async fn main() { 134 | let schema = Arc::new(Schema::new::()); 135 | let server = Server { schema }; 136 | 137 | server.run("127.0.0.1:8080").await; 138 | } 139 | ``` 140 | 141 | ## Next 142 | 143 | #### Planned features 144 | 145 | * Create a `query::NodeSelection` from a HTTP request (by reading Vulcain headers) 146 | * Rust macros (a lot of code in `examples/books/` will be auto-generated) 147 | * Content negotiation with JSON-LD and OpenAPI support 148 | * Write operations support : 149 | * `PATCH` method with `application/merge-patch+json` or maybe `application/json-patch+json` 150 | * `DELETE` method 151 | 152 | #### Potential features (long-term) 153 | 154 | * Support for Hydra or Siren or https://level3.rest/ 155 | * Native [Mercure](https://github.com/dunglas/mercure) support 156 | * Proxy automatically to legacy APIs by reading OpenAPI schema or GraphQL schema 157 | -------------------------------------------------------------------------------- /examples/books/README.md: -------------------------------------------------------------------------------- 1 | # Books example 2 | 3 | ## Usage 4 | 5 | For now, I use [h2](https://github.com/hyperium/h2), so `rest-rs` doesn't support HTTP 1.0 upgrades. 6 | We need to use an HTTP/2 only client such as `curl` with the `--http2-prior-knowledge` flag. 7 | 8 | ```bash 9 | # Start server 10 | cargo run --example books 11 | 12 | # Send request 13 | curl --http2-prior-knowledge --trace-ascii - http://localhost:8080/books/book-123 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/books/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rest_rs; 2 | 3 | use rest_rs::{ 4 | query::NodeSelection, 5 | schema::{Schema, TypeMetadata}, 6 | server::Server, 7 | types::{ 8 | resource::{Link, Resource}, 9 | ObjectOutputType, OutputType, ResolvedNode, Type, 10 | }, 11 | }; 12 | 13 | use futures::core_reexport::convert::TryFrom; 14 | use rest_rs::types::resource::Route; 15 | use route_recognizer::Params; 16 | use std::borrow::Cow; 17 | use std::sync::Arc; 18 | 19 | #[derive(Debug, Clone, Copy)] 20 | pub struct Root {} 21 | 22 | impl<'a> Root { 23 | async fn field_str(&self) -> String { 24 | "I'm Root !".to_string() 25 | } 26 | 27 | async fn field_link(&self) -> Link { 28 | Link(Book {}) 29 | } 30 | } 31 | 32 | impl Type for Root { 33 | fn type_name() -> Cow<'static, str> { 34 | Cow::Borrowed("Root") 35 | } 36 | 37 | fn type_metadata(schema: &mut Schema) -> TypeMetadata { 38 | let fields = &[ 39 | TypeMetadata::new_field::<&str>(schema, "field_str"), 40 | TypeMetadata::new_field::>(schema, "field_link"), 41 | ]; 42 | 43 | TypeMetadata::new_object::(fields) 44 | } 45 | } 46 | 47 | #[async_trait::async_trait] 48 | impl ObjectOutputType for Root { 49 | async fn resolve_field(&self, selection: &NodeSelection) -> (&'static str, ResolvedNode) { 50 | let resolved_node = match selection.name { 51 | "field_str" => self.field_str().await.resolve(&selection).await, 52 | "field_link" => self.field_link().await.resolve(&selection).await, 53 | _ => panic!("Field {} not found on type Root", selection.name), 54 | }; 55 | 56 | (selection.name, resolved_node) 57 | } 58 | } 59 | 60 | pub struct RootRoute {} 61 | 62 | impl TryFrom for RootRoute { 63 | type Error = (); 64 | 65 | fn try_from(value: Params) -> Result { 66 | match value.iter().count() { 67 | 0 => Ok(RootRoute {}), 68 | _ => Err(()), 69 | } 70 | } 71 | } 72 | 73 | impl Route for RootRoute { 74 | fn iri(&self) -> String { 75 | "/".into() 76 | } 77 | 78 | fn path_pattern() -> &'static str { 79 | "/" 80 | } 81 | } 82 | 83 | #[async_trait::async_trait] 84 | impl Resource for Root { 85 | type Route = RootRoute; 86 | 87 | fn route(&self) -> Self::Route { 88 | RootRoute {} 89 | } 90 | 91 | async fn fetch(route: Self::Route) -> Option { 92 | Some(Self {}) 93 | } 94 | } 95 | 96 | #[derive(Debug, Clone, Copy)] 97 | pub struct Book {} 98 | 99 | impl Book { 100 | async fn field_str(&self) -> String { 101 | "I'm a book !".to_string() 102 | } 103 | 104 | async fn field_recursive(&self) -> Link { 105 | Link(Book {}) 106 | } 107 | } 108 | 109 | impl Type for Book { 110 | fn type_name() -> Cow<'static, str> { 111 | Cow::Borrowed("Book") 112 | } 113 | 114 | fn type_metadata(schema: &mut Schema) -> TypeMetadata { 115 | let fields = &[ 116 | TypeMetadata::new_field::<&str>(schema, "field_str"), 117 | // TypeMetadata::new_field::>(schema, "field_recursive"), 118 | ]; 119 | 120 | TypeMetadata::new_object::(fields) 121 | } 122 | } 123 | 124 | #[async_trait::async_trait] 125 | impl ObjectOutputType for Book { 126 | async fn resolve_field(&self, selection: &NodeSelection) -> (&'static str, ResolvedNode) { 127 | let resolved_node = match selection.name { 128 | "field_str" => self.field_str().await.resolve(&selection).await, 129 | "field_recursive" => self.field_recursive().await.resolve(&selection).await, 130 | _ => panic!("Field {} not found on type Book", selection.name), 131 | }; 132 | 133 | (selection.name, resolved_node) 134 | } 135 | } 136 | 137 | pub struct BookRoute { 138 | id: String, 139 | } 140 | 141 | impl<'a> TryFrom for BookRoute { 142 | type Error = (); 143 | 144 | fn try_from(value: Params) -> Result { 145 | match value.find("id") { 146 | Some(id) => Ok(BookRoute { id: id.into() }), 147 | _ => Err(()), 148 | } 149 | } 150 | } 151 | 152 | impl<'a> Route for BookRoute { 153 | fn iri(&self) -> String { 154 | format!("/books/{}", self.id) 155 | } 156 | 157 | fn path_pattern() -> &'static str { 158 | "/books/:id" 159 | } 160 | } 161 | 162 | #[async_trait::async_trait] 163 | impl Resource for Book { 164 | type Route = BookRoute; 165 | 166 | fn route(&self) -> Self::Route { 167 | BookRoute { 168 | id: "book-123".into(), 169 | } 170 | } 171 | 172 | async fn fetch(route: Self::Route) -> Option { 173 | if route.id.eq("book-123") { 174 | Some(Self {}) 175 | } else { 176 | None 177 | } 178 | } 179 | } 180 | 181 | #[tokio::main] 182 | async fn main() { 183 | let schema = Arc::new(Schema::new::()); 184 | let server = Server { schema }; 185 | 186 | server.run("127.0.0.1:8080").await; 187 | } 188 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(dead_code)] 3 | 4 | pub mod query; 5 | pub mod schema; 6 | pub mod server; 7 | pub mod types; 8 | -------------------------------------------------------------------------------- /src/query.rs: -------------------------------------------------------------------------------- 1 | use crate::schema::{Schema, TypeMetadata}; 2 | 3 | #[derive(Debug, Default, Clone)] 4 | pub struct NodeSelection { 5 | pub name: &'static str, 6 | pub nodes: Vec, 7 | } 8 | 9 | impl NodeSelection { 10 | pub fn new(name: &'static str, type_metadata: &TypeMetadata, schema: &Schema) -> Self { 11 | NodeSelection { 12 | name, 13 | nodes: match type_metadata.fields(schema) { 14 | Some(fields) => fields 15 | .iter() 16 | .map(|field| { 17 | let field_type_metadata = schema.type_metadata(&field.field_type); 18 | 19 | NodeSelection::new(field.name, field_type_metadata, schema) 20 | }) 21 | .collect(), 22 | None => vec![], 23 | }, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/schema.rs: -------------------------------------------------------------------------------- 1 | use crate::query::NodeSelection; 2 | use crate::types::resource::{Resource, Route}; 3 | use crate::types::{ObjectOutputType, ResolvedNode, Type}; 4 | use futures::future::BoxFuture; 5 | use route_recognizer::{Params, Router}; 6 | use std::collections::HashMap; 7 | use std::convert::TryFrom; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct FieldMetadata { 11 | pub name: &'static str, 12 | pub field_type: String, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum TypeMetadata { 17 | Scalar { 18 | name: String, 19 | }, 20 | Object { 21 | name: String, 22 | fields: Vec, 23 | }, 24 | List { 25 | name: String, 26 | item_type: String, 27 | }, 28 | Resource { 29 | name: String, 30 | item_type: String, 31 | }, 32 | } 33 | 34 | impl TypeMetadata { 35 | pub fn new_field(schema: &mut Schema, name: &'static str) -> FieldMetadata { 36 | FieldMetadata { 37 | name, 38 | field_type: schema.register_type::(), 39 | } 40 | } 41 | 42 | pub fn new_object(fields: &[FieldMetadata]) -> TypeMetadata { 43 | TypeMetadata::Object { 44 | name: T::type_name().to_string(), 45 | fields: fields.to_vec(), 46 | } 47 | } 48 | 49 | pub fn fields<'a>(&'a self, schema: &'a Schema) -> Option<&'a Vec> { 50 | match self { 51 | TypeMetadata::Object { fields, .. } => Some(fields), 52 | TypeMetadata::Resource { item_type, .. } | TypeMetadata::List { item_type, .. } => { 53 | let type_metadata = schema.type_metadata(item_type.as_ref()); 54 | 55 | type_metadata.fields(&schema) 56 | } 57 | TypeMetadata::Scalar { .. } => None, 58 | } 59 | } 60 | } 61 | 62 | pub struct ResourceRoute { 63 | pub name: String, 64 | pub resolver: fn(Params, &NodeSelection) -> BoxFuture<'static, Option>, 65 | } 66 | 67 | pub struct Schema { 68 | types: HashMap, 69 | pub router: Router, 70 | } 71 | 72 | impl Schema { 73 | pub fn new() -> Self { 74 | let mut schema = Schema { 75 | types: Default::default(), 76 | router: Router::new(), 77 | }; 78 | schema.register_type::(); 79 | schema.register_resource::(); 80 | 81 | schema 82 | } 83 | 84 | pub fn type_metadata(&self, name: &str) -> &TypeMetadata { 85 | self.types 86 | .get(name) 87 | .expect(format!("Unable to get type metadata {}.", name).as_ref()) 88 | } 89 | 90 | pub fn register_type(&mut self) -> String { 91 | let name = T::type_name().to_string(); 92 | 93 | if !self.types.contains_key(&name) { 94 | self.types.insert( 95 | name.clone(), 96 | TypeMetadata::Scalar { 97 | name: "placeholder".to_string(), 98 | }, 99 | ); 100 | 101 | let type_metadata = T::type_metadata(self); 102 | self.types.insert(name.clone(), type_metadata); 103 | } 104 | 105 | name 106 | } 107 | 108 | pub fn register_resource(&mut self) { 109 | let route = ResourceRoute { 110 | name: T::type_name().to_string(), 111 | resolver: |params: Params, selection: &NodeSelection| { 112 | let selection_clone = selection.clone(); 113 | 114 | Box::pin(async move { 115 | match T::Route::try_from(params) { 116 | Ok(id) => match T::fetch(id).await { 117 | Some(resource) => Some(resource.resolve(&selection_clone).await), 118 | _ => None, 119 | }, 120 | _ => None, 121 | } 122 | }) 123 | }, 124 | }; 125 | 126 | self.router.add(T::Route::path_pattern(), route); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::query::NodeSelection; 2 | use crate::schema::{ResourceRoute, Schema}; 3 | use crate::types::ResolvedNode; 4 | use futures::future::BoxFuture; 5 | use h2::server; 6 | use h2::server::SendResponse; 7 | use http::{Method, Response, StatusCode}; 8 | use hyper::body::Bytes; 9 | use std::error::Error; 10 | use std::sync::Arc; 11 | use tokio::net::{TcpListener, TcpStream}; 12 | 13 | pub struct Server { 14 | pub schema: Arc, 15 | } 16 | 17 | impl Server { 18 | pub async fn run(&self, addr: &str) { 19 | let mut listener = TcpListener::bind(addr).await.unwrap(); 20 | 21 | loop { 22 | if let Ok((socket, _peer_addr)) = listener.accept().await { 23 | let schema = self.schema.clone(); 24 | 25 | tokio::spawn(async move { 26 | if let Err(error) = handle(socket, schema).await { 27 | println!("{:?}", error); 28 | } 29 | }); 30 | } 31 | } 32 | } 33 | } 34 | 35 | async fn handle(socket: TcpStream, schema: Arc) -> Result<(), Box> { 36 | let mut connection = server::handshake(socket).await?; 37 | 38 | while let Some(result) = connection.accept().await { 39 | let (req, mut stream) = result?; 40 | 41 | match schema.router.recognize(req.uri().path()) { 42 | Ok(route_recognizer) => match req.method() { 43 | &Method::GET => { 44 | let params = route_recognizer.params; 45 | let ResourceRoute { name, resolver } = route_recognizer.handler; 46 | 47 | let type_metadata = schema.type_metadata(name.as_str()); 48 | let selection = NodeSelection::new("root", type_metadata, &schema); 49 | let resolved_node = (resolver)(params, &selection).await; 50 | 51 | if let Some(resolved_node) = resolved_node { 52 | send_root_node(resolved_node, &mut stream).await?; 53 | } else { 54 | let response = Response::builder() 55 | .status(StatusCode::NOT_FOUND) 56 | .body(()) 57 | .unwrap(); 58 | stream.send_response(response, true).unwrap(); 59 | } 60 | } 61 | _ => { 62 | let response = Response::builder() 63 | .status(StatusCode::METHOD_NOT_ALLOWED) 64 | .body(()) 65 | .unwrap(); 66 | stream.send_response(response, true).unwrap(); 67 | } 68 | }, 69 | _ => { 70 | let response = Response::builder() 71 | .status(StatusCode::NOT_FOUND) 72 | .body(()) 73 | .unwrap(); 74 | stream.send_response(response, true).unwrap(); 75 | } 76 | } 77 | } 78 | 79 | Ok(()) 80 | } 81 | 82 | async fn send_root_node( 83 | root: ResolvedNode, 84 | stream: &mut SendResponse, 85 | ) -> Result<(), Box> { 86 | let ResolvedNode(content, children_futures) = root; 87 | let children = futures::future::join_all(children_futures).await; 88 | 89 | for ResolvedNode(child_content, _) in &children { 90 | println!("Push-Promise : {:#}", child_content); 91 | } 92 | 93 | println!("Send Response : {:#}", content); 94 | let res = Response::builder().status(StatusCode::OK).body(()).unwrap(); 95 | let mut send = stream.send_response(res, false)?; 96 | 97 | let content_bytes = Bytes::from(serde_json::to_vec(&content).unwrap()); 98 | send.send_data(content_bytes, true)?; 99 | 100 | let mut futures = Vec::with_capacity(children.len()); 101 | for child in children { 102 | futures.push(send_node(child)); 103 | } 104 | futures::future::join_all(futures).await; 105 | 106 | Ok(()) 107 | } 108 | 109 | pub fn send_node<'a>(node: ResolvedNode) -> BoxFuture<'a, ()> { 110 | Box::pin(async move { 111 | let ResolvedNode(content, children_futures) = node; 112 | let children = futures::future::join_all(children_futures).await; 113 | 114 | for ResolvedNode(child_content, _) in &children { 115 | println!("Push-Promise : {:#}", child_content); 116 | } 117 | 118 | println!("Server Push : {:#}", content); 119 | 120 | let mut futures = Vec::with_capacity(children.len()); 121 | for child in children { 122 | futures.push(send_node(child)); 123 | } 124 | futures::future::join_all(futures).await; 125 | }) 126 | } 127 | -------------------------------------------------------------------------------- /src/types/list.rs: -------------------------------------------------------------------------------- 1 | use crate::query::NodeSelection; 2 | use crate::schema::{Schema, TypeMetadata}; 3 | use crate::types::{OutputType, ResolvedNode, ResourceList, Type}; 4 | 5 | use serde_json::Value; 6 | use std::borrow::Cow; 7 | 8 | impl Type for [T] { 9 | fn type_name() -> Cow<'static, str> { 10 | Cow::Owned(format!("[{}]", T::type_name())) 11 | } 12 | 13 | fn type_metadata(schema: &mut Schema) -> TypeMetadata { 14 | TypeMetadata::List { 15 | name: Self::type_name().to_string(), 16 | item_type: schema.register_type::(), 17 | } 18 | } 19 | } 20 | 21 | #[async_trait::async_trait] 22 | impl OutputType for [T] { 23 | async fn resolve(&self, selection: &NodeSelection) -> ResolvedNode { 24 | let futures = self.iter().map(|item| item.resolve(selection)); 25 | let nodes = futures::future::join_all(futures).await; 26 | let (content, children): (Vec, Vec) = nodes 27 | .into_iter() 28 | .map(|ResolvedNode(content, children)| (content, children)) 29 | .unzip(); 30 | 31 | ResolvedNode( 32 | Value::Array(content), 33 | children.into_iter().flatten().collect(), 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod list; 2 | pub mod resource; 3 | pub mod scalars; 4 | 5 | use crate::query::NodeSelection; 6 | use crate::schema::{Schema, TypeMetadata}; 7 | use futures::future::BoxFuture; 8 | use serde_json::Value; 9 | use std::borrow::Cow; 10 | 11 | pub type ResourceList = Vec>; 12 | pub struct ResolvedNode(pub Value, pub ResourceList); 13 | 14 | pub trait Type { 15 | fn type_name() -> Cow<'static, str>; 16 | 17 | fn type_metadata(schema: &mut Schema) -> TypeMetadata; 18 | } 19 | 20 | #[async_trait::async_trait] 21 | pub trait OutputType: Type + Send + Sync { 22 | async fn resolve(&self, selection: &NodeSelection) -> ResolvedNode; 23 | } 24 | 25 | #[async_trait::async_trait] 26 | pub trait ObjectOutputType: OutputType { 27 | async fn resolve_field(&self, selection: &NodeSelection) -> (&'static str, ResolvedNode); 28 | } 29 | 30 | #[async_trait::async_trait] 31 | impl OutputType for T { 32 | async fn resolve(&self, selection: &NodeSelection) -> ResolvedNode { 33 | let mut futures = Vec::with_capacity(selection.nodes.len()); 34 | for node in &selection.nodes { 35 | futures.push(self.resolve_field(&node)); 36 | } 37 | 38 | let mut object_content = serde_json::Map::with_capacity(futures.len()); 39 | let mut object_children = Vec::with_capacity(futures.len()); 40 | let objects = futures::future::join_all(futures).await; 41 | 42 | for (field_name, ResolvedNode(content, mut children)) in objects { 43 | object_content.insert(field_name.to_string(), content); 44 | object_children.append(&mut children); 45 | } 46 | 47 | ResolvedNode(object_content.into(), object_children) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/types/resource.rs: -------------------------------------------------------------------------------- 1 | use crate::query::NodeSelection; 2 | use crate::schema::{Schema, TypeMetadata}; 3 | use crate::types::{OutputType, ResolvedNode, ResourceList, Type}; 4 | 5 | use route_recognizer::Params; 6 | use serde_json::Value; 7 | use std::borrow::Cow; 8 | use std::convert::TryFrom; 9 | 10 | pub trait Route: TryFrom + Send { 11 | fn iri(&self) -> String; 12 | fn path_pattern() -> &'static str; 13 | } 14 | 15 | #[async_trait::async_trait] 16 | pub trait Resource: OutputType + Sized { 17 | type Route: Route; 18 | 19 | fn route(&self) -> Self::Route; 20 | 21 | async fn fetch(route: Self::Route) -> Option; 22 | } 23 | 24 | pub struct Link(pub T); 25 | 26 | impl Type for Link { 27 | fn type_name() -> Cow<'static, str> { 28 | Cow::Owned(format!("Resource:{}", T::type_name())) 29 | } 30 | 31 | fn type_metadata(schema: &mut Schema) -> TypeMetadata { 32 | schema.register_resource::(); 33 | 34 | TypeMetadata::Resource { 35 | name: Self::type_name().to_string(), 36 | item_type: schema.register_type::(), 37 | } 38 | } 39 | } 40 | 41 | #[async_trait::async_trait] 42 | impl OutputType for Link { 43 | async fn resolve(&self, selection: &NodeSelection) -> ResolvedNode { 44 | let node_clone = self.0.clone(); 45 | let selection_clone = selection.clone(); 46 | 47 | let content = Value::String(node_clone.route().iri()); 48 | let children: ResourceList = vec![Box::pin(async move { 49 | node_clone.resolve(&selection_clone).await 50 | })]; 51 | 52 | ResolvedNode(content, children) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/types/scalars.rs: -------------------------------------------------------------------------------- 1 | use crate::query::NodeSelection; 2 | use crate::schema::{Schema, TypeMetadata}; 3 | use crate::types::{OutputType, ResolvedNode, Type}; 4 | use serde_json::Value; 5 | use std::borrow::Cow; 6 | 7 | impl Type for String { 8 | fn type_name() -> Cow<'static, str> { 9 | Cow::Borrowed("String") 10 | } 11 | 12 | fn type_metadata(_schema: &mut Schema) -> TypeMetadata { 13 | TypeMetadata::Scalar { 14 | name: Self::type_name().to_string(), 15 | } 16 | } 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl OutputType for String { 21 | async fn resolve(&self, _: &NodeSelection) -> ResolvedNode { 22 | ResolvedNode(Value::String(self.clone()), vec![]) 23 | } 24 | } 25 | 26 | impl Type for &str { 27 | fn type_name() -> Cow<'static, str> { 28 | Cow::Borrowed("String") 29 | } 30 | 31 | fn type_metadata(_schema: &mut Schema) -> TypeMetadata { 32 | TypeMetadata::Scalar { 33 | name: Self::type_name().to_string(), 34 | } 35 | } 36 | } 37 | 38 | #[async_trait::async_trait] 39 | impl OutputType for &str { 40 | async fn resolve(&self, _: &NodeSelection) -> ResolvedNode { 41 | ResolvedNode(Value::String(self.to_string()), vec![]) 42 | } 43 | } 44 | --------------------------------------------------------------------------------