From d0cecb26aa07a6d7d1b1b6052fd772066ca5b588 Mon Sep 17 00:00:00 2001 From: Paul Delafosse Date: Sat, 25 Nov 2023 08:02:06 +0100 Subject: [PATCH] refactor: migrate to axum --- .gitignore | 1 + Cargo.lock | 707 ++++++++----------- Cargo.toml | 24 +- Rocket.toml | 4 - config.example.toml | 7 + src/{model/mod.rs => cog/commit.rs} | 13 +- src/cog/mod.rs | 2 + src/cog/report.rs | 170 +++++ src/comment.rs | 60 -- src/error.rs | 26 +- src/event_guard.rs | 27 - src/{octo => gh}/authenticate.rs | 26 +- src/gh/check_run.rs | 14 + src/gh/commits.rs | 34 + src/gh/event.rs | 45 ++ src/gh/mod.rs | 185 +++++ src/main.rs | 193 ++--- src/model/github_event/mod.rs | 1 - src/model/github_event/pull_request_event.rs | 48 -- src/model/installation.rs | 7 - src/model/installation_token.rs | 27 - src/model/report.rs | 80 --- src/octo/check_run.rs | 147 ---- src/octo/commits.rs | 68 -- src/octo/mod.rs | 3 - src/settings.rs | 37 + 26 files changed, 919 insertions(+), 1037 deletions(-) delete mode 100644 Rocket.toml create mode 100644 config.example.toml rename src/{model/mod.rs => cog/commit.rs} (56%) create mode 100644 src/cog/mod.rs create mode 100644 src/cog/report.rs delete mode 100644 src/comment.rs delete mode 100644 src/event_guard.rs rename src/{octo => gh}/authenticate.rs (75%) create mode 100644 src/gh/check_run.rs create mode 100644 src/gh/commits.rs create mode 100644 src/gh/event.rs create mode 100644 src/gh/mod.rs delete mode 100644 src/model/github_event/mod.rs delete mode 100644 src/model/github_event/pull_request_event.rs delete mode 100644 src/model/installation.rs delete mode 100644 src/model/installation_token.rs delete mode 100644 src/model/report.rs delete mode 100644 src/octo/check_run.rs delete mode 100644 src/octo/commits.rs delete mode 100644 src/octo/mod.rs create mode 100644 src/settings.rs diff --git a/.gitignore b/.gitignore index 61ae246..6d4396f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea .private_key .cargo/ +config.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f773cc3..72962c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.2" @@ -86,12 +97,6 @@ dependencies = [ "syn 2.0.28", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - [[package]] name = "atty" version = "0.2.14" @@ -109,6 +114,88 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-extra" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab90e7b70bea63a153137162affb6a0bce26b584c24a4c7885509783e2cf30b" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "pin-project-lite", + "serde", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "backtrace" version = "0.3.68" @@ -136,12 +223,6 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - [[package]] name = "bitflags" version = "1.3.2" @@ -187,11 +268,12 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -246,7 +328,7 @@ dependencies = [ "anyhow", "chrono", "colored", - "config", + "config 0.12.0", "conventional_commit_parser", "edit", "git2", @@ -262,7 +344,7 @@ dependencies = [ "stderrlog", "tempfile", "tera", - "toml 0.5.11", + "toml 0.7.6", "which", ] @@ -272,13 +354,22 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "axum", + "axum-extra", + "axum-macros", "cocogitto", + "config 0.13.4", + "futures", "indoc", "jsonwebtoken", - "log", "octocrab", - "rocket", + "serde", + "serde_json", + "tokio", "tokio-test", + "tower-http", + "tracing", + "tracing-subscriber", ] [[package]] @@ -307,24 +398,32 @@ dependencies = [ ] [[package]] -name = "conventional_commit_parser" -version = "0.9.4" +name = "config" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58660f9e1d5eeeeec9c33d1473ea8bba000c673a2189edaeedb4523ec7d6f7cb" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" dependencies = [ - "pest", - "pest_derive", + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.5.11", + "yaml-rust", ] [[package]] -name = "cookie" -version = "0.17.0" +name = "conventional_commit_parser" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +checksum = "58660f9e1d5eeeeec9c33d1473ea8bba000c673a2189edaeedb4523ec7d6f7cb" dependencies = [ - "percent-encoding", - "time 0.3.24", - "version_check", + "pest", + "pest_derive", ] [[package]] @@ -374,39 +473,6 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95203a6a50906215a502507c0f879a0ce7ff205a6111e2db2a5ef8e4bb92e43" -[[package]] -name = "devise" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" -dependencies = [ - "bitflags 2.3.3", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.28", -] - [[package]] name = "digest" version = "0.10.7" @@ -417,6 +483,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "doc-comment" version = "0.3.3" @@ -439,15 +511,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -481,20 +544,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" -[[package]] -name = "figment" -version = "0.10.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5" -dependencies = [ - "atomic", - "pear", - "serde", - "toml 0.7.6", - "uncased", - "version_check", -] - [[package]] name = "fnv" version = "1.0.7" @@ -512,9 +561,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -527,9 +576,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -537,15 +586,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -554,15 +603,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", @@ -571,21 +620,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -599,19 +648,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -652,12 +688,6 @@ dependencies = [ "url", ] -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "globset" version = "0.4.12" @@ -682,30 +712,14 @@ dependencies = [ "walkdir", ] -[[package]] -name = "h2" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 1.9.3", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -793,14 +807,13 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -885,17 +898,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.0.0" @@ -908,15 +910,19 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] -name = "inlinable_string" -version = "0.1.15" +name = "iri-string" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "is-terminal" @@ -962,15 +968,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonwebtoken" -version = "8.3.0" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +checksum = "155c4d7e39ad04c172c5e3a99c434ea3b4a7ba7960b38ecd562b270b097cce09" dependencies = [ "base64 0.21.2", "pem", - "ring", + "ring 0.17.5", "serde", "serde_json", "simple_asn1", @@ -984,9 +1001,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libgit2-sys" @@ -1018,6 +1035,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -1040,21 +1063,6 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - [[package]] name = "matchers" version = "0.1.0" @@ -1064,6 +1072,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.5.0" @@ -1093,35 +1107,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] -[[package]] -name = "multer" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin 0.9.8", - "tokio", - "tokio-util", - "version_check", -] - [[package]] name = "nom" version = "7.1.3" @@ -1193,9 +1187,9 @@ dependencies = [ [[package]] name = "octocrab" -version = "0.28.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0943920a77d028b66ebe4407813733ad4d0ebe7b12dafd608ddec8478e5fef0b" +checksum = "abfeeafb5fa0da7046229ec3c7b3bd2981aae05c549871192c408d59fc0fffd5" dependencies = [ "arc-swap", "async-trait", @@ -1240,6 +1234,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "overload" version = "0.1.1" @@ -1284,36 +1288,14 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" -[[package]] -name = "pear" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi 1.0.0-rc", -] - -[[package]] -name = "pear_codegen" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.28", -] - [[package]] name = "pem" -version = "1.1.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", + "serde", ] [[package]] @@ -1427,9 +1409,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1458,19 +1440,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", - "version_check", - "yansi 1.0.0-rc", -] - [[package]] name = "quote" version = "1.0.32" @@ -1519,26 +1488,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "ref-cast" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ef7e18e8841942ddb1cf845054f8008410030a3997875d9e49b7a363063df1" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfaf0c85b766276c797f3791f5bc6d5bd116b41d53049af2789666b0c0bc9fa" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - [[package]] name = "regex" version = "1.9.1" @@ -1593,91 +1542,44 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] [[package]] -name = "rocket" -version = "0.5.0-rc.3" +name = "ring" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ - "async-stream", - "async-trait", - "atomic", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap 1.9.3", - "is-terminal", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time 0.3.24", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi 0.5.1", + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", ] [[package]] -name = "rocket_codegen" -version = "0.5.0-rc.3" +name = "ron" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" dependencies = [ - "devise", - "glob", - "indexmap 1.9.3", - "proc-macro2", - "quote", - "rocket_http", - "syn 2.0.28", - "unicode-xid", + "base64 0.13.1", + "bitflags 1.3.2", + "serde", ] [[package]] -name = "rocket_http" -version = "0.5.0-rc.3" +name = "rust-ini" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" dependencies = [ - "cookie", - "either", - "futures", - "http", - "hyper", - "indexmap 1.9.3", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time 0.3.24", - "tokio", - "uncased", + "cfg-if", + "ordered-multimap", ] [[package]] @@ -1706,7 +1608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" dependencies = [ "log", - "ring", + "ring 0.16.20", "rustls-webpki", "sct", ] @@ -1738,8 +1640,8 @@ version = "0.101.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -1772,12 +1674,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1790,8 +1686,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -1834,18 +1730,18 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.180" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -1854,9 +1750,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -2004,6 +1900,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.5.2" @@ -2016,24 +1922,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - [[package]] name = "stderrlog" version = "0.5.4" @@ -2069,6 +1957,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "tempfile" version = "3.7.0" @@ -2199,19 +2093,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] @@ -2228,9 +2122,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -2282,7 +2176,6 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2321,7 +2214,7 @@ version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap 2.0.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -2347,9 +2240,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "bitflags 2.3.3", "bytes", @@ -2358,7 +2251,9 @@ dependencies = [ "http", "http-body", "http-range-header", + "iri-string", "pin-project-lite", + "tower", "tower-layer", "tower-service", "tracing", @@ -2378,11 +2273,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2391,9 +2285,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", @@ -2402,9 +2296,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -2412,20 +2306,20 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -2451,15 +2345,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "ubyte" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" -dependencies = [ - "serde", -] - [[package]] name = "ucd-trie" version = "0.1.6" @@ -2472,7 +2357,6 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" dependencies = [ - "serde", "version_check", ] @@ -2548,16 +2432,16 @@ dependencies = [ ] [[package]] -name = "unicode-xid" -version = "0.2.4" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" @@ -2811,16 +2695,13 @@ dependencies = [ ] [[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "yansi" -version = "1.0.0-rc" +name = "yaml-rust" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee746ad3851dd3bc40e4a028ab3b00b99278d929e48957bcb2d111874a7e43e" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index e11c200..0d4de1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,26 @@ A pedantic conventional commit github bot powered by Cocogitto # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4.14" -cocogitto = { git = "https://github.com/cocogitto/cocogitto", default-features = false, features = [] } -rocket = { version = "0.5.0-rc.1", features = ["json"] } -octocrab = "0.28.0" -async-trait = "0.1.51" +cocogitto = { git = "https://github.com/cocogitto/cocogitto", default-features = false, features = [] } +octocrab = "0.32.0" indoc = "2.0.3" +jsonwebtoken = "9.1.0" anyhow = "1.0.47" -jsonwebtoken = "8" + +axum = { version = "0.6.20", features = ["json"] } +axum-macros = "0.3.7" +axum-extra = "0.8.0" + +tower-http = { version = "0.4.4", features = ["trace"] } +tokio = { version = "1.34.0", features = ["full"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +config = "0.13.4" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +async-trait = "0.1.72" +futures = "0.3.29" [dev-dependencies] tokio-test = "0.4.2" +indoc = "2.0.4" diff --git a/Rocket.toml b/Rocket.toml deleted file mode 100644 index 377625d..0000000 --- a/Rocket.toml +++ /dev/null @@ -1,4 +0,0 @@ -[default] -address = "0.0.0.0" -port = 8080 -limits = { string = "1 MiB", json = "1 MiB" } \ No newline at end of file diff --git a/config.example.toml b/config.example.toml new file mode 100644 index 0000000..19cec4a --- /dev/null +++ b/config.example.toml @@ -0,0 +1,7 @@ +address = "0.0.0.0" +port = 8080 +github_private_key = """ +-----BEGIN RSA PRIVATE KEY----- +My private key here +-----END RSA PRIVATE KEY----- +""" \ No newline at end of file diff --git a/src/model/mod.rs b/src/cog/commit.rs similarity index 56% rename from src/model/mod.rs rename to src/cog/commit.rs index 699ade1..cb37106 100644 --- a/src/model/mod.rs +++ b/src/cog/commit.rs @@ -1,9 +1,4 @@ -use crate::octo::commits::CommitObjectDto; - -pub mod github_event; -pub mod installation; -pub mod installation_token; -pub mod report; +use octocrab::models::repos::RepoCommit; #[derive(Debug, Clone)] pub struct Commit { @@ -12,13 +7,13 @@ pub struct Commit { pub message: String, } -impl From<&CommitObjectDto> for Commit { - fn from(dto: &CommitObjectDto) -> Self { +impl From<&RepoCommit> for Commit { + fn from(dto: &RepoCommit) -> Self { let author = dto .author .as_ref() .map(|author| author.login.clone()) - .unwrap_or_else(|| dto.commit.author.name.clone()); + .unwrap_or_else(|| dto.commit.author.clone().unwrap().user.name.clone()); Self { author, diff --git a/src/cog/mod.rs b/src/cog/mod.rs new file mode 100644 index 0000000..d645a9d --- /dev/null +++ b/src/cog/mod.rs @@ -0,0 +1,2 @@ +pub mod commit; +pub mod report; diff --git a/src/cog/report.rs b/src/cog/report.rs new file mode 100644 index 0000000..ed0cf1f --- /dev/null +++ b/src/cog/report.rs @@ -0,0 +1,170 @@ +use std::fmt; +use std::fmt::Formatter; + +use anyhow::anyhow; +use cocogitto::conventional::commit::{verify, ConventionalCommitError}; +use cocogitto::settings::Settings as CogSettings; +use indoc::formatdoc; +use octocrab::models::repos::RepoCommit; + +use crate::cog::commit::Commit; + +/// Helper struct to build the final PR comment +/// and github check runs +pub struct CogBotReportBuilder { + inner: Vec, +} + +impl CogBotReportBuilder { + pub fn new(commits: &[RepoCommit], config: CogSettings) -> Self { + let inner: Vec = commits + .iter() + .map(Commit::from) + .map(|commit| CommitReport::from_commit(commit, config.ignore_merge_commits)) + .collect(); + + Self { inner } + } + + pub fn build_comment_success(&self) -> String { + let range = self.get_range(); + format!(":heavy_check_mark: {range} - Conventional commits check succeeded.") + } + + pub fn build_comment_failure(&self) -> String { + let range = self.get_range(); + + let success_commit_count = self.success_count(); + let error_reports = self.get_errors(); + let error_count = error_reports.len(); + let error_reports = error_reports + .into_iter() + .map(|report| report.to_string()) + .collect::>() + .join("\n"); + + formatdoc!( + ":x: Found {} compliant commit and {} non-compliant commits in {}. + + {}", + success_commit_count, + error_count, + range, + error_reports + ) + } + + fn get_range(&self) -> String { + let start = self + .inner + .first() + .expect("At least one errored commit") + .get_sha(); + let end = self + .inner + .last() + .expect("At least one errored commit") + .get_sha(); + + if start == end { + format!("{}...{}", start, end) + } else { + start.to_string() + } + } + + pub fn has_error(&self) -> bool { + self.inner + .iter() + .any(|commit| matches!(commit, CommitReport::Error(_))) + } + + fn success_count(&self) -> usize { + self.inner + .iter() + .filter(|commit| matches!(commit, CommitReport::Success(_))) + .count() + } + + fn get_errors(&self) -> Vec<&CommitErrorReport> { + self.inner + .iter() + .filter_map(|commit| match commit { + CommitReport::Error(commit) => Some(commit), + _ => None, + }) + .collect() + } +} + +#[derive(Debug)] +pub enum CommitReport { + Ignored(Commit), + Success(Commit), + Error(CommitErrorReport), +} + +impl CommitReport { + pub fn get_sha(&self) -> &str { + match self { + CommitReport::Success(commit) | CommitReport::Ignored(commit) => &commit.sha, + CommitReport::Error(err) => &err.sha, + } + } + + pub fn from_commit(commit: Commit, ignore_merge_commit: bool) -> Self { + match verify( + Some(commit.author.clone()), + commit.message.as_str(), + ignore_merge_commit, + ) { + Ok(_) => CommitReport::Success(commit), + Err(error) => CommitReport::Error(CommitErrorReport { + sha: commit.sha, + author: commit.author, + message: commit.message, + error, + }), + } + } +} + +impl From for CommitReport { + fn from(commit: Commit) -> Self { + CommitReport::from_commit(commit, true) + } +} + +#[derive(Debug)] +pub struct CommitErrorReport { + pub sha: String, + pub author: String, + pub message: String, + pub error: Box, +} + +impl fmt::Display for CommitErrorReport { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let error = anyhow!(self.error.clone()); + let error = format!("{:?}", error) + .lines() + .collect::>() + .join("\n\t"); + + let message = formatdoc!( + "Commit {} by @{} is not conform to the conventional commit specification : + - **message:** `{}` + - **cause:** + ``` + {} + ``` + ", + self.sha, + self.author, + self.message, + error, + ); + + writeln!(f, "{}", message) + } +} diff --git a/src/comment.rs b/src/comment.rs deleted file mode 100644 index 5bfdd22..0000000 --- a/src/comment.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::model::report::CommitReport; -use indoc::formatdoc; - -pub fn build_comment_failure(reports: Vec) -> String { - // should be ok to unwrap since we build this only we at least one commit is errored - let start = reports.first().unwrap().get_sha(); - let end = reports.last().unwrap().get_sha(); - - let range = if start == end { - format!("{}...{}", start, end) - } else { - start.to_string() - }; - - let success_commit_count = reports - .iter() - .filter(|report| matches!(report, CommitReport::Success(_))) - .count(); - - let error_reports = reports - .iter() - .filter_map(|report| { - if let CommitReport::Error(error_report) = report { - Some(error_report.to_string()) - } else { - None - } - }) - .collect::>(); - let error_count = error_reports.len(); - let error_reports = error_reports.join("\n"); - - formatdoc!( - ":x: Found {} compliant commit and {} non-compliant commits in {}. - - {}", - success_commit_count, - error_count, - range, - error_reports - ) -} - -pub fn build_comment_success(reports: Vec) -> String { - let start = &reports.first().unwrap().get_sha(); - let end = &reports.last().unwrap().get_sha(); - - if start == end { - format!( - ":heavy_check_mark: {} - Conventional commits check succeeded.", - &start[0..7] - ) - } else { - format!( - ":heavy_check_mark: {}...{} - Conventional commits check succeeded.", - &start[0..7], - &end[0..7] - ) - } -} diff --git a/src/error.rs b/src/error.rs index 556504d..38c9f08 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,23 @@ -#[derive(Debug)] -pub enum ApiError { - UnmanagedEvent(String), - NotAGithubEvent, +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; + +pub type AppResult = Result; + +pub struct AppError { + inner: anyhow::Error, +} + +impl From for AppError +where + T: Into, +{ + fn from(t: T) -> Self { + Self { inner: t.into() } + } +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", self.inner)).into_response() + } } diff --git a/src/event_guard.rs b/src/event_guard.rs deleted file mode 100644 index f6dbb05..0000000 --- a/src/event_guard.rs +++ /dev/null @@ -1,27 +0,0 @@ -use rocket::http::Status; -use rocket::request::{FromRequest, Outcome}; -use rocket::Request; - -use crate::error::ApiError; - -pub struct PullRequestEventType; - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for PullRequestEventType { - type Error = ApiError; - - async fn from_request(req: &'r Request<'_>) -> Outcome { - let event = req.headers().get_one("X-Github-Event"); - - match event { - None => Outcome::Failure((Status::Ok, ApiError::NotAGithubEvent)), - Some(event) => { - if matches!(event, "pull_request") { - Outcome::Success(PullRequestEventType) - } else { - Outcome::Failure((Status::Ok, ApiError::UnmanagedEvent(event.to_string()))) - } - } - } - } -} diff --git a/src/octo/authenticate.rs b/src/gh/authenticate.rs similarity index 75% rename from src/octo/authenticate.rs rename to src/gh/authenticate.rs index a4ac49c..14f34b5 100644 --- a/src/octo/authenticate.rs +++ b/src/gh/authenticate.rs @@ -1,18 +1,26 @@ -use crate::model::installation_token::InstallationToken; use jsonwebtoken::EncodingKey; use octocrab::models::Installation; use octocrab::params::apps::CreateInstallationAccessToken; use octocrab::Octocrab; +use serde::{Deserialize, Serialize}; +use tracing::info; -pub async fn authenticate(installation_id: u64, repository: &str) -> octocrab::Result { - let app_id = 151884; +const COCOGITTO_BOT_APP_ID: u64 = 151884; - let env_key = std::env::var("GITHUB_PRIVATE_KEY").expect("GITHUB_PRIVATE_KEY not set"); +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct InstallationToken { + pub token: String, +} - let key = EncodingKey::from_rsa_pem(env_key.as_bytes()) +pub async fn authenticate( + installation_id: u64, + repository: &str, + gh_key: &str, +) -> octocrab::Result { + let key = EncodingKey::from_rsa_pem(gh_key.as_bytes()) .expect("Configured GitHub private key is not a valid PEM-encoded RSA key"); - let token = octocrab::auth::create_jwt(app_id.into(), &key).unwrap(); + let token = octocrab::auth::create_jwt(COCOGITTO_BOT_APP_ID.into(), &key).unwrap(); let temp_client = Octocrab::builder().personal_token(token).build()?; @@ -42,13 +50,11 @@ pub async fn authenticate(installation_id: u64, repository: &str) -> octocrab::R installation.access_tokens_url.as_ref().unwrap(), Some(&create_access_token), ) - .await - .unwrap(); + .await?; let authed_client = octocrab::OctocrabBuilder::new() .personal_token(access.token) - .build() - .unwrap(); + .build()?; info!( "Authentication success for repo {} with installation id : {}", diff --git a/src/gh/check_run.rs b/src/gh/check_run.rs new file mode 100644 index 0000000..cb1c32a --- /dev/null +++ b/src/gh/check_run.rs @@ -0,0 +1,14 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct CheckOutput { + pub title: String, + pub summary: String, + pub text: String, +} + +impl CheckOutput { + pub fn to_value(self) -> serde_json::Value { + serde_json::to_value(self).expect("valid check run output") + } +} diff --git a/src/gh/commits.rs b/src/gh/commits.rs new file mode 100644 index 0000000..2063a7e --- /dev/null +++ b/src/gh/commits.rs @@ -0,0 +1,34 @@ +use octocrab::models::repos::RepoCommit; +use octocrab::{Octocrab, Page}; + +#[async_trait::async_trait] +pub trait GetCommits { + async fn get_commits( + &self, + owner: &str, + repo: &str, + pr_number: u64, + ) -> octocrab::Result>; +} + +#[async_trait::async_trait] +impl GetCommits for Octocrab { + async fn get_commits( + &self, + owner: &str, + repo: &str, + pr_number: u64, + ) -> octocrab::Result> { + let url = format!("/repos/{}/{}/pulls/{}/commits", owner, repo, pr_number); + let mut current_page: Page = self.get(url, None::<&()>).await?; + let mut response = current_page.take_items(); + + while let Ok(Some(mut new_page)) = self.get_page(¤t_page.next).await { + response.extend(new_page.take_items()); + + current_page = new_page; + } + + Ok(response) + } +} diff --git a/src/gh/event.rs b/src/gh/event.rs new file mode 100644 index 0000000..edffd1f --- /dev/null +++ b/src/gh/event.rs @@ -0,0 +1,45 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct CheckSuiteEvent { + pub action: CheckSuiteAction, + pub check_suite: CheckSuitePayload, + pub repository: Repository, + pub installation: Installation, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum CheckSuiteAction { + Requested, + ReRequested, + Completed, +} + +#[derive(Debug, Deserialize)] +pub struct CheckSuitePayload { + pub pull_requests: Vec, + pub head_sha: String, +} + +#[derive(Debug, Deserialize)] +pub struct Repository { + pub name: String, + pub owner: RepositoryOwner, + pub default_branch: String, +} + +#[derive(Debug, Deserialize)] +pub struct PullRequest { + pub number: u64, +} + +#[derive(Debug, Deserialize)] +pub struct RepositoryOwner { + pub login: String, +} + +#[derive(Debug, Deserialize)] +pub struct Installation { + pub id: u64, +} diff --git a/src/gh/mod.rs b/src/gh/mod.rs new file mode 100644 index 0000000..717052a --- /dev/null +++ b/src/gh/mod.rs @@ -0,0 +1,185 @@ +use cocogitto::settings::Settings as CogSettings; +use octocrab::checks::CheckRunStatus; +use octocrab::models::checks::CheckRun; +use octocrab::models::issues::Comment; +use octocrab::models::repos::RepoCommit; +use octocrab::Octocrab; +use tokio::join; +use tracing::{info, warn}; + +use event::CheckSuiteEvent; + +use crate::cog::report::CogBotReportBuilder; +use crate::gh::authenticate::authenticate; +use crate::gh::check_run::CheckOutput; +use crate::gh::commits::GetCommits; + +pub mod authenticate; +pub mod check_run; +pub mod commits; +pub mod event; + +pub struct CocogittoBot { + inner: Octocrab, + owner: String, + repo: String, + head_sha: String, + pull_request_number: u64, + default_branch: String, +} + +const COCOGITTO_BOT_LOGIN: &str = "cocogitto-bot[bot]"; + +impl CocogittoBot { + pub async fn from_check_suite(event: CheckSuiteEvent, gh_key: &str) -> octocrab::Result { + let check_suite = event.check_suite; + let installation = event.installation; + let repository = event.repository; + + if check_suite.pull_requests.len() > 1 { + warn!("Multiple pull request check_suite event will handle only the first PR"); + } + + let inner = authenticate(installation.id, &repository.name, gh_key).await?; + let pull_request = check_suite + .pull_requests + .into_iter() + .next() + .expect("Pull request should not be empty"); + + Ok(Self { + inner, + owner: repository.owner.login, + repo: repository.name, + head_sha: check_suite.head_sha, + pull_request_number: pull_request.number, + default_branch: repository.default_branch, + }) + } + + pub async fn run(&self) -> anyhow::Result<()> { + let check_run = self.create_check_runs().await?; + self.delete_previous_comment_if_exists().await?; + let cog_config = self.get_cog_config().await?; + let commits = self.get_pull_request_commits().await?; + // self.build_and_send_commit_reports(commits, cog_config, check_run) + // .await + Ok(()) + } + + async fn get_pull_request_commits(&self) -> octocrab::Result> { + self.inner + .get_commits(&self.owner, &self.repo, self.pull_request_number) + .await + } + + async fn get_cog_config(&self) -> octocrab::Result { + let cog_file = self + .inner + .repos(&self.owner, &self.repo) + .get_content() + .path("cog.toml") + .r#ref(&self.default_branch) + .send() + .await + .ok() + .and_then(|mut content| content.take_items().into_iter().next()) + .and_then(|cog| cog.decoded_content()) + .unwrap_or("".to_string()); + + Ok( + CogSettings::try_from(cog_file).unwrap_or_else(|_| CogSettings { + ignore_merge_commits: true, + ..CogSettings::default() + }), + ) + } + + async fn delete_previous_comment_if_exists(&self) -> octocrab::Result<()> { + let issues = self + .inner + .issues(&self.owner, &self.repo) + .list_comments(self.pull_request_number) + .page(1u32) + .send() + .await?; + + let previous_comment = issues + .items + .iter() + .find(|comment| comment.user.login == COCOGITTO_BOT_LOGIN); + + if let Some(previous_comment) = previous_comment { + info!( + "Deleting comment {} in {}/{}#{}", + previous_comment.id, self.owner, self.repo, self.pull_request_number + ); + + self.inner + .issues(&self.owner, &self.repo) + .delete_comment(previous_comment.id) + .await?; + } + + Ok(()) + } + + async fn build_and_send_commit_reports( + &self, + commits: Vec, + cog_config: CogSettings, + check_run: CheckRun, + ) -> anyhow::Result<()> { + let report = CogBotReportBuilder::new(&commits, cog_config); + + let comment = if report.has_error() { + report.build_comment_failure() + } else { + report.build_comment_success() + }; + + let summary = if report.has_error() { + "failure".to_string() + } else { + "success".to_string() + }; + + let check_output = CheckOutput { + title: format!("Cog status check #{}", self.pull_request_number), + summary: summary.clone(), + text: comment.clone(), + }; + + let issue_handler = self.inner.issues(&self.owner, &self.repo); + let check_handler = self.inner.checks(&self.owner, &self.repo); + + let (checks, comment) = join!( + check_handler + .update_check_run(check_run.id) + .conclusion(&summary) + .output(check_output.to_value()) + .status(CheckRunStatus::Completed) + .send(), + issue_handler.create_comment(self.pull_request_number, &comment) + ); + + let _: CheckRun = checks?; + let _: Comment = comment?; + + Ok(()) + } + + async fn create_check_runs(&self) -> octocrab::Result { + info!( + "Creating check runs for {}/{}#{}", + self.owner, self.repo, self.pull_request_number + ); + + self.inner + .checks(&self.owner, &self.repo) + .create_check_run("cog-status-check", &self.head_sha) + .status(CheckRunStatus::Queued) + .send() + .await + } +} diff --git a/src/main.rs b/src/main.rs index 894219e..8448d14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,139 +1,86 @@ -#[macro_use] -extern crate rocket; +use axum::extract::State; +use axum::http::HeaderMap; -#[macro_use] -extern crate log; +use axum::routing::post; +use axum::{Json, Router}; +use axum_macros::debug_handler; -use rocket::serde::json::Json; +use gh::event::{CheckSuiteAction, CheckSuiteEvent}; +use tower_http::trace::TraceLayer; +use tracing::{info, warn}; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; -use cocogitto::settings::Settings; -use model::github_event::pull_request_event::PullRequestEvent; -use model::report::CommitReport; -use model::Commit; -use octo::authenticate; -use octo::commits::GetCommits; +use crate::error::AppResult; -use crate::event_guard::PullRequestEventType; -use crate::model::github_event::pull_request_event::PullRequestAction; -use crate::octo::check_run::CheckRunSummary; +use crate::gh::CocogittoBot; +use crate::settings::Settings; -mod comment; +mod cog; mod error; -mod event_guard; -mod model; -mod octo; +mod gh; +mod settings; -#[get("/health")] -async fn health() {} - -#[post("/", data = "", rank = 2, format = "application/json")] -async fn pull_request(_event: PullRequestEventType, body: Json) -> &'static str { - let event = body.0; +#[derive(Clone)] +pub struct AppState { + github_key: String, +} +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new( + std::env::var("RUST_LOG") + .unwrap_or_else(|_| "cocogitto_github_app=debug,tower_http=debug".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let config = Settings::get()?; + let addr = config.address(); + + let router = Router::new() + .route("/", post(pull_request_handler)) + .layer(TraceLayer::new_for_http()) + .with_state(AppState { + github_key: config.github_private_key, + }); + + axum::Server::bind(&addr) + .serve(router.into_make_service()) + .await?; + + Ok(()) +} - if event.action == PullRequestAction::Closed { - return "ok"; +#[debug_handler] +async fn pull_request_handler( + State(state): State, + headers: HeaderMap, + Json(event): Json, +) -> AppResult<()> { + let Some(event_header) = headers.get("X-Github-Event") else { + warn!("'X-Github-Event' header missing, ignoring request"); + return Ok(()); }; - let owner = &event.repository.owner.login; - let repo = &event.repository.name; - let pull_request_number = &event.number; - let installation_id = event.installation.id; - - let octo = authenticate::authenticate(installation_id, repo) - .await - .expect("Unable to authenticate"); - - // Get the comments for the current pull request - let issues = octo - .issues(owner, repo) - .list_comments(*pull_request_number) - .page(1u32) - .send() - .await - .unwrap(); - - // Try to find a previous cocogitto-bot comment - let previous_comment = issues - .items - .iter() - .find(|comment| comment.user.login == "cocogitto-bot[bot]"); - - // Delete this comment if found - if let Some(previous_comment) = previous_comment { - info!( - "Deleting comment {} in {}/{}#{}", - previous_comment.id, owner, repo, pull_request_number - ); - octo.issues(owner, repo) - .delete_comment(previous_comment.id) - .await - .unwrap(); - } - - // Get all commits for the current pull request - let commits = octo - .get_commits(owner, repo, *pull_request_number) - .await - .unwrap(); - - // Check the target repo for an existing Cocogitto config file - let cog_file = octo - .repos(owner, repo) - .get_content() - .path("cog.toml") - .r#ref(&event.repository.default_branch) - .send() - .await - .ok() - .and_then(|mut content| content.take_items().into_iter().next()) - .and_then(|cog| cog.decoded_content()) - .unwrap_or("".to_string()); - - // Parse the config file into Cocogitto `Settings` (falling - // back to the default if the target repo doesn't have a `cog.toml`) - let cog_config = Settings::try_from(cog_file).unwrap_or_else(|_| Settings { - ignore_merge_commits: true, - ..Settings::default() - }); - - // Turn them into conventional commits report - let reports: Vec = commits - .iter() - .map(Commit::from) - .map(|commit| CommitReport::from_commit(commit, cog_config.ignore_merge_commits)) - .collect(); - - // Send a github check-run for every single commit in the R - let outcome = octo::check_run::per_commit_check_run(&octo, owner, repo, &commits) - .await - .unwrap(); - - info!( - "Commit statuses checked in {}/{}#{}", - owner, repo, pull_request_number - ); - - // Build check-run summary comment (failure if a single commit fails) - let comment = match outcome { - CheckRunSummary::Errored => comment::build_comment_failure(reports), - CheckRunSummary::NoError => comment::build_comment_success(reports), + let Ok("check_suite") = event_header.to_str() else { + info!("Ignoring non check_suite event"); + return Ok(()); }; - // Send the comment - octo.issues(owner, repo) - .create_comment(*pull_request_number, &comment) - .await - .unwrap(); + if event.action == CheckSuiteAction::Completed { + info!("Ignoring completed check_suite"); + return Ok(()); + } - info!( - "Comment summary sent to {}/{}#{}", - owner, repo, pull_request_number - ); + if event.check_suite.pull_requests.is_empty() { + info!("Ignoring check_suite with no pull request"); + } - "ok" -} + CocogittoBot::from_check_suite(event, &state.github_key) + .await? + .run() + .await?; -#[launch] -fn rocket() -> _ { - rocket::build().mount("/", routes![pull_request, health]) + Ok(()) } diff --git a/src/model/github_event/mod.rs b/src/model/github_event/mod.rs deleted file mode 100644 index 6a35e5b..0000000 --- a/src/model/github_event/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod pull_request_event; diff --git a/src/model/github_event/pull_request_event.rs b/src/model/github_event/pull_request_event.rs deleted file mode 100644 index da31923..0000000 --- a/src/model/github_event/pull_request_event.rs +++ /dev/null @@ -1,48 +0,0 @@ -use rocket::serde::Deserialize; - -use crate::model::installation::Installation; - -#[derive(Debug, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct PullRequestEvent { - pub action: PullRequestAction, - pub number: u64, - pub repository: PullRequestRepository, - pub installation: Installation, - pub pull_request: PullRequestEventInner, -} - -#[derive(Debug, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct PullRequestEventInner { - pub head: Head, -} - -#[derive(Debug, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct Head { - pub sha: String, -} - -#[derive(Debug, Deserialize, Eq, PartialOrd, PartialEq)] -#[serde(crate = "rocket::serde")] -#[serde(rename_all = "lowercase")] -pub enum PullRequestAction { - Synchronize, - Opened, - Closed, -} - -#[derive(Debug, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct PullRequestRepository { - pub name: String, - pub owner: PullRequestOwner, - pub default_branch: String, -} - -#[derive(Debug, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct PullRequestOwner { - pub login: String, -} diff --git a/src/model/installation.rs b/src/model/installation.rs deleted file mode 100644 index 5aabc7c..0000000 --- a/src/model/installation.rs +++ /dev/null @@ -1,7 +0,0 @@ -use rocket::serde::Deserialize; - -#[derive(Debug, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct Installation { - pub id: u64, -} diff --git a/src/model/installation_token.rs b/src/model/installation_token.rs deleted file mode 100644 index 5efd8ca..0000000 --- a/src/model/installation_token.rs +++ /dev/null @@ -1,27 +0,0 @@ -use octocrab::models::Repository; -use rocket::serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[serde(crate = "rocket::serde")] -#[non_exhaustive] -pub struct InstallationToken { - pub token: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub expires_at: Option, - pub permissions: Permissions, - #[serde(skip_serializing_if = "Option::is_none")] - pub repositories: Option>, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -#[non_exhaustive] -pub struct Permissions { - #[serde(default)] - pub admin: bool, - #[serde(default)] - pub triage: bool, - #[serde(default)] - pub maintain: bool, -} diff --git a/src/model/report.rs b/src/model/report.rs deleted file mode 100644 index 87cd5dd..0000000 --- a/src/model/report.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::fmt; -use std::fmt::Formatter; - -use crate::model::Commit; -use anyhow::anyhow; -use cocogitto::conventional::commit::{verify, ConventionalCommitError}; -use indoc::formatdoc; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum CommitReport { - Ignored(Commit), - Success(Commit), - Error(CommitErrorReport), -} - -impl CommitReport { - pub fn get_sha(&self) -> &str { - match self { - CommitReport::Success(commit) | CommitReport::Ignored(commit) => &commit.sha, - CommitReport::Error(err) => &err.sha, - } - } - - pub fn from_commit(commit: Commit, ignore_merge_commit: bool) -> Self { - match verify( - Some(commit.author.clone()), - commit.message.as_str(), - ignore_merge_commit, - ) { - Ok(_) => CommitReport::Success(commit), - Err(error) => CommitReport::Error(CommitErrorReport { - sha: commit.sha, - author: commit.author, - message: commit.message, - error, - }), - } - } -} - -impl From for CommitReport { - fn from(commit: Commit) -> Self { - CommitReport::from_commit(commit, true) - } -} - -#[derive(Debug)] -pub struct CommitErrorReport { - pub sha: String, - pub author: String, - pub message: String, - pub error: Box, -} - -impl fmt::Display for CommitErrorReport { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let error = anyhow!(self.error.clone()); - let error = format!("{:?}", error) - .lines() - .collect::>() - .join("\n\t"); - - let message = formatdoc!( - "Commit {} by @{} is not conform to the conventional commit specification : - - **message:** `{}` - - **cause:** - ``` - {} - ``` - ", - self.sha, - self.author, - self.message, - error, - ); - - writeln!(f, "{}", message) - } -} diff --git a/src/octo/check_run.rs b/src/octo/check_run.rs deleted file mode 100644 index b8cf59b..0000000 --- a/src/octo/check_run.rs +++ /dev/null @@ -1,147 +0,0 @@ -use octocrab::{models, Octocrab}; -use rocket::serde::{Deserialize, Serialize}; - -use crate::model::report::CommitReport; -use crate::model::Commit; -use crate::octo::commits::CommitObjectDto; - -#[async_trait::async_trait] -pub trait CheckRunExt { - async fn check_run( - &self, - owner: &str, - repo: &str, - check_run: &CheckRunResult, - ) -> octocrab::Result; -} - -#[async_trait::async_trait] -impl CheckRunExt for Octocrab { - async fn check_run( - &self, - owner: &str, - repo: &str, - check_run: &CheckRunResult, - ) -> octocrab::Result { - let url = format!("/repos/{}/{}/check-runs", owner, repo); - self.post(url, Some(check_run)).await - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct CheckRunResult { - pub output: CheckOutput, - pub name: String, - pub head_sha: String, - pub conclusion: CheckRunConclusion, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct CheckOutput { - pub title: String, - pub summary: String, - pub text: String, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(crate = "rocket::serde", rename_all = "lowercase")] -pub enum CheckRunConclusion { - Success, - Failure, -} - -pub enum CheckRunSummary { - Errored, - NoError, -} - -pub async fn per_commit_check_run( - octo: &Octocrab, - owner: &str, - repo: &str, - commits: &[CommitObjectDto], -) -> octocrab::Result { - let mut reports: Vec = commits - .iter() - .map(Commit::from) - .map(CommitReport::from) - .collect(); - - let has_failures = reports - .iter() - .any(|report| matches!(report, CommitReport::Error(_))); - - let previous_commits_reports: Vec = reports.drain(0..reports.len() - 1).collect(); - - for report in previous_commits_reports { - let check_run = CheckRunResult::from(report); - octo.check_run(owner, repo, &check_run).await?; - } - - if has_failures { - // Only one report remains - let head = reports.get(0).unwrap(); - - let text = match head { - CommitReport::Success(_) | CommitReport::Ignored(_) => { - "Found non-compliant commits in the current pull request :\n".to_string() - } - CommitReport::Error(err) => err.to_string(), - }; - - let final_run = CheckRunResult { - output: CheckOutput { - title: "Conventional commits check".to_string(), - summary: "Failure".to_string(), - text, - }, - name: "Cog status check".to_string(), - head_sha: head.get_sha().to_string(), - conclusion: CheckRunConclusion::Failure, - }; - - octo.check_run(owner, repo, &final_run).await?; - Ok(CheckRunSummary::Errored) - } else { - Ok(CheckRunSummary::NoError) - } -} - -impl From for CheckRunResult { - fn from(report: CommitReport) -> Self { - match report { - CommitReport::Success(commit) => CheckRunResult { - output: CheckOutput { - title: "Conventional commits check".to_string(), - summary: "Success".to_string(), - text: "".to_string(), - }, - name: "Cog status check".to_string(), - head_sha: commit.sha, - conclusion: CheckRunConclusion::Success, - }, - CommitReport::Error(report) => CheckRunResult { - output: CheckOutput { - title: "Conventional commits check".to_string(), - summary: "Failure".to_string(), - text: report.to_string(), - }, - name: "Cog status check".to_string(), - head_sha: report.sha, - conclusion: CheckRunConclusion::Failure, - }, - CommitReport::Ignored(commit) => CheckRunResult { - output: CheckOutput { - title: "Conventional commits check".to_string(), - summary: "Success".to_string(), - text: "Merge commit are ignored from conventional commits check".to_string(), - }, - name: "Cog status check".to_string(), - head_sha: commit.sha, - conclusion: CheckRunConclusion::Success, - }, - } - } -} diff --git a/src/octo/commits.rs b/src/octo/commits.rs deleted file mode 100644 index 9e56bf6..0000000 --- a/src/octo/commits.rs +++ /dev/null @@ -1,68 +0,0 @@ -use octocrab::{Octocrab, Page}; -use rocket::serde::{Deserialize, Serialize}; - -#[async_trait::async_trait] -pub trait GetCommits { - async fn get_commits( - &self, - owner: &str, - repo: &str, - pr_number: u64, - ) -> octocrab::Result>; -} - -#[async_trait::async_trait] -impl GetCommits for Octocrab { - async fn get_commits( - &self, - owner: &str, - repo: &str, - pr_number: u64, - ) -> octocrab::Result> { - let url = format!("/repos/{}/{}/pulls/{}/commits", owner, repo, pr_number); - let mut current_page: Page = self.get(url, None::<&()>).await?; - let mut response = current_page.take_items(); - - while let Ok(Some(mut new_page)) = self.get_page(¤t_page.next).await { - response.extend(new_page.take_items()); - - current_page = new_page; - } - - Ok(response) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(crate = "rocket::serde")] -pub struct CommitObjectDto { - pub sha: String, - pub commit: CommitDto, - pub author: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(crate = "rocket::serde")] -pub struct CommitDto { - pub message: String, - pub tree: TreeDto, - pub author: AuthorInnerDto, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(crate = "rocket::serde")] -pub struct AuthorDto { - pub login: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(crate = "rocket::serde")] -pub struct AuthorInnerDto { - pub name: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(crate = "rocket::serde")] -pub struct TreeDto { - sha: String, -} diff --git a/src/octo/mod.rs b/src/octo/mod.rs deleted file mode 100644 index b800a52..0000000 --- a/src/octo/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod authenticate; -pub mod check_run; -pub mod commits; diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..7793994 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,37 @@ +use config::{Config, ConfigError, File, FileFormat}; +use serde::Deserialize; +use std::net::{Ipv4Addr, SocketAddr}; + +#[derive(Deserialize)] +pub struct Settings { + address: String, + port: u16, + pub github_private_key: String, +} + +impl Settings { + pub fn get() -> Result { + let config = Config::builder() + .add_source(File::new("config.toml", FileFormat::Toml)) + .build()?; + + config.try_deserialize() + } + + pub fn address(&self) -> SocketAddr { + let addr: Ipv4Addr = self.address.parse().expect("Valid socket address"); + SocketAddr::new(addr.into(), self.port) + } +} + +#[cfg(test)] +mod test { + use crate::settings::Settings; + + #[test] + fn should_get_settings() { + let settings = Settings::get(); + assert!(settings.is_ok()); + assert_eq!(settings.unwrap().address().port(), 8080); + } +}