Quellcode durchsuchen

Move to async/await and hyper

Mark Rousskov vor 5 Jahren
Ursprung
Commit
33f4bc9dda
11 geänderte Dateien mit 435 neuen und 221 gelöschten Zeilen
  1. 120 7
      Cargo.lock
  2. 2 0
      Cargo.toml
  3. 1 1
      rust-toolchain
  4. 4 4
      src/config.rs
  5. 109 70
      src/github.rs
  6. 13 8
      src/handlers.rs
  7. 21 15
      src/handlers/assign.rs
  8. 42 36
      src/handlers/relabel.rs
  9. 6 6
      src/interactions.rs
  10. 8 5
      src/lib.rs
  11. 109 69
      src/main.rs

+ 120 - 7
Cargo.lock

@@ -96,6 +96,7 @@ version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -267,6 +268,11 @@ name = "dtoa"
 version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "either"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.17"
@@ -368,6 +374,20 @@ name = "futures"
 version = "0.1.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "futures-channel-preview"
+version = "0.3.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-core-preview"
+version = "0.3.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "futures-cpupool"
 version = "0.1.8"
@@ -377,6 +397,58 @@ dependencies = [
  "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "futures-executor-preview"
+version = "0.3.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-channel-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-io-preview"
+version = "0.3.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-preview"
+version = "0.3.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-channel-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-executor-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-io-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-sink-preview"
+version = "0.3.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-util-preview"
+version = "0.3.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-channel-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-io-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "getopts"
 version = "0.2.19"
@@ -422,6 +494,17 @@ dependencies = [
  "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "http-body"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "httparse"
 version = "1.3.3"
@@ -455,7 +538,7 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "0.12.28"
+version = "0.12.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -463,6 +546,7 @@ dependencies = [
  "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "h2 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
  "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -471,13 +555,14 @@ dependencies = [
  "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -487,7 +572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
- "hyper 0.12.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -839,6 +924,11 @@ dependencies = [
  "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "pin-utils"
+version = "0.1.0-alpha.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "pkg-config"
 version = "0.3.14"
@@ -1044,7 +1134,7 @@ dependencies = [
  "flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "hyper 0.12.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1363,6 +1453,16 @@ dependencies = [
  "tokio-trace-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "tokio-buf"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "tokio-current-thread"
 version = "0.1.6"
@@ -1494,8 +1594,10 @@ dependencies = [
  "dotenv 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1619,7 +1721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "want"
-version = "0.0.6"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1725,6 +1827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum devise_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf41c59b22b5e3ec0ea55c7847e5f358d340f3a8d6d53a5cf4f1564967f96487"
 "checksum dotenv 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4424bad868b0ffe6ae351ee463526ba625bbca817978293bbe6bb7dc1804a175"
 "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
+"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
 "checksum encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4155785c79f2f6701f185eb2e6b4caf0555ec03477cb4c70db67b465311620ed"
 "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
 "checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9"
@@ -1738,16 +1841,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139"
+"checksum futures-channel-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "21c71ed547606de08e9ae744bb3c6d80f5627527ef31ecf2a7210d0e67bc8fae"
+"checksum futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4b141ccf9b7601ef987f36f1c0d9522f76df3bba1cf2e63bfacccc044c4558f5"
 "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
+"checksum futures-executor-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "87ba260fe51080ba37f063ad5b0732c4ff1f737ea18dcb67833d282cdc2c6f14"
+"checksum futures-io-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "082e402605fcb8b1ae1e5ba7d7fdfd3e31ef510e2a8367dd92927bb41ae41b3a"
+"checksum futures-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "bf25f91c8a9a1f64c451e91b43ba269ed359b9f52d35ed4b3ce3f9c842435867"
+"checksum futures-sink-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4309a25a1069a1f3c10647b227b9afe6722b67a030d3f00a9cbdc171fc038de4"
+"checksum futures-util-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "af8198c48b222f02326940ce2b3aa9e6e91a32886eeaad7ca3b8e4c70daa3f4e"
 "checksum getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "72327b15c228bfe31f1390f93dd5e9279587f0463836393c9df719ce62a3e450"
 "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
 "checksum h2 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "a539b63339fbbb00e081e84b6e11bd1d9634a82d91da2984a18ac74a8823f392"
 "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
 "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a"
+"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
 "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83"
 "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
 "checksum hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273"
-"checksum hyper 0.12.28 (registry+https://github.com/rust-lang/crates.io-index)" = "e8e4606fed1c162e3a63d408c07584429f49a4f34c7176cb6cbee60e78f2372c"
+"checksum hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)" = "a64d71c1e77d39da024f06f5821ee00ad9c38febb90370bad1f07a94e0bc8793"
 "checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f"
 "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
 "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d"
@@ -1789,6 +1900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
 "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
 "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
+"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
 "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
 "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
 "checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d"
@@ -1845,6 +1957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
 "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
 "checksum tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cec6c34409089be085de9403ba2010b80e36938c9ca992c4f67f407bb13db0b1"
+"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46"
 "checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443"
 "checksum tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "83ea44c6c0773cc034771693711c35c677b4b5a4b21b9e7071704c54de7d555e"
 "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926"
@@ -1874,7 +1987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d"
 "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
 "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
-"checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3"
+"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"

+ 2 - 0
Cargo.toml

@@ -22,6 +22,8 @@ parser = { path = "parser" }
 rust_team_data = { git = "https://github.com/rust-lang/team" }
 glob = "0.3.0"
 toml = "0.5.1"
+hyper = "0.12.32"
+futures-preview = { version = "=0.3.0-alpha.17", features = ["compat"] }
 
 [dependencies.serde]
 version = "1"

+ 1 - 1
rust-toolchain

@@ -1 +1 @@
-nightly-2019-05-12
+nightly-2019-07-09

+ 4 - 4
src/config.rs

@@ -31,11 +31,11 @@ pub(crate) struct RelabelConfig {
     pub(crate) allow_unauthenticated: Vec<String>,
 }
 
-pub(crate) fn get(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, Error> {
+pub(crate) async fn get(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, Error> {
     if let Some(config) = get_cached_config(repo) {
         Ok(config)
     } else {
-        get_fresh_config(gh, repo)
+        get_fresh_config(gh, repo).await
     }
 }
 
@@ -50,9 +50,9 @@ fn get_cached_config(repo: &str) -> Option<Arc<Config>> {
     })
 }
 
-fn get_fresh_config(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, Error> {
+async fn get_fresh_config(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, Error> {
     let contents = gh
-        .raw_file(repo, "master", CONFIG_FILE_NAME)?
+        .raw_file(repo, "master", CONFIG_FILE_NAME).await?
         .ok_or_else(|| {
             failure::err_msg(
                 "This repository is not enabled to use triagebot.\n\

+ 109 - 70
src/github.rs

@@ -1,30 +1,71 @@
 use failure::{Error, ResultExt};
+
+use futures::{
+    compat::{Future01CompatExt, Stream01CompatExt},
+    stream::{FuturesUnordered, StreamExt},
+};
 use reqwest::header::{AUTHORIZATION, USER_AGENT};
-use reqwest::{Client, Error as HttpError, RequestBuilder, Response, StatusCode};
+use reqwest::{
+    r#async::{Client, RequestBuilder, Response},
+    StatusCode,
+};
 use std::fmt;
-use std::io::Read;
 
 #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
 pub struct User {
     pub login: String,
 }
 
+impl GithubClient {
+    async fn _send_req(&self, req: RequestBuilder) -> Result<(Response, String), Error> {
+        let req = req
+            .build()
+            .with_context(|_| format!("failed to build request"))?;
+
+        let req_dbg = format!("{:?}", req);
+
+        let resp = self.client.execute(req).compat().await.context(req_dbg.clone())?;
+
+        resp.error_for_status_ref().context(req_dbg.clone())?;
+
+        Ok((resp, req_dbg))
+    }
+    async fn send_req(&self, req: RequestBuilder) -> Result<Vec<u8>, Error> {
+        let (resp, req_dbg) = self._send_req(req).await?;
+
+        let mut body = Vec::new();
+        let mut stream = resp.into_body().compat();
+        while let Some(chunk) = stream.next().await {
+            let chunk = chunk
+                .context("reading stream failed")
+                .map_err(Error::from)
+                .context(req_dbg.clone())?;
+            body.extend_from_slice(&chunk);
+        }
+
+        Ok(body)
+    }
+
+    async fn json<T>(&self, req: RequestBuilder) -> Result<T, Error>
+    where
+        T: serde::de::DeserializeOwned,
+    {
+        let (mut resp, req_dbg) = self._send_req(req).await?;
+
+        Ok(resp.json().compat().await.context(req_dbg)?)
+    }
+}
+
 impl User {
-    pub fn current(client: &GithubClient) -> Result<Self, Error> {
-        Ok(client
-            .get("https://api.github.com/user")
-            .send_req()?
-            .json()?)
+    pub async fn current(client: &GithubClient) -> Result<Self, Error> {
+        client.json(client.get("https://api.github.com/user")).await
     }
 
-    pub fn is_team_member(&self, client: &GithubClient) -> Result<bool, Error> {
-        let client = client.raw();
+    pub async fn is_team_member<'a>(&'a self, client: &'a GithubClient) -> Result<bool, Error> {
         let url = format!("{}/teams.json", rust_team_data::v1::BASE_URL);
         let permission: rust_team_data::v1::Teams = client
-            .get(&url)
-            .send()
-            .and_then(Response::error_for_status)
-            .and_then(|mut r| r.json())
+            .json(client.raw().get(&url))
+            .await
             .context("could not get team data")?;
         let map = permission.teams;
         Ok(map["all"].members.iter().any(|g| g.github == self.login)
@@ -40,11 +81,11 @@ pub struct Label {
 }
 
 impl Label {
-    fn exists(&self, repo_api_prefix: &str, client: &GithubClient) -> bool {
+    async fn exists<'a>(&'a self, repo_api_prefix: &'a str, client: &'a GithubClient) -> bool {
         #[allow(clippy::redundant_pattern_matching)]
         match client
-            .get(&format!("{}/labels/{}", repo_api_prefix, self.name))
-            .send_req()
+            .send_req(client.get(&format!("{}/labels/{}", repo_api_prefix, self.name)))
+            .await
         {
             Ok(_) => true,
             // XXX: Error handling if the request failed for reasons beyond 'label didn't exist'
@@ -77,7 +118,7 @@ pub struct Comment {
 #[derive(Debug)]
 pub enum AssignmentError {
     InvalidAssignee,
-    Http(HttpError),
+    Http(Error),
 }
 
 pub enum Selection<'a, T> {
@@ -96,38 +137,27 @@ impl fmt::Display for AssignmentError {
 
 impl std::error::Error for AssignmentError {}
 
-impl From<HttpError> for AssignmentError {
-    fn from(h: HttpError) -> AssignmentError {
-        AssignmentError::Http(h)
-    }
-}
-
 impl Issue {
-    pub fn get_comment(&self, client: &GithubClient, id: usize) -> Result<Comment, Error> {
+    pub async fn get_comment(&self, client: &GithubClient, id: usize) -> Result<Comment, Error> {
         let comment_url = format!("{}/issues/comments/{}", self.repository_url, id);
-        let comment = client
-            .get(&comment_url)
-            .send_req()
-            .context("failed to get comment")?
-            .json()?;
+        let comment = client.json(client.get(&comment_url)).await?;
         Ok(comment)
     }
 
-    pub fn edit_body(&self, client: &GithubClient, body: &str) -> Result<(), Error> {
+    pub async fn edit_body(&self, client: &GithubClient, body: &str) -> Result<(), Error> {
         let edit_url = format!("{}/issues/{}", self.repository_url, self.number);
         #[derive(serde::Serialize)]
         struct ChangedIssue<'a> {
             body: &'a str,
         }
-        client
+        client._send_req(client
             .patch(&edit_url)
-            .json(&ChangedIssue { body })
-            .send_req()
+            .json(&ChangedIssue { body })).await
             .context("failed to edit issue body")?;
         Ok(())
     }
 
-    pub fn edit_comment(
+    pub async fn edit_comment(
         &self,
         client: &GithubClient,
         id: usize,
@@ -138,28 +168,28 @@ impl Issue {
         struct NewComment<'a> {
             body: &'a str,
         }
-        client
+        client._send_req(client
             .patch(&comment_url)
-            .json(&NewComment { body: new_body })
-            .send_req()
+            .json(&NewComment { body: new_body }))
+        .await
             .context("failed to edit comment")?;
         Ok(())
     }
 
-    pub fn post_comment(&self, client: &GithubClient, body: &str) -> Result<(), Error> {
+    pub async fn post_comment(&self, client: &GithubClient, body: &str) -> Result<(), Error> {
         #[derive(serde::Serialize)]
         struct PostComment<'a> {
             body: &'a str,
         }
-        client
+        client._send_req(client
             .post(&self.comments_url)
-            .json(&PostComment { body })
-            .send_req()
+            .json(&PostComment { body }))
+        .await
             .context("failed to post comment")?;
         Ok(())
     }
 
-    pub fn set_labels(&self, client: &GithubClient, mut labels: Vec<Label>) -> Result<(), Error> {
+    pub async fn set_labels(&self, client: &GithubClient, labels: Vec<Label>) -> Result<(), Error> {
         // PUT /repos/:owner/:repo/issues/:number/labels
         // repo_url = https://api.github.com/repos/Codertocat/Hello-World
         let url = format!(
@@ -168,18 +198,23 @@ impl Issue {
             number = self.number
         );
 
-        labels.retain(|label| label.exists(&self.repository_url, &client));
+        let mut stream = labels.into_iter().map(|label| {
+            async { (label.exists(&self.repository_url, &client).await, label) }
+        }).collect::<FuturesUnordered<_>>();
+        let mut labels = Vec::new();
+        while let Some((true, label)) = stream.next().await {
+            labels.push(label);
+        }
 
         #[derive(serde::Serialize)]
         struct LabelsReq {
             labels: Vec<String>,
         }
-        client
+        client._send_req(client
             .put(&url)
             .json(&LabelsReq {
                 labels: labels.iter().map(|l| l.name.clone()).collect(),
-            })
-            .send_req()
+            })).await
             .context("failed to set labels")?;
 
         Ok(())
@@ -193,10 +228,10 @@ impl Issue {
         self.assignees.contains(user)
     }
 
-    pub fn remove_assignees(
+    pub async fn remove_assignees(
         &self,
         client: &GithubClient,
-        selection: Selection<User>,
+        selection: Selection<'_, User>,
     ) -> Result<(), AssignmentError> {
         let url = format!(
             "{repo_url}/issues/{number}/assignees",
@@ -217,17 +252,16 @@ impl Issue {
         struct AssigneeReq<'a> {
             assignees: &'a [&'a str],
         }
-        client
+        client._send_req(client
             .delete(&url)
             .json(&AssigneeReq {
                 assignees: &assignees[..],
-            })
-            .send_req()
+            })).await
             .map_err(AssignmentError::Http)?;
         Ok(())
     }
 
-    pub fn set_assignee(&self, client: &GithubClient, user: &str) -> Result<(), AssignmentError> {
+    pub async fn set_assignee(&self, client: &GithubClient, user: &str) -> Result<(), AssignmentError> {
         let url = format!(
             "{repo_url}/issues/{number}/assignees",
             repo_url = self.repository_url,
@@ -240,8 +274,8 @@ impl Issue {
             name = user,
         );
 
-        match client.get(&check_url).send() {
-            Ok(resp) => {
+        match client._send_req(client.get(&check_url)).await {
+            Ok((resp, _)) => {
                 if resp.status() == reqwest::StatusCode::NO_CONTENT {
                     // all okay
                 } else if resp.status() == reqwest::StatusCode::NOT_FOUND {
@@ -251,17 +285,17 @@ impl Issue {
             Err(e) => return Err(AssignmentError::Http(e)),
         }
 
-        self.remove_assignees(client, Selection::All)?;
+        self.remove_assignees(client, Selection::All).await?;
 
         #[derive(serde::Serialize)]
         struct AssigneeReq<'a> {
             assignees: &'a [&'a str],
         }
 
-        client
+        client._send_req(client
             .post(&url)
-            .json(&AssigneeReq { assignees: &[user] })
-            .send_req()
+            .json(&AssigneeReq { assignees: &[user] }))
+            .await
             .map_err(AssignmentError::Http)?;
 
         Ok(())
@@ -363,7 +397,6 @@ impl Event {
 
 trait RequestSend: Sized {
     fn configure(self, g: &GithubClient) -> Self;
-    fn send_req(self) -> Result<Response, HttpError>;
 }
 
 impl RequestSend for RequestBuilder {
@@ -371,13 +404,6 @@ impl RequestSend for RequestBuilder {
         self.header(USER_AGENT, "rust-lang-triagebot")
             .header(AUTHORIZATION, format!("token {}", g.token))
     }
-
-    fn send_req(self) -> Result<Response, HttpError> {
-        match self.send() {
-            Ok(r) => r.error_for_status(),
-            Err(e) => Err(e),
-        }
-    }
 }
 
 #[derive(Clone)]
@@ -395,16 +421,29 @@ impl GithubClient {
         &self.client
     }
 
-    pub fn raw_file(&self, repo: &str, branch: &str, path: &str) -> Result<Option<Vec<u8>>, Error> {
+    pub async fn raw_file(&self, repo: &str, branch: &str, path: &str) -> Result<Option<Vec<u8>>, Error> {
         let url = format!(
             "https://raw.githubusercontent.com/{}/{}/{}",
             repo, branch, path
         );
-        let mut resp = self.get(&url).send()?;
-        match resp.status() {
+        let req = self.get(&url);
+        let req_dbg = format!("{:?}", req);
+        let req = req
+            .build()
+            .with_context(|_| format!("failed to build request {:?}", req_dbg))?;
+        let resp = self.client.execute(req).compat().await.context(req_dbg.clone())?;
+        let status = resp.status();
+        match status {
             StatusCode::OK => {
                 let mut buf = Vec::with_capacity(resp.content_length().unwrap_or(4) as usize);
-                resp.read_to_end(&mut buf)?;
+                let mut stream = resp.into_body().compat();
+                while let Some(chunk) = stream.next().await {
+                    let chunk = chunk
+                        .context("reading stream failed")
+                        .map_err(Error::from)
+                        .context(req_dbg.clone())?;
+                    buf.extend_from_slice(&chunk);
+                }
                 Ok(Some(buf))
             }
             StatusCode::NOT_FOUND => Ok(None),

+ 13 - 8
src/handlers.rs

@@ -1,15 +1,16 @@
 use crate::github::{Event, GithubClient};
 use failure::Error;
+use futures::future::BoxFuture;
 
 macro_rules! handlers {
     ($($name:ident = $handler:expr,)*) => {
         $(mod $name;)*
 
-        pub fn handle(ctx: &Context, event: &Event) -> Result<(), Error> {
+        pub async fn handle(ctx: &Context, event: &Event) -> Result<(), Error> {
+            let config = crate::config::get(&ctx.github, event.repo_name()).await?;
             $(if let Some(input) = Handler::parse_input(&$handler, ctx, event)? {
-                let config = crate::config::get(&ctx.github, event.repo_name())?;
                 if let Some(config) = &config.$name {
-                    Handler::handle_input(&$handler, ctx, config, event, input)?;
+                    Handler::handle_input(&$handler, ctx, config, event, input).await?;
                 } else {
                     failure::bail!(
                         "The feature `{}` is not enabled in this repository.\n\
@@ -39,13 +40,17 @@ pub trait Handler: Sync + Send {
     type Input;
     type Config;
 
-    fn parse_input(&self, ctx: &Context, event: &Event) -> Result<Option<Self::Input>, Error>;
-
-    fn handle_input(
+    fn parse_input(
         &self,
         ctx: &Context,
-        config: &Self::Config,
         event: &Event,
+    ) -> Result<Option<Self::Input>, Error>;
+
+    fn handle_input<'a>(
+        &self,
+        ctx: &'a Context,
+        config: &'a Self::Config,
+        event: &'a Event,
         input: Self::Input,
-    ) -> Result<(), Error>;
+    ) -> BoxFuture<'a, Result<(), Error>>;
 }

+ 21 - 15
src/handlers/assign.rs

@@ -20,6 +20,7 @@ use crate::{
 use failure::{Error, ResultExt};
 use parser::command::assign::AssignCommand;
 use parser::command::{Command, Input};
+use futures::future::{FutureExt, BoxFuture};
 
 pub(super) struct AssignmentHandler;
 
@@ -62,14 +63,19 @@ impl Handler for AssignmentHandler {
         }
     }
 
-    fn handle_input(
+    fn handle_input<'a>(
         &self,
-        ctx: &Context,
-        _config: &AssignConfig,
-        event: &Event,
+        ctx: &'a Context,
+        _config: &'a AssignConfig,
+        event: &'a Event,
         cmd: AssignCommand,
-    ) -> Result<(), Error> {
-        let is_team_member = if let Err(_) | Ok(false) = event.user().is_team_member(&ctx.github) {
+    ) -> BoxFuture<'a, Result<(), Error>> {
+        handle_input(ctx, event, cmd).boxed()
+    }
+}
+
+async fn handle_input(ctx: &Context, event: &Event, cmd: AssignCommand) -> Result<(), Error> {
+        let is_team_member = if let Err(_) | Ok(false) = event.user().is_team_member(&ctx.github).await {
             false
         } else {
             true
@@ -94,8 +100,8 @@ impl Handler for AssignmentHandler {
                         event
                             .issue()
                             .unwrap()
-                            .remove_assignees(&ctx.github, Selection::All)?;
-                        e.apply(&ctx.github, String::new(), AssignData { user: None })?;
+                            .remove_assignees(&ctx.github, Selection::All).await?;
+                        e.apply(&ctx.github, String::new(), AssignData { user: None }).await?;
                         return Ok(());
                     } else {
                         failure::bail!("Cannot release another user's assignment");
@@ -106,8 +112,8 @@ impl Handler for AssignmentHandler {
                         event
                             .issue()
                             .unwrap()
-                            .remove_assignees(&ctx.github, Selection::One(&current))?;
-                        e.apply(&ctx.github, String::new(), AssignData { user: None })?;
+                            .remove_assignees(&ctx.github, Selection::One(&current)).await?;
+                        e.apply(&ctx.github, String::new(), AssignData { user: None }).await?;
                         return Ok(());
                     } else {
                         failure::bail!("Cannot release unassigned issue");
@@ -119,15 +125,15 @@ impl Handler for AssignmentHandler {
             user: Some(to_assign.clone()),
         };
 
-        e.apply(&ctx.github, String::new(), &data)?;
+        e.apply(&ctx.github, String::new(), &data).await?;
 
-        match event.issue().unwrap().set_assignee(&ctx.github, &to_assign) {
+        match event.issue().unwrap().set_assignee(&ctx.github, &to_assign).await {
             Ok(()) => return Ok(()), // we are done
             Err(github::AssignmentError::InvalidAssignee) => {
                 event
                     .issue()
                     .unwrap()
-                    .set_assignee(&ctx.github, &ctx.username)
+                    .set_assignee(&ctx.github, &ctx.username).await
                     .context("self-assignment failed")?;
                 e.apply(
                     &ctx.github,
@@ -137,11 +143,11 @@ impl Handler for AssignmentHandler {
                         event.html_url().unwrap()
                     ),
                     &data,
-                )?;
+                ).await?;
             }
             Err(e) => return Err(e.into()),
         }
 
         Ok(())
-    }
+
 }

+ 42 - 36
src/handlers/relabel.rs

@@ -17,6 +17,7 @@ use crate::{
 use failure::Error;
 use parser::command::relabel::{LabelDelta, RelabelCommand};
 use parser::command::{Command, Input};
+use futures::future::{FutureExt, BoxFuture};
 
 pub(super) struct RelabelHandler;
 
@@ -54,58 +55,63 @@ impl Handler for RelabelHandler {
         }
     }
 
-    fn handle_input(
+    fn handle_input<'a>(
         &self,
-        ctx: &Context,
-        config: &RelabelConfig,
-        event: &Event,
+        ctx: &'a Context,
+        config: &'a RelabelConfig,
+        event: &'a Event,
         input: RelabelCommand,
-    ) -> Result<(), Error> {
-        let mut issue_labels = event.issue().unwrap().labels().to_owned();
-        let mut changed = false;
-        for delta in &input.0 {
-            let name = delta.label().as_str();
-            if let Err(msg) = check_filter(name, config, &event.user(), &ctx.github) {
-                ErrorComment::new(&event.issue().unwrap(), msg.to_string()).post(&ctx.github)?;
-                return Ok(());
-            }
-            match delta {
-                LabelDelta::Add(label) => {
-                    if !issue_labels.iter().any(|l| l.name == label.as_str()) {
-                        changed = true;
-                        issue_labels.push(github::Label {
-                            name: label.to_string(),
-                        });
-                    }
+    ) -> BoxFuture<'a, Result<(), Error>> {
+        handle_input(ctx, config, event, input).boxed()
+    }
+}
+
+async fn handle_input(ctx: &Context, config: &RelabelConfig, event: &Event, input: RelabelCommand) -> Result<(), Error> {
+    let mut issue_labels = event.issue().unwrap().labels().to_owned();
+    let mut changed = false;
+    for delta in &input.0 {
+        let name = delta.label().as_str();
+        if let Err(msg) = check_filter(name, config, &event.user(), &ctx.github).await {
+            let cmnt = ErrorComment::new(&event.issue().unwrap(), msg.to_string());
+            cmnt.post(&ctx.github).await?;
+            return Ok(());
+        }
+        match delta {
+            LabelDelta::Add(label) => {
+                if !issue_labels.iter().any(|l| l.name == label.as_str()) {
+                    changed = true;
+                    issue_labels.push(github::Label {
+                        name: label.to_string(),
+                    });
                 }
-                LabelDelta::Remove(label) => {
-                    if let Some(pos) = issue_labels.iter().position(|l| l.name == label.as_str()) {
-                        changed = true;
-                        issue_labels.remove(pos);
-                    }
+            }
+            LabelDelta::Remove(label) => {
+                if let Some(pos) = issue_labels.iter().position(|l| l.name == label.as_str()) {
+                    changed = true;
+                    issue_labels.remove(pos);
                 }
             }
         }
+    }
 
-        if changed {
-            event
-                .issue()
-                .unwrap()
-                .set_labels(&ctx.github, issue_labels)?;
-        }
-
-        Ok(())
+    if changed {
+        event
+            .issue()
+            .unwrap()
+            .set_labels(&ctx.github, issue_labels).await?;
     }
+
+    Ok(())
 }
 
-fn check_filter(
+async fn check_filter(
     label: &str,
     config: &RelabelConfig,
     user: &github::User,
     client: &GithubClient,
 ) -> Result<(), Error> {
     let is_team_member;
-    match user.is_team_member(client) {
+    match user.is_team_member(client).await {
         Ok(true) => return Ok(()),
         Ok(false) => {
             is_team_member = Ok(());

+ 6 - 6
src/interactions.rs

@@ -18,7 +18,7 @@ impl<'a> ErrorComment<'a> {
         }
     }
 
-    pub fn post(&self, client: &GithubClient) -> Result<(), Error> {
+    pub async fn post(&self, client: &GithubClient) -> Result<(), Error> {
         let mut body = String::new();
         writeln!(body, "**Error**: {}", self.message)?;
         writeln!(body)?;
@@ -26,7 +26,7 @@ impl<'a> ErrorComment<'a> {
             body,
             "Please let **`@rust-lang/release`** know if you're having trouble with this bot."
         )?;
-        self.issue.post_comment(client, &body)
+        self.issue.post_comment(client, &body).await
     }
 }
 
@@ -99,7 +99,7 @@ impl<'a> EditIssueBody<'a> {
         )
     }
 
-    pub fn apply<T>(&self, client: &GithubClient, text: String, data: T) -> Result<(), Error>
+    pub async fn apply<T>(&self, client: &GithubClient, text: String, data: T) -> Result<(), Error>
     where
         T: serde::Serialize,
     {
@@ -127,16 +127,16 @@ impl<'a> EditIssueBody<'a> {
                     let end_idx = start_idx + all_new.len();
                     current_body.replace_range(start_idx..end_idx, "");
                 }
-                self.issue.edit_body(&client, &current_body)?;
+                self.issue.edit_body(&client, &current_body).await?;
             } else {
                 let end_idx = current_body.find(&END_BOT).unwrap();
                 current_body.insert_str(end_idx, &bot_section);
-                self.issue.edit_body(&client, &current_body)?;
+                self.issue.edit_body(&client, &current_body).await?;
             }
         } else {
             let new_body = format!("{}{}", current_body, all_new);
 
-            self.issue.edit_body(&client, &new_body)?;
+            self.issue.edit_body(&client, &new_body).await?;
         }
         Ok(())
     }

+ 8 - 5
src/lib.rs

@@ -1,3 +1,4 @@
+#![feature(async_await)]
 #![allow(clippy::new_without_default)]
 
 use failure::{Error, ResultExt};
@@ -41,7 +42,7 @@ pub fn deserialize_payload<T: serde::de::DeserializeOwned>(v: &str) -> Result<T,
     Ok(serde_json::from_str(&v).with_context(|_| format!("input: {:?}", v))?)
 }
 
-pub fn webhook(
+pub async fn webhook(
     event: EventName,
     payload: String,
     ctx: &handlers::Context,
@@ -53,9 +54,10 @@ pub fn webhook(
                 .map_err(Error::from)?;
 
             let event = github::Event::IssueComment(payload);
-            if let Err(err) = handlers::handle(&ctx, &event) {
+            if let Err(err) = handlers::handle(&ctx, &event).await {
                 if let Some(issue) = event.issue() {
-                    ErrorComment::new(issue, err.to_string()).post(&ctx.github)?;
+                    let cmnt = ErrorComment::new(issue, err.to_string());
+                    cmnt.post(&ctx.github).await?;
                 }
                 return Err(err.into());
             }
@@ -66,9 +68,10 @@ pub fn webhook(
                 .map_err(Error::from)?;
 
             let event = github::Event::Issue(payload);
-            if let Err(err) = handlers::handle(&ctx, &event) {
+            if let Err(err) = handlers::handle(&ctx, &event).await {
                 if let Some(issue) = event.issue() {
-                    ErrorComment::new(issue, err.to_string()).post(&ctx.github)?;
+                    let cmnt = ErrorComment::new(issue, err.to_string());
+                    cmnt.post(&ctx.github).await?;
                 }
                 return Err(err.into());
             }

+ 109 - 69
src/main.rs

@@ -1,91 +1,131 @@
-#![feature(proc_macro_hygiene, decl_macro)]
+#![feature(async_await)]
 #![allow(clippy::new_without_default)]
 
-#[macro_use]
-extern crate rocket;
-
-use failure::ResultExt;
-use reqwest::Client;
-use rocket::{
-    data::Data,
-    http::Status,
-    request::{self, FromRequest, Request},
-    Outcome, State,
+use futures::{
+    compat::{Future01CompatExt, Stream01CompatExt},
+    future::{FutureExt, TryFutureExt},
+    stream::StreamExt,
 };
-use std::{env, io::Read};
-use triagebot::{github, handlers, payload, EventName, WebhookError};
-
-struct XGitHubEvent<'r>(&'r str);
+use hyper::{header, service::service_fn, Body, Request, Response, Server, StatusCode};
+use reqwest::r#async::Client;
+use std::{env, net::SocketAddr, sync::Arc};
+use triagebot::{github, handlers::Context, payload, EventName};
 
-impl<'a, 'r> FromRequest<'a, 'r> for XGitHubEvent<'a> {
-    type Error = &'static str;
-    fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
-        let ev = if let Some(ev) = req.headers().get_one("X-GitHub-Event") {
-            ev
-        } else {
-            return Outcome::Failure((Status::BadRequest, "Needs a X-GitHub-Event"));
-        };
-        Outcome::Success(XGitHubEvent(ev))
+async fn serve_req(req: Request<Body>, ctx: Arc<Context>) -> Result<Response<Body>, hyper::Error> {
+    let (req, body_stream) = req.into_parts();
+    if req.uri.path() != "/github-hook" {
+        return Ok(Response::builder()
+            .status(StatusCode::NOT_FOUND)
+            .body(Body::empty())
+            .unwrap());
     }
-}
-
-struct XHubSignature<'r>(&'r str);
-
-impl<'a, 'r> FromRequest<'a, 'r> for XHubSignature<'a> {
-    type Error = &'static str;
-    fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
-        let ev = if let Some(ev) = req.headers().get_one("X-Hub-Signature") {
-            ev
-        } else {
-            return Outcome::Failure((Status::BadRequest, "Needs a X-Hub-Signature"));
+    if req.method != hyper::Method::POST {
+        return Ok(Response::builder()
+            .status(StatusCode::METHOD_NOT_ALLOWED)
+            .header(header::ALLOW, "POST")
+            .body(Body::empty())
+            .unwrap());
+    }
+    let event = if let Some(ev) = req.headers.get("X-GitHub-Event") {
+        let ev = match ev.to_str().ok() {
+            Some(v) => v,
+            None => {
+                return Ok(Response::builder()
+                    .status(StatusCode::BAD_REQUEST)
+                    .body(Body::from("X-GitHub-Event header must be UTF-8 encoded"))
+                    .unwrap());
+            }
         };
-        Outcome::Success(XHubSignature(ev))
+        match ev.parse::<EventName>() {
+            Ok(v) => v,
+            Err(_) => unreachable!(),
+        }
+    } else {
+        return Ok(Response::builder()
+            .status(StatusCode::BAD_REQUEST)
+            .body(Body::from("X-GitHub-Event header must be set"))
+            .unwrap());
+    };
+    let signature = if let Some(sig) = req.headers.get("X-Hub-Signature") {
+        match sig.to_str().ok() {
+            Some(v) => v,
+            None => {
+                return Ok(Response::builder()
+                    .status(StatusCode::BAD_REQUEST)
+                    .body(Body::from("X-Hub-Signature header must be UTF-8 encoded"))
+                    .unwrap());
+            }
+        }
+    } else {
+        return Ok(Response::builder()
+            .status(StatusCode::BAD_REQUEST)
+            .body(Body::from("X-Hub-Signature header must be set"))
+            .unwrap());
+    };
+
+    let mut c = body_stream.compat();
+    let mut payload = Vec::new();
+    while let Some(chunk) = c.next().await {
+        let chunk = chunk?;
+        payload.extend_from_slice(&chunk);
     }
-}
 
-#[post("/github-hook", data = "<payload>")]
-fn webhook(
-    signature: XHubSignature,
-    event_header: XGitHubEvent,
-    payload: Data,
-    ctx: State<handlers::Context>,
-) -> Result<(), WebhookError> {
-    let event = match event_header.0.parse::<EventName>() {
-        Ok(v) => v,
-        Err(_) => unreachable!(),
+    if let Err(_) = payload::assert_signed(signature, &payload) {
+        return Ok(Response::builder()
+            .status(StatusCode::FORBIDDEN)
+            .body(Body::from("Wrong signature"))
+            .unwrap());
+    }
+    let payload = match String::from_utf8(payload) {
+        Ok(p) => p,
+        Err(_) => {
+            return Ok(Response::builder()
+                .status(StatusCode::BAD_REQUEST)
+                .body(Body::from("Payload must be UTF-8"))
+                .unwrap());
+        }
     };
 
-    let mut stream = payload.open().take(1024 * 1024 * 5); // 5 Megabytes
-    let mut buf = Vec::new();
-    if let Err(err) = stream.read_to_end(&mut buf) {
-        log::trace!("failed to read request body: {:?}", err);
-        return Err(WebhookError::from(failure::err_msg(
-            "failed to read request body",
-        )));
+    match triagebot::webhook(event, payload, &ctx).await {
+        Ok(()) => {}
+        Err(err) => {
+            log::error!("request failed: {:?}", err);
+            return Ok(Response::builder()
+                .status(StatusCode::INTERNAL_SERVER_ERROR)
+                .body(Body::from("request failed"))
+                .unwrap());
+        }
     }
 
-    payload::assert_signed(signature.0, &buf).map_err(failure::Error::from)?;
-    let payload = String::from_utf8(buf)
-        .context("utf-8 payload required")
-        .map_err(failure::Error::from)?;
-    triagebot::webhook(event, payload, &ctx)
+    Ok(Response::new(Body::from("processed request")))
 }
 
-fn main() {
-    dotenv::dotenv().ok();
+async fn run_server(addr: SocketAddr) {
+    log::info!("Listening on http://{}", addr);
+
     let client = Client::new();
     let gh = github::GithubClient::new(
         client.clone(),
         env::var("GITHUB_API_TOKEN").expect("Missing GITHUB_API_TOKEN"),
     );
-    let ctx = handlers::Context {
+    let ctx = Arc::new(Context {
         github: gh.clone(),
-        username: github::User::current(&gh).unwrap().login,
-    };
+        username: github::User::current(&gh).await.unwrap().login,
+    });
+
+    let serve_future = Server::bind(&addr).serve(move || {
+        let ctx = ctx.clone();
+        service_fn(move |req| serve_req(req, ctx.clone()).boxed().compat())
+    });
+
+    if let Err(e) = serve_future.compat().await {
+        eprintln!("server error: {}", e);
+    }
+}
+
+fn main() {
+    dotenv::dotenv().ok();
 
-    rocket::ignite()
-        .manage(gh)
-        .manage(ctx)
-        .mount("/", routes![webhook])
-        .launch();
+    let addr = ([0, 0, 0, 0], 8002).into();
+    hyper::rt::run(run_server(addr).unit_error().boxed().compat());
 }