diff --git a/Cargo.lock b/Cargo.lock index ca65bc3..ddbbc78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,264 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "actix-codec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" -dependencies = [ - "bitflags 2.5.0", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-cors" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" -dependencies = [ - "actix-utils", - "actix-web", - "derive_more", - "futures-util", - "log", - "once_cell", - "smallvec", -] - -[[package]] -name = "actix-governor" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e7b88f3804e01bd4191fdb08650430bbfcb43d3d9b2890064df3551ec7d25b" -dependencies = [ - "actix-http", - "actix-web", - "futures", - "governor", -] - -[[package]] -name = "actix-http" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash", - "base64 0.22.1", - "bitflags 2.5.0", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http 0.2.12", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn 2.0.66", -] - -[[package]] -name = "actix-router" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" -dependencies = [ - "bytestring", - "cfg-if", - "http 0.2.12", - "regex", - "regex-lite", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.7", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1cf67dadb19d7c95e5a299e2dda24193b89d5d4f33a3b9800888ede9e19aa32" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-utils", - "actix-web-codegen", - "ahash", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "futures-core", - "futures-util", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "regex-lite", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.5.7", - "time", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "actix-web-lab" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7675c1a84eec1b179c844cdea8488e3e409d8e4984026e92fa96c87dd86f33c6" -dependencies = [ - "actix-http", - "actix-router", - "actix-service", - "actix-utils", - "actix-web", - "actix-web-lab-derive", - "ahash", - "arc-swap", - "async-trait", - "bytes", - "bytestring", - "csv", - "derive_more", - "futures-core", - "futures-util", - "http 0.2.12", - "impl-more", - "itertools", - "local-channel", - "mediatype", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "serde", - "serde_html_form", - "serde_json", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "actix-web-lab-derive" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa0b287c8de4a76b691f29dbb5451e8dd5b79d777eaf87350c9b0cbfdb5e968" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "addr2line" version = "0.21.0" @@ -308,21 +50,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -476,27 +203,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "brotli" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bson" version = "2.10.0" @@ -507,7 +213,7 @@ dependencies = [ "base64 0.13.1", "bitvec", "hex", - "indexmap", + "indexmap 2.2.6", "js-sys", "once_cell", "rand", @@ -530,25 +236,11 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -[[package]] -name = "bytestring" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" -dependencies = [ - "bytes", -] - [[package]] name = "cc" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" -dependencies = [ - "jobserver", - "libc", - "once_cell", -] [[package]] name = "cfg-if" @@ -616,17 +308,6 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -642,15 +323,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -685,27 +357,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "csv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] - [[package]] name = "ctr" version = "0.9.2" @@ -785,19 +436,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "data-encoding" version = "2.6.0" @@ -945,15 +583,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - [[package]] name = "enum-as-inner" version = "0.4.0" @@ -1057,16 +686,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" -[[package]] -name = "flate2" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1169,12 +788,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.30" @@ -1214,63 +827,22 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "governor" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" -dependencies = [ - "cfg-if", - "dashmap", - "futures", - "futures-timer", - "no-std-compat", - "nonzero_ext", - "parking_lot", - "portable-atomic", - "quanta", - "rand", - "smallvec", - "spinning_top", -] - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "handlebars" version = "4.5.0" @@ -1299,6 +871,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1374,6 +961,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -1393,7 +991,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body", + "http-body 1.0.0", "pin-project-lite", ] @@ -1419,6 +1017,29 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.3.1" @@ -1429,7 +1050,7 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "httparse", "itoa", "pin-project-lite", @@ -1446,7 +1067,7 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.1.0", - "hyper", + "hyper 1.3.1", "hyper-util", "rustls 0.22.4", "rustls-pki-types", @@ -1465,8 +1086,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -1532,10 +1153,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] -name = "impl-more" -version = "0.1.6" +name = "indexmap" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] [[package]] name = "indexmap" @@ -1544,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", "serde", ] @@ -1563,7 +1188,6 @@ version = "4.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "341ed5f9799ddfee421304898675a41e7eb3c376df246556b44ccb374bf42979" dependencies = [ - "actix-web", "aes", "anyhow", "async-recursion", @@ -1580,7 +1204,7 @@ dependencies = [ "handlebars 4.5.0", "http 1.1.0", "http-serde-ext", - "indexmap", + "indexmap 2.2.6", "js-sandbox-ios", "jsonpath_lib", "moka", @@ -1639,15 +1263,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "jobserver" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" -dependencies = [ - "libc", -] - [[package]] name = "js-sandbox-ios" version = "0.1.0" @@ -1691,21 +1306,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "jsonwebtoken" -version = "9.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" -dependencies = [ - "base64 0.21.7", - "js-sys", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - [[package]] name = "keccak" version = "0.1.5" @@ -1715,12 +1315,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -1745,23 +1339,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "local-channel" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" -dependencies = [ - "futures-core", - "futures-sink", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" - [[package]] name = "lock_api" version = "0.4.12" @@ -1787,6 +1364,15 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "mark-flaky-tests" version = "1.0.2" @@ -1841,18 +1427,67 @@ dependencies = [ "digest", ] -[[package]] -name = "mediatype" -version = "0.19.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd" - [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "metrics" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" +dependencies = [ + "ahash", + "metrics-macros", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d4fa7ce7c4862db464a37b0b31d89bca874562f034bd7993895572783d02950" +dependencies = [ + "base64 0.21.7", + "hyper 0.14.30", + "indexmap 1.9.3", + "ipnet", + "metrics", + "metrics-util", + "quanta 0.11.1", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-macros" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b4faf00617defe497754acde3024865bc143d44a86799b24e191ecff91354f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "metrics-util" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de2ed6e491ed114b40b732e4d1659a9d53992ebd87490c44a6ffe23739d973e" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.13.1", + "metrics", + "num_cpus", + "quanta 0.11.1", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -1875,7 +1510,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.48.0", ] @@ -1895,7 +1529,7 @@ dependencies = [ "futures-util", "once_cell", "parking_lot", - "quanta", + "quanta 0.12.3", "rustc_version 0.4.0", "smallvec", "tagptr", @@ -1951,24 +1585,6 @@ dependencies = [ "webpki-roots 0.25.4", ] -[[package]] -name = "mutually_exclusive_features" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d02c0b00610773bb7fc61d85e13d86c7858cbdf00e1a120bfc41bc055dbaa0e" - -[[package]] -name = "no-std-compat" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" - -[[package]] -name = "nonzero_ext" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2028,10 +1644,6 @@ dependencies = [ name = "oauth-api" version = "0.1.0" dependencies = [ - "actix-cors", - "actix-governor", - "actix-web", - "actix-web-lab", "anyhow", "async-trait", "chrono", @@ -2041,9 +1653,9 @@ dependencies = [ "futures", "handlebars 5.1.2", "integrationos-domain", - "jsonwebtoken", "mark-flaky-tests", - "moka", + "metrics", + "metrics-exporter-prometheus", "mongodb", "once_cell", "rand", @@ -2052,7 +1664,6 @@ dependencies = [ "serde_json", "tokio", "tracing", - "tracing-actix-web", "tracing-bunyan-formatter", "tracing-log 0.2.0", "tracing-subscriber", @@ -2080,7 +1691,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc02deea53ffe807708244e5914f6b099ad7015a207ee24317c22112e17d9c5c" dependencies = [ - "indexmap", + "indexmap 2.2.6", "serde", "serde_json", ] @@ -2126,12 +1737,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pbkdf2" version = "0.11.0" @@ -2141,16 +1746,6 @@ dependencies = [ "digest", ] -[[package]] -name = "pem" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" -dependencies = [ - "base64 0.22.1", - "serde", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -2234,17 +1829,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "powerfmt" @@ -2347,6 +1936,22 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid 10.7.0", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quanta" version = "0.12.3" @@ -2356,7 +1961,7 @@ dependencies = [ "crossbeam-utils", "libc", "once_cell", - "raw-cpuid", + "raw-cpuid 11.0.2", "wasi", "web-sys", "winapi", @@ -2413,6 +2018,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "raw-cpuid" version = "11.0.2" @@ -2487,12 +2101,6 @@ dependencies = [ "regex-syntax 0.8.3", ] -[[package]] -name = "regex-lite" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" - [[package]] name = "regex-syntax" version = "0.6.29" @@ -2516,9 +2124,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.3.1", "hyper-rustls", "hyper-util", "ipnet", @@ -2771,26 +2379,13 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "serde_html_form" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5" -dependencies = [ - "form_urlencoded", - "indexmap", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_json" version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ - "indexmap", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -2856,17 +2451,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha1_smol" version = "1.0.0" @@ -2922,16 +2506,10 @@ dependencies = [ ] [[package]] -name = "simple_asn1" -version = "0.6.2" +name = "sketches-ddsketch" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" [[package]] name = "slab" @@ -2991,15 +2569,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "spinning_top" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" -dependencies = [ - "lock_api", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -3241,17 +2810,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.11" @@ -3278,7 +2836,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -3322,19 +2880,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-actix-web" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa069bd1503dd526ee793bb3fce408895136c95fc86d2edb2acf1c646d7f0684" -dependencies = [ - "actix-web", - "mutually_exclusive_features", - "pin-project", - "tracing", - "uuid", -] - [[package]] name = "tracing-attributes" version = "0.1.27" @@ -3942,31 +3487,3 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" - -[[package]] -name = "zstd" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 0818608..8f7ea0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,6 @@ edition = "2021" lto = "thin" [dependencies] -actix-cors = "0.7.0" -actix-governor = "0.5.0" -actix-web = "4.5.1" -actix-web-lab = "0.20.2" anyhow = "1.0.79" async-trait = "0.1.80" chrono = { version = "0.4.33", features = ["serde"] } @@ -18,12 +14,9 @@ dotenvy = "0.15.7" envconfig = "0.10.0" futures = "0.3.30" handlebars = "5.1.1" -integrationos-domain = { version = "4.1.6", features = [ - "dummy", - "actix-error", -] } -jsonwebtoken = "9.2.0" -moka = { version = "0.12.5", features = ["future"] } +integrationos-domain = { version = "4.1.6", features = ["dummy"] } +metrics = "0.21.1" +metrics-exporter-prometheus = "0.12.1" mongodb = "2.8.0" reqwest = { version = "0.12.3", features = [ "json", @@ -33,7 +26,6 @@ serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.113" tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } tracing = { version = "0.1.40", features = ["log"] } -tracing-actix-web = "0.7.9" tracing-bunyan-formatter = "0.3.9" tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.18", features = [ diff --git a/src/algebra/metrics.rs b/src/algebra/metrics.rs new file mode 100644 index 0000000..8ba0d54 --- /dev/null +++ b/src/algebra/metrics.rs @@ -0,0 +1,42 @@ +use anyhow::anyhow; +use metrics_exporter_prometheus::PrometheusBuilder; + +pub const SUCCESSFULLY_REFRESHED_GAUGE: &str = "successfully_refreshed"; +pub const FAILED_TO_REFRESH_GAUGE: &str = "failed_to_refresh"; +pub const REFRESH_TOTAL: &str = "refresh_total"; + +#[derive(Clone, Debug)] +pub struct Metrics; + +impl Metrics { + pub fn new() -> anyhow::Result { + PrometheusBuilder::new().install().map_err(|e| { + tracing::error!("Failed to install prometheus exporter: {}", e); + anyhow!(e) + })?; + + metrics::describe_gauge!( + SUCCESSFULLY_REFRESHED_GAUGE, + "The number of successfully refreshed connections" + ); + + metrics::describe_gauge!( + FAILED_TO_REFRESH_GAUGE, + "The number of failed to refresh connections" + ); + + metrics::describe_gauge!(REFRESH_TOTAL, "The total number of refreshes"); + + Ok(Self) + } + + pub fn add_refreshed(&self, value: u64) { + metrics::increment_gauge!(SUCCESSFULLY_REFRESHED_GAUGE, value as f64); + metrics::increment_gauge!(REFRESH_TOTAL, value as f64); + } + + pub fn add_failed_to_refresh(&self, value: u64) { + metrics::increment_gauge!(FAILED_TO_REFRESH_GAUGE, value as f64); + metrics::increment_gauge!(REFRESH_TOTAL, value as f64); + } +} diff --git a/src/algebra/mod.rs b/src/algebra/mod.rs index a6f8fab..5a2193f 100644 --- a/src/algebra/mod.rs +++ b/src/algebra/mod.rs @@ -1,9 +1,9 @@ +mod metrics; mod parameter; mod refresh; mod storage; -mod token; +pub use metrics::*; pub use parameter::*; pub use refresh::*; pub use storage::*; -pub use token::*; diff --git a/src/algebra/refresh.rs b/src/algebra/refresh.rs index 7098c8b..ef0ea19 100644 --- a/src/algebra/refresh.rs +++ b/src/algebra/refresh.rs @@ -1,7 +1,7 @@ use crate::{ algebra::StorageExt, domain::{Refresh, Trigger, Unit}, - ParameterExt, Refreshed, + Metrics, ParameterExt, Refreshed, }; use chrono::{Duration, Utc}; use integrationos_domain::{ @@ -39,6 +39,7 @@ pub async fn refresh( secrets: Arc, oauths: Arc>, client: Client, + metrics: Arc, ) -> Result { let refresh_before = Utc::now(); let refresh_after = refresh_before + Duration::minutes(msg.refresh_before_in_minutes()); @@ -73,18 +74,22 @@ pub async fn refresh( requests.push(result); } - match futures::future::join_all(requests) - .await - .into_iter() - .collect::, _>>() - { - Ok(vec) => { - tracing::info!("Refreshed {} connections: {:?}", vec.len(), vec); + let results = futures::future::join_all(requests).await; - Ok(()) - } - Err(err) => Err(InternalError::io_err(err.to_string().as_str(), None)), - } + let (successes, failures): (Vec<_>, Vec<_>) = + results.into_iter().partition(|result| result.is_ok()); + + tracing::info!("Refreshed {} connections: {:?}", successes.len(), successes); + tracing::info!( + "Failed to refresh {} connections: {:?}", + failures.len(), + failures + ); + + metrics.add_refreshed(successes.len() as u64); + metrics.add_failed_to_refresh(failures.len() as u64); + + Ok(()) } pub async fn trigger( diff --git a/src/algebra/token.rs b/src/algebra/token.rs deleted file mode 100644 index 45e3309..0000000 --- a/src/algebra/token.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::service::Configuration; -use chrono::{Duration, Utc}; -use integrationos_domain::{Claims, IntegrationOSError as Error, InternalError}; -use jsonwebtoken::{encode, EncodingKey, Header}; - -pub trait TokenExt { - fn generate(&self, configuration: Configuration, expiration: i64) -> Result; -} - -#[derive(Debug, Default)] -pub struct Token; - -impl TokenExt for Token { - fn generate(&self, configuration: Configuration, expiration: i64) -> Result { - let key = configuration.server().admin_secret(); - let key = key.as_bytes(); - let key = EncodingKey::from_secret(key); - let now = Utc::now(); - let iat = now.timestamp(); - let exp = (now + Duration::days(expiration)).timestamp(); - let header = Header::default(); - - let claims = Claims { - id: "ADMIN".into(), - email: "admin@integrationos.com".into(), - username: "admin".into(), - user_key: "admin".into(), - first_name: "admin".into(), - last_name: "admin".into(), - buildable_id: "".into(), - container_id: "".into(), - pointers: vec![], - is_buildable_core: false, - iat, - exp, - aud: "integration-team".into(), - iss: "oauth-integrationos".into(), - }; - - let token = encode(&header, &claims, &key) - .map_err(|e| InternalError::encryption_error(e.to_string().as_str(), None))?; - - Ok(token) - } -} diff --git a/src/lib.rs b/src/lib.rs index a1137b0..dd3120f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,130 +5,3 @@ mod service; pub use algebra::*; pub use domain::*; pub use service::*; - -use actix_cors::Cors; -use actix_governor::{Governor, GovernorConfigBuilder}; -use actix_web::{ - dev::Server, - web::{scope, Data}, - App, HttpServer, -}; -use actix_web_lab::middleware::from_fn; -use anyhow::Context; -use std::{net::TcpListener, time::Duration}; - -pub const PREFIX: &str = "/v1"; -pub const INTEGRATION_PREFIX: &str = "/integration"; - -pub struct Application { - port: u16, - server: Server, -} - -impl Application { - pub async fn start(configuration: &Configuration) -> Result { - tracing::info!( - "Starting application with configuration: {}{:#?}{}", - "\n", - &configuration, - "\n" - ); - let address = format!( - "{}:{}", - configuration.server().host(), - configuration.server().port() - ); - let listener = TcpListener::bind(&address)?; - let port = listener.local_addr()?.port(); - let state = AppState::try_from(configuration.clone()).await?; - - let sleep_timer = Duration::from_secs(configuration.oauth().sleep_timer()); - let refresh_before = configuration.oauth().refresh_before(); - - let connections = state.connections().clone(); - let oauths = state.oauths().clone(); - let secrets = state.secrets().clone(); - let client = state.client().clone(); - - tokio::spawn(async move { - loop { - let res = refresh( - Refresh::new(refresh_before), - connections.clone(), - secrets.clone(), - oauths.clone(), - client.clone(), - ) - .await; - if let Err(e) = res { - tracing::warn!("Failed to send refresh message: {:?}", e); - } - - tracing::info!("Sleeping for {} seconds", sleep_timer.as_secs()); - tokio::time::sleep(sleep_timer).await; - } - }); - - let server = run(listener, configuration.clone(), state).await?; - - Ok(Self { port, server }) - } - - pub fn port(&self) -> u16 { - self.port - } - - pub fn handler(self) -> Server { - self.server - } - - pub async fn spawn(self) -> Result<(), anyhow::Error> { - let server = self.handler(); - let http = tokio::spawn(server); - - tokio::select! { - res = http => { - res.context("Failed to spawn http application.")?.context("Failed to spawn http application.") - }, - } - } -} - -async fn run( - listener: TcpListener, - configuration: Configuration, - state: AppState, -) -> Result { - let governor = GovernorConfigBuilder::default() - .per_second(configuration.server().burst_rate_limit()) - .permissive(configuration.server().is_development()) - .burst_size(configuration.server().burst_size_limit()) - .finish() - .context("Failed to create governor.")?; - - let server = HttpServer::new(move || { - let trace: Tracer = Tracer::default(); - App::new() - .wrap(trace.tracer()) - .wrap( - Cors::default() - .allowed_methods(vec!["GET", "POST"]) - .allow_any_origin() - .allow_any_header() - .supports_credentials() - .max_age(3600), - ) - .wrap(Governor::new(&governor)) - .service( - scope(&(PREFIX.to_owned() + INTEGRATION_PREFIX)) // /v1/integration - .wrap(from_fn(auth_middleware)) - .service(trigger_refresh), - ) - .service(scope(PREFIX).service(health_check)) // /v1 - .app_data(Data::new(state.clone())) - }) - .listen(listener)? - .run(); - - Ok(server) -} diff --git a/src/main.rs b/src/main.rs index d8b7f81..ff653b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,44 @@ use dotenvy::dotenv; use envconfig::Envconfig; use integrationos_domain::telemetry::{get_subscriber, init_subscriber}; -use oauth_api::{Application, Configuration}; +use oauth_api::{refresh, AppState, Refresh, RefreshConfig}; +use std::time::Duration; -#[actix_web::main] +#[tokio::main] async fn main() -> anyhow::Result<()> { dotenv().ok(); let suscriber = get_subscriber("oauth-api".into(), "info".into(), std::io::stdout); init_subscriber(suscriber); - let configuration = Configuration::init_from_env()?; + let configuration = RefreshConfig::init_from_env()?; - let address = configuration.server().app_url().to_string(); - let application = Application::start(&configuration).await?; + tracing::info!( + "Starting application with configuration: {}{:#?}{}", + "\n", + &configuration, + "\n" + ); + let state = AppState::try_from(configuration.clone()).await?; - tracing::info!("Starting server at {}", &address); - application.spawn().await?; + let sleep_timer = Duration::from_secs(configuration.sleep_timer()); + let refresh_before = configuration.refresh_before(); - Ok(()) + loop { + let res = refresh( + Refresh::new(refresh_before), + state.connections().clone(), + state.secrets().clone(), + state.oauths().clone(), + state.client().clone(), + state.metrics().clone(), + ) + .await; + if let Err(e) = res { + tracing::warn!("Failed to send refresh message: {:?}", e); + } + + tracing::info!("Sleeping for {} seconds", sleep_timer.as_secs()); + tokio::time::sleep(sleep_timer).await; + } } diff --git a/src/service/configuration/mod.rs b/src/service/configuration/mod.rs index 76c5d73..50b00eb 100644 --- a/src/service/configuration/mod.rs +++ b/src/service/configuration/mod.rs @@ -1,7 +1,3 @@ -mod telemetry; - -pub use telemetry::*; - use envconfig::Envconfig; use integrationos_domain::{ database::DatabaseConfig, environment::Environment, secrets::SecretsConfig, @@ -10,7 +6,7 @@ use std::collections::HashMap; use std::fmt::Debug; #[derive(Clone, Envconfig)] -pub struct OAuthConfig { +pub struct RefreshConfig { #[envconfig(from = "REFRESH_BEFORE_IN_MINUTES", default = "10")] refresh_before: i64, #[envconfig(from = "SLEEP_TIMER_IN_SECONDS", default = "20")] @@ -19,18 +15,24 @@ pub struct OAuthConfig { database: DatabaseConfig, #[envconfig(nested = true)] secrets_config: SecretsConfig, + #[envconfig(from = "TIMEOUT", default = "30")] + timeout: u64, + #[envconfig(from = "ENVIRONMENT", default = "test")] + environment: Environment, } -impl Debug for OAuthConfig { +impl Debug for RefreshConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("OAuthConfig") - .field("refresh_before", &self.refresh_before) - .field("sleep_timer", &self.sleep_timer) - .finish() + writeln!(f, "REFRESH_BEFORE_IN_MINUTES: {}", self.refresh_before)?; + writeln!(f, "SLEEP_TIMER_IN_SECONDS: {}", self.sleep_timer)?; + writeln!(f, "TIMEOUT: {}", self.timeout)?; + writeln!(f, "ENVIRONMENT: {}", self.environment)?; + write!(f, "{}", self.database)?; + write!(f, "{}", self.secrets_config) } } -impl OAuthConfig { +impl RefreshConfig { pub fn refresh_before(&self) -> i64 { self.refresh_before } @@ -46,148 +48,17 @@ impl OAuthConfig { pub fn secrets_config(&self) -> &SecretsConfig { &self.secrets_config } -} - -#[derive(Clone, Envconfig, Debug)] -pub struct ServerConfig { - #[envconfig(from = "ENVIRONMENT", default = "test")] - /// The environment for the server - environment: Environment, - #[envconfig(from = "HOST", default = "localhost")] - /// The host for the server - host: String, - #[envconfig(from = "PORT", default = "3007")] - /// The port for the server - port: u16, - #[envconfig(from = "APP_URL", default = "http://localhost:3007")] - /// The URL for the server - app_url: String, - #[envconfig( - from = "JWT_SECRET", - default = "2thZ2UiOnsibmFtZSI6IlN0YXJ0dXBsa3NoamRma3NqZGhma3NqZGhma3NqZG5jhYtggfaP9ubmVjdGlvbnMiOjUwMDAwMCwibW9kdWxlcyI6NSwiZW5kcG9pbnRzIjo3b4e05e2-f050-401f-9822-44f43f71753c" - )] - /// The secret for the JWT - jwt_secret: String, - #[envconfig(from = "TIMEOUT", default = "30000")] - timeout: u64, - #[envconfig( - from = "SECRET_ADMIN", - default = "my_admin_secret_super_extra_secure_key_to_verify_admin_sessions_this_one_must_be_at_least_51_characters" - )] - admin_secret: String, - /// Burst rate limit - #[envconfig(from = "BURST_RATE_LIMIT", default = "10")] - burst_rate_limit: u64, - /// Burst size limit - #[envconfig(from = "BURST_SIZE_LIMIT", default = "15")] - burst_size_limit: u32, - #[envconfig(from = "HEADER_AUTH", default = "x-integrationos-secret")] - pub auth_header: String, - #[envconfig(from = "HEADER_ADMIN", default = "x-integrationos-admin-token")] - pub admin_header: String, - #[envconfig(from = "CACHE_SIZE", default = "10000")] - pub cache_size: u64, -} - -impl ServerConfig { - pub fn host(&self) -> &str { - &self.host - } - - pub fn environment(&self) -> &Environment { - &self.environment - } - - pub fn port(&self) -> u16 { - self.port - } - - pub fn app_url(&self) -> &str { - &self.app_url - } - - pub fn is_development(&self) -> bool { - self.environment == Environment::Development || self.environment == Environment::Test - } - - pub fn jwt_secret(&self) -> &str { - &self.jwt_secret - } pub fn timeout(&self) -> u64 { self.timeout } - pub fn cache_size(&self) -> u64 { - self.cache_size - } - - pub fn burst_rate_limit(&self) -> u64 { - self.burst_rate_limit - } - - pub fn auth_header(&self) -> &str { - &self.auth_header - } - - pub fn burst_size_limit(&self) -> u32 { - self.burst_size_limit - } - - pub fn admin_header(&self) -> &str { - &self.admin_header - } - - pub fn admin_secret(&self) -> &str { - &self.admin_secret - } -} - -#[derive(Clone, Envconfig)] -pub struct Configuration { - #[envconfig(nested = true)] - oauth: OAuthConfig, - #[envconfig(nested = true)] - server: ServerConfig, -} - -impl Debug for Configuration { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let _ = f - .debug_struct("OAuthConfig") - .field("refresh_before", &self.oauth.refresh_before) - .field("sleep_timer", &self.oauth.sleep_timer) - .finish(); - - writeln!(f)?; - - f.debug_struct("ServerConfig") - .field("environment", &self.server.environment) - .field("host", &self.server.host) - .field("port", &self.server.port) - .field("jwt_secret", &"[REDACTED]") - .field("admin_secret", &"[REDACTED]") - .field("app_url", &self.server.app_url) - .field("timeout", &self.server.timeout) - .field("burst_rate_limit", &self.server.burst_rate_limit) - .field("burst_size_limit", &self.server.burst_size_limit) - .field("auth_header", &self.server.auth_header) - .field("cache_size", &self.server.cache_size) - .finish() - } -} - -impl Configuration { - pub fn oauth(&self) -> &OAuthConfig { - &self.oauth - } - - pub fn server(&self) -> &ServerConfig { - &self.server + pub fn environment(&self) -> Environment { + self.environment } } -impl From> for OAuthConfig { +impl From> for RefreshConfig { fn from(value: HashMap<&str, &str>) -> Self { let refresh_before = value .get("REFRESH_BEFORE_IN_MINUTES") @@ -205,89 +76,23 @@ impl From> for OAuthConfig { .collect(); let database = DatabaseConfig::init_from_hashmap(&owned).unwrap_or_default(); let secrets_config = SecretsConfig::init_from_hashmap(&owned).unwrap_or_default(); - - Self { - refresh_before, - sleep_timer, - database, - secrets_config, - } - } -} - -impl From> for ServerConfig { - fn from(value: HashMap<&str, &str>) -> Self { - let environment = value - .get("ENVIRONMENT") - .and_then(|value| value.parse().ok()) - .unwrap_or("test".parse().unwrap()); - let host = value.get("HOST").unwrap_or(&"localhost").to_string(); - let port = value - .get("PORT") - .and_then(|value| value.parse().ok()) - .unwrap_or(3008); - let app_url = value - .get("APP_URL") - .unwrap_or(&"http://localhost:3008") - .to_string(); - let jwt_secret = value - .get("JWT_SECRET") - .unwrap_or( - &"2thZ2UiOnsibmFtZSI6IlN0YXJ0dXBsa3NoamRma3NqZGhma3NqZGhma3NqZG5jhYtggfaP9ubmVjdGlvbnMiOjUwMDAwMCwibW9kdWxlcyI6NSwiZW5kcG9pbnRzIjo3b4e05e2-f050-401f-9822-44f43f71753c" - ) - .to_string(); let timeout = value .get("TIMEOUT") .and_then(|value| value.parse().ok()) - .unwrap_or(30000); - let burst_rate_limit = value - .get("BURST_RATE_LIMIT") - .and_then(|value| value.parse().ok()) - .unwrap_or(10); - let burst_size_limit = value - .get("BURST_SIZE_LIMIT") - .and_then(|value| value.parse().ok()) - .unwrap_or(15); - let auth_header = value - .get("HEADER_AUTH") - .unwrap_or(&"x-integrationos-secret") - .to_string(); - let cache_size = value - .get("CACHE_SIZE") - .and_then(|value| value.parse().ok()) - .unwrap_or(10000); - let secret_admin = value - .get("SECRET_ADMIN") - .unwrap_or( - &"my_admin_secret_super_extra_secure_key_to_verify_admin_sessions_this_one_must_be_at_least_51_characters" - ) - .to_string(); - let admin_header = value - .get("HEADER_ADMIN") - .unwrap_or(&"x-integrationos-admin-token") - .to_string(); + .unwrap_or(30); + let environment = value + .get("ENVIRONMENT") + .unwrap_or(&"test") + .parse() + .expect("Failed to parse environment"); Self { + refresh_before, environment, - host, - port, - admin_header, - app_url, - jwt_secret, + sleep_timer, timeout, - burst_rate_limit, - admin_secret: secret_admin, - burst_size_limit, - auth_header, - cache_size, + database, + secrets_config, } } } - -impl From> for Configuration { - fn from(value: HashMap<&str, &str>) -> Self { - let oauth = OAuthConfig::from(value.clone()); - let server = ServerConfig::from(value); - Self { oauth, server } - } -} diff --git a/src/service/configuration/mod.rs-E b/src/service/configuration/mod.rs-E new file mode 100644 index 0000000..dc765fb --- /dev/null +++ b/src/service/configuration/mod.rs-E @@ -0,0 +1,98 @@ +use envconfig::Envconfig; +use integrationos_domain::{ + database::DatabaseConfig, environment::Environment, secrets::SecretsConfig, +}; +use std::collections::HashMap; +use std::fmt::Debug; + +#[derive(Clone, Envconfig)] +pub struct RefreshConfig { + #[envconfig(from = "REFRESH_BEFORE_IN_MINUTES", default = "10")] + refresh_before: i64, + #[envconfig(from = "SLEEP_TIMER_IN_SECONDS", default = "20")] + sleep_timer: u64, + #[envconfig(nested = true)] + database: DatabaseConfig, + #[envconfig(nested = true)] + secrets_config: SecretsConfig, + #[envconfig(from = "TIMEOUT", default = "30")] + timeout: u64, + #[envconfig(from = "ENVIRONMENT", default = "test")] + environment: Environment, +} + +impl Debug for RefreshConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OAuthConfig") + .field("refresh_before", &self.refresh_before) + .field("sleep_timer", &self.sleep_timer) + .field("timeout", &self.timeout) + .field("environment", &self.environment) + .finish() + } +} + +impl RefreshConfig { + pub fn refresh_before(&self) -> i64 { + self.refresh_before + } + + pub fn sleep_timer(&self) -> u64 { + self.sleep_timer + } + + pub fn database(&self) -> &DatabaseConfig { + &self.database + } + + pub fn secrets_config(&self) -> &SecretsConfig { + &self.secrets_config + } + + pub fn timeout(&self) -> u64 { + self.timeout + } + + pub fn environment(&self) -> Environment { + self.environment + } +} + +impl From> for RefreshConfig { + fn from(value: HashMap<&str, &str>) -> Self { + let refresh_before = value + .get("REFRESH_BEFORE_IN_MINUTES") + .and_then(|value| value.parse().ok()) + .unwrap_or(10); + + let sleep_timer = value + .get("SLEEP_TIMER_IN_SECONDS") + .and_then(|value| value.parse().ok()) + .unwrap_or(20); + + let owned = value + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + let database = DatabaseConfig::init_from_hashmap(&owned).unwrap_or_default(); + let secrets_config = SecretsConfig::init_from_hashmap(&owned).unwrap_or_default(); + let timeout = value + .get("TIMEOUT") + .and_then(|value| value.parse().ok()) + .unwrap_or(30); + let environment = value + .get("ENVIRONMENT") + .unwrap_or(&"test") + .parse() + .expect("Failed to parse environment"); + + Self { + refresh_before, + environment, + sleep_timer, + timeout, + database, + secrets_config, + } + } +} diff --git a/src/service/configuration/telemetry.rs b/src/service/configuration/telemetry.rs deleted file mode 100644 index ffb8e19..0000000 --- a/src/service/configuration/telemetry.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::PREFIX; -use actix_web::body::MessageBody; -use actix_web::dev::{ServiceRequest, ServiceResponse}; -use tracing::subscriber::set_global_default; -use tracing::Level; -use tracing::Span; -use tracing_actix_web::{DefaultRootSpanBuilder, RootSpanBuilder, TracingLogger}; -use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; -use tracing_log::LogTracer; -use tracing_subscriber::fmt::MakeWriter; -use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; - -pub struct Telemetry -where - T: SubscriberExt + Send + Sync + 'static, -{ - pub subscriber: T, -} - -/// Compose multiple layers into a `tracing`'s subscriber. -/// -/// # Implementation Notes -/// -/// We are using `impl Subscriber` as return type to avoid having to spell out the actual -/// type of the returned subscriber, which is indeed quite complex. -pub fn get_subscriber( - name: String, - env_filter: String, - sink: Sink, -) -> Telemetry -where - Sink: for<'a> MakeWriter<'a> + Send + Sync + 'static, -{ - // JSON formatting layer - let formatting_layer: BunyanFormattingLayer = BunyanFormattingLayer::new( - name, // Output the JSON logs to the stdout. - sink, - ); - - // Filter Layer - let filter_layer = - EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter)); - - Telemetry { - subscriber: Registry::default() - .with(filter_layer) - .with(JsonStorageLayer) - .with(formatting_layer), - } -} - -/// Register a subscriber as global default to process span data. -/// -/// It should only be called once! -pub fn init_subscriber(subscriber: Telemetry) { - LogTracer::init().expect("Failed to set logger"); - set_global_default(subscriber.subscriber).expect("Failed to set subscriber"); -} - -pub struct SpanBuilder; - -impl RootSpanBuilder for SpanBuilder { - fn on_request_start(request: &ServiceRequest) -> Span { - let level = if request.path() == PREFIX.to_owned() + "/health_check" - || request.path() == PREFIX.to_owned() + "/metrics" - { - Level::TRACE - } else { - Level::INFO - }; - tracing_actix_web::root_span!(level = level, request) - } - - fn on_request_end( - span: Span, - outcome: &Result, actix_web::Error>, - ) { - DefaultRootSpanBuilder::on_request_end(span, outcome); - } -} - -pub struct Tracer { - tracer: TracingLogger, -} - -impl Tracer { - pub fn new() -> Self { - let tracer = TracingLogger::::new(); - - Tracer { tracer } - } - - pub fn tracer(&self) -> TracingLogger { - self.tracer.clone() - } -} - -impl Default for Tracer { - fn default() -> Self { - Self::new() - } -} diff --git a/src/service/http/mod.rs b/src/service/http/mod.rs deleted file mode 100644 index dada69c..0000000 --- a/src/service/http/mod.rs +++ /dev/null @@ -1,144 +0,0 @@ -mod private; -mod public; - -pub use private::*; -pub use public::*; - -use super::AppState; -use actix_web::{ - body::MessageBody, - dev::{ServiceRequest, ServiceResponse}, - error::ErrorUnauthorized, - web::Data, - Error as ActixWebError, HttpMessage, -}; -use actix_web::{http::StatusCode, HttpResponse, HttpResponseBuilder}; -use actix_web_lab::middleware::Next; -use integrationos_domain::{event_access::EventAccess, Claims}; -use jsonwebtoken::{decode, DecodingKey, Validation}; -use mongodb::bson::doc; -use std::sync::Arc; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub enum ResponseType { - Health, - Trigger, - Query, - Error, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct ServerResponse -where - T: serde::Serialize, -{ - #[serde(rename = "type")] - pub r#type: ResponseType, - pub args: T, - pub code: u16, -} - -impl ServerResponse -where - T: serde::Serialize, -{ - pub fn from(r#type: ResponseType, args: T, code: u16) -> HttpResponse { - HttpResponseBuilder::new(StatusCode::from_u16(code).unwrap_or(StatusCode::OK)) - .json(ServerResponse { r#type, args, code }) - } -} - -pub async fn auth_middleware( - req: ServiceRequest, - next: Next, -) -> Result, ActixWebError> { - let state = req.app_data::>(); - let state = match state { - None => return Err(ErrorUnauthorized("No state found")), - Some(state) => state, - }; - - let event_access = extract_event_info(&req, state).await; - let claims = claims(&req, state); - - match (event_access, claims) { - (Ok(event_access), Ok(claims)) => { - req.extensions_mut().insert(claims.to_owned()); - req.extensions_mut().insert(event_access.to_owned()); - next.call(req).await - } - (Err(err), _) | (_, Err(err)) => Err(ErrorUnauthorized(err)), - } -} - -fn claims(req: &ServiceRequest, state: &Data) -> Result { - let token = req - .headers() - .get(state.configuration().server().admin_header()) - .and_then(|header| header.to_str().ok()) - .map(|h| h.to_string().split_at(7).1.to_string()); - - let token = match token { - Some(token) => token, - None => return Err(ErrorUnauthorized("No token found")), - }; - - let mut validator = Validation::default(); - validator.set_audience(&["integration-team", "oauth-integrationos"]); - - let claims = decode::( - &token, - &DecodingKey::from_secret(state.configuration().server().admin_secret().as_ref()), - &validator, - ) - .map_err(|_| ErrorUnauthorized("Invalid token"))?; - - Ok(claims.claims) -} - -async fn extract_event_info( - req: &ServiceRequest, - state: &Data, -) -> Result, ActixWebError> { - let Some(auth_header) = req - .headers() - .get(state.configuration().server().auth_header()) - else { - Err(ErrorUnauthorized("No auth header found"))? - }; - - let event_access = state - .cache() - .try_get_with_by_ref(auth_header, async { - let key = auth_header - .to_str() - .map_err(|e| format!("Invalid auth header: {}", e))?; - - if let Some(event_access) = state - .event_access() - .get_one(doc! { - "accessKey": key, - "deleted": false - }) - .await - .map_err(|e| { - tracing::warn!("{}", e); - format!("{}", e) - })? - { - Ok(Arc::new(event_access)) - } else { - Err(format!("No event access found for key: {}", key)) - } - }) - .await; - - let event_access: Arc = match event_access { - Ok(event_access) => event_access, - Err(err) => Err(ErrorUnauthorized(err))?, - }; - - Ok(event_access) -} diff --git a/src/service/http/private/mod.rs b/src/service/http/private/mod.rs deleted file mode 100644 index e863fd1..0000000 --- a/src/service/http/private/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod trigger; - -pub use trigger::*; diff --git a/src/service/http/private/trigger.rs b/src/service/http/private/trigger.rs deleted file mode 100644 index bc00028..0000000 --- a/src/service/http/private/trigger.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{ - algebra::StorageExt, - domain::Trigger, - service::{AppState, ResponseType, ServerResponse}, - trigger, -}; -use actix_web::{ - post, - web::{Data, Path}, - HttpResponse, -}; -use integrationos_domain::{ - error::IntegrationOSError as Error, ApplicationError, Id, InternalError, -}; -use reqwest::StatusCode; -use serde_json::json; - -#[tracing::instrument(name = "Trigger refresh", skip(state))] -#[post("/trigger/{id}")] -pub async fn trigger_refresh(state: Data, id: Path) -> Result { - let id = id.into_inner(); - let connection = state - .connections() - .get(id) - .await? - .ok_or(ApplicationError::not_found( - format!("Connection with id {} not found", id).as_str(), - None, - ))?; - - tracing::info!("Triggering refresh for connection {}", connection.id); - - let outcome = trigger( - Trigger::new(connection), - state.secrets().clone(), - state.connections().clone(), - state.oauths().clone(), - state.client().clone(), - ) - .await - .map_err(|e| InternalError::io_err(e.to_string().as_str(), None))?; - - let json = json!({ - "id": id, - "outcome": outcome, - }); - - Ok(ServerResponse::from( - ResponseType::Trigger, - json, - StatusCode::OK.as_u16(), - )) -} diff --git a/src/service/http/public/health.rs b/src/service/http/public/health.rs deleted file mode 100644 index 4797970..0000000 --- a/src/service/http/public/health.rs +++ /dev/null @@ -1,8 +0,0 @@ -use actix_web::{get, HttpResponse}; - -use crate::service::{ResponseType, ServerResponse}; - -#[get("/health_check")] -pub async fn health_check() -> HttpResponse { - ServerResponse::from(ResponseType::Health, "I'm alive!".to_string(), 200) -} diff --git a/src/service/http/public/mod.rs b/src/service/http/public/mod.rs deleted file mode 100644 index 96b5207..0000000 --- a/src/service/http/public/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod health; - -pub use health::*; diff --git a/src/service/mod.rs b/src/service/mod.rs index 8920940..cef3d27 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,93 +1,88 @@ mod configuration; -mod http; pub use configuration::*; -pub use http::*; -use actix_web::http::header::HeaderValue; use integrationos_domain::{ algebra::MongoStore, client::secrets_client::SecretsClient, connection_oauth_definition::ConnectionOAuthDefinition, error::IntegrationOSError as Error, event_access::EventAccess, Connection, InternalError, Store, }; -use moka::future::Cache; use mongodb::options::FindOptions; use reqwest::Client; +use serde_json::Value; use std::{sync::Arc, time::Duration}; use tokio::time::timeout; +use crate::Metrics; + #[derive(Clone, Debug)] pub struct AppState { - configuration: Configuration, - cache: Cache>, client: Client, secrets: Arc, connections: Arc>, oauths: Arc>, event_access: Arc>, + metrics: Arc, } impl AppState { - pub async fn try_from(config: Configuration) -> Result { + pub async fn try_from(config: RefreshConfig) -> Result { let client = Client::builder() - .timeout(Duration::from_millis(config.server().timeout())) + .timeout(Duration::from_millis(config.timeout())) .build() .map_err(|e| InternalError::io_err(e.to_string().as_str(), None))?; - let mongo_client = mongodb::Client::with_uri_str(&config.oauth().database().control_db_url) + let mongo_client = mongodb::Client::with_uri_str(&config.database().control_db_url) .await .map_err(|e| InternalError::io_err(e.to_string().as_str(), None))?; - timeout(Duration::from_millis(config.server().timeout()), async { + timeout(Duration::from_secs(config.timeout()), async { mongo_client - .database("admin") - .collection::("system.users") + .database(&config.database().event_db_name) + .collection::("system-stats") .find( None, FindOptions::builder() .limit(1) - .max_time(Duration::from_secs(1)) - .max_await_time(Duration::from_secs(1)) + .max_time(Duration::from_secs(config.timeout())) + .max_await_time(Duration::from_secs(config.timeout())) .build(), ) .await + .map_err(|e| { + tracing::warn!("Failed to connect to MongoDB within {} seconds. Please check your connection string. {:?}", config.timeout(), e); + e + }) }) .await - .expect( - "Failed to connect to MongoDB within 5 seconds. Please check your connection string.", - ) + .unwrap_or_else(|_| panic!("Failed to connect to MongoDB within {} seconds. Please check your connection string.", config.timeout())) .ok(); - let database = mongo_client.database(config.oauth().database().control_db_name.as_ref()); - let secrets = SecretsClient::new(config.oauth().secrets_config())?; + let database = mongo_client.database(config.database().control_db_name.as_ref()); + let secrets = SecretsClient::new(config.secrets_config())?; let oauths = MongoStore::::new( &database, &Store::ConnectionOAuthDefinitions, ) .await?; let connections = MongoStore::::new(&database, &Store::Connections).await?; - let cache = Cache::new(config.server().cache_size()); let event_access = MongoStore::::new(&database, &Store::EventAccess).await?; let oauths = Arc::new(oauths); let connections = Arc::new(connections); let secrets = Arc::new(secrets); let event_access = Arc::new(event_access); + let metrics = Arc::new(Metrics::new()?); Ok(AppState { - configuration: config, - cache, event_access, connections, + metrics, client, oauths, secrets, }) } - pub fn configuration(&self) -> &Configuration { - &self.configuration - } - pub fn client(&self) -> &Client { &self.client } @@ -96,10 +91,6 @@ impl AppState { &self.connections } - pub fn cache(&self) -> &Cache> { - &self.cache - } - pub fn oauths(&self) -> &Arc> { &self.oauths } @@ -111,4 +102,8 @@ impl AppState { pub fn secrets(&self) -> &Arc { &self.secrets } + + pub fn metrics(&self) -> &Arc { + &self.metrics + } } diff --git a/tests/http/health.rs b/tests/http/health.rs deleted file mode 100644 index eff530e..0000000 --- a/tests/http/health.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::suite::TestApp; -use std::collections::HashMap; - -#[actix_web::test] -async fn health_check_works() { - // Arrange - let application = TestApp::spawn(HashMap::new()).await; - // Act - let response = application.get("health_check").await; - // Assert - assert!(response.status().is_success()); - assert_eq!(Some(48), response.content_length()); -} diff --git a/tests/http/mod.rs b/tests/http/mod.rs deleted file mode 100644 index e0d7220..0000000 --- a/tests/http/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod health; -mod trigger; diff --git a/tests/http/trigger.rs b/tests/http/trigger.rs deleted file mode 100644 index d755f11..0000000 --- a/tests/http/trigger.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::suite::TestApp; -use integrationos_domain::{prefix::IdPrefix, Id}; -use mark_flaky_tests::flaky; -use oauth_api::{Token, TokenExt}; -use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; -use std::collections::HashMap; -use uuid::Uuid; - -#[actix_web::test] -async fn returns_401_for_missing_headers() { - // Arrange - let application = TestApp::spawn(HashMap::new()).await; - // Act - let path = format!("integration/trigger/{}", Uuid::nil()); - let response = application.post(path, "", None).await; - // Assert - assert_eq!(401, response.status().as_u16()); -} - -#[actix_web::test] -async fn returns_404_for_invalid_prefix_id() { - // Arrange - let application = TestApp::spawn(HashMap::new()).await; - let event_access = application.insert_event_access().await; - let event_access_token = event_access.access_key; - let token = Token - .generate(application.configuration().clone(), 1) - .expect("Failed to generate token"); - - let headers = HeaderMap::from_iter(vec![ - ( - HeaderName::from_static("x-integrationos-secret"), - HeaderValue::from_str(&event_access_token).expect("Failed to create header value"), - ), - ( - HeaderName::from_static("x-integrationos-admin-token"), - HeaderValue::from_str(&format!("Bearer {}", token)) - .expect("Failed to create header value"), - ), - ]); - let path = format!("integration/trigger/{}", Uuid::nil()); - // Act - let response = application.post(path, "", Some(headers)).await; - // Assert - assert_eq!( - "text/plain; charset=utf-8", - response - .headers() - .get("content-type") - .expect("Failed to get content type") - .to_str() - .expect("Failed to convert content type to string") - ); - assert_eq!( - "Argument provided is invalid: Invalid ID prefix: 00000000-0000-0000-0000-000000000000", - response.text().await.expect("Failed to get response text") - ); -} - -#[actix_web::test] -async fn returns_401_for_non_existent_event_access() { - // Arrange - let application = TestApp::spawn(HashMap::new()).await; - let event_access = "sk_live_1_Gom7umYOtRPyCbx4o2XNIlM32-2wf2dPI6s7nsdlWeXuhRj1rgDEvFeYAVckQvwG-5IUzRHGWnloNx2fci7IdFcdlTqYAuUuj6QQZPOvS2sxGK4YKnkmS1UFqcXFDCsSYZxASBaqJaBZA1HMEVuv61-cepuCBJccX90hXqQlKZvZ5s0i8hRZszeCA9b3H18paLy7"; - let token = Token - .generate(application.configuration().clone(), 1) - .expect("Failed to generate token"); - let headers = HeaderMap::from_iter(vec![ - ( - HeaderName::from_static("x-integrationos-secret"), - HeaderValue::from_str(event_access).expect("Failed to create header value"), - ), - ( - HeaderName::from_static("x-integrationos-admin-token"), - HeaderValue::from_str(&format!("Bearer {}", token)) - .expect("Failed to create header value"), - ), - ]); - let id = Id::now(IdPrefix::ConnectionModelDefinition); - let path = format!("integration/trigger/{}", id); - // Act - let response = application.post(path, "", Some(headers)).await; - // Assert - assert_eq!(401, response.status().as_u16()); - assert_eq!( - "text/plain; charset=utf-8", - response - .headers() - .get("content-type") - .expect("Failed to get content type") - .to_str() - .expect("Failed to convert content type to string") - ); - assert_eq!( - format!("No event access found for key: {}", event_access), - response.text().await.expect("Failed to get response text") - ); -} - -#[actix_web::test] -#[flaky] -async fn returns_404_inexistent_event() { - // Arrange - let application = TestApp::spawn(HashMap::new()).await; - let event_access = application.insert_event_access().await; - let event_access_token = event_access.access_key; - - let token = Token - .generate(application.configuration().clone(), 1) - .expect("Failed to generate token"); - - let headers = HeaderMap::from_iter(vec![ - ( - HeaderName::from_static("x-integrationos-secret"), - HeaderValue::from_str(&event_access_token).expect("Failed to create header value"), - ), - ( - HeaderName::from_static("x-integrationos-admin-token"), - HeaderValue::from_str(&format!("Bearer {}", token)) - .expect("Failed to create header value"), - ), - ]); - - let id = Id::now(IdPrefix::ConnectionModelDefinition); - let path = format!("integration/trigger/{}", id); - // Act - let response = application.post(path, "", Some(headers)).await; - // Assert - let msg = format!( - "{{\"passthrough\":{{\"type\":\"NotFound\",\"code\":2005,\"status\":404,\"key\":\"err::application::not_found\",\"message\":\"Connection with id {} not found\"}}}}", - id - ); - assert_eq!(404, response.status().as_u16()); - assert_eq!( - "application/json", - response - .headers() - .get("content-type") - .expect("Failed to get content type") - .to_str() - .expect("Failed to convert content type to string") - ); - assert_eq!( - msg, - response.text().await.expect("Failed to get response text") - ); -} diff --git a/tests/lib.rs b/tests/lib.rs deleted file mode 100644 index 9c7b31c..0000000 --- a/tests/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod http; -pub mod suite; diff --git a/tests/suite.rs b/tests/suite.rs deleted file mode 100644 index 17698c4..0000000 --- a/tests/suite.rs +++ /dev/null @@ -1,198 +0,0 @@ -use chrono::{DateTime, TimeZone, Utc}; -use fake::{Fake, Faker}; -use integrationos_domain::{ - access_key_data::AccessKeyData, access_key_prefix::AccessKeyPrefix, - connection_model_definition::ConnectionModelDefinition, encrypted_data::PASSWORD_LENGTH, - environment::Environment, event_access::EventAccess, event_type::EventType, prefix::IdPrefix, - AccessKey, Id, Store, -}; -use mongodb::{Client as MongoClient, Database}; -use oauth_api::Configuration; -use once_cell::sync::Lazy; -use rand::Rng; -use reqwest::{header::HeaderMap, Client}; -use std::collections::HashMap; -use uuid::Uuid; - -pub struct TestApp { - client: Client, - address: String, - configuration: Configuration, - mongo: Database, -} - -static IV: Lazy<[u8; 16]> = Lazy::new(|| rand::thread_rng().gen::<[u8; 16]>()); -pub static EPOCH: Lazy> = Lazy::new(|| { - TimeZone::from_utc_datetime( - &Utc, - &DateTime::from_timestamp(0, 0) - .expect("Failed to create timestamp") - .naive_utc(), - ) -}); -pub static ID: Lazy = Lazy::new(|| Id::now(IdPrefix::ConnectionModelDefinition)); -pub static EVENT_ACCESS_PASSWORD: Lazy<[u8; PASSWORD_LENGTH]> = Lazy::new(|| { - "32KFFT_i4UpkJmyPwY2TGzgHpxfXs7zS" - .as_bytes() - .try_into() - .expect("Failed to convert password to array") -}); - -impl TestApp { - #[cfg(test)] - pub async fn get>(&self, path: T) -> reqwest::Response { - let path = path.into(); - self.client - .get(format!("{}/v1/{}", self.address, path)) - .send() - .await - .expect("Failed to execute request") - } - - #[cfg(test)] - pub async fn post, B: Into>( - &self, - path: T, - body: B, - headers: Option, - ) -> reqwest::Response { - let path = path.into(); - let headers = headers.unwrap_or_default(); - self.client - .post(format!("{}/v1/{}", self.address, path)) - .headers(headers) - .body(body.into()) - .send() - .await - .expect("Failed to execute request") - } - - #[cfg(test)] - pub async fn spawn(config: HashMap<&str, &str>) -> Self { - use std::collections::hash_map::RandomState; - - use oauth_api::Application; - - let url = "mongodb://127.0.0.1:27017/?directConnection=true"; - let uuid = Uuid::new_v4().to_string(); - - let configuration = Configuration::from( - HashMap::<&str, &str, RandomState>::from_iter([ - ("HOST", "localhost"), - ("PORT", "0"), - ("CONTROL_DATABASE_URL", url), - ("EVENT_DATABASE_URL", url), - ("CONTEXT_DATABASE_URL", url), - ("UDM_DATABASE_URL", url), - ("EVENT_DATABASE_NAME", uuid.as_str()), - ("CONTEXT_DATABASE_NAME", uuid.as_str()), - ("CONTROL_DATABASE_NAME", uuid.as_str()), - ("UDM_DATABASE_NAME", uuid.as_str()), - ("SECRETS_SERVICE_BASE_URL", "http://localhost:1080/"), - ("SECRETS_SERVICE_GET_PATH", "v1/secrets/get/"), - ("SECRETS_SERVICE_CREATE_PATH", "v1/secrets/create/"), - ("REFRESH_BEFORE_IN_MINUTES", "10"), - ("SLEEP_TIMER_IN_SECONDS", "20"), - ("ENVIRONMENT", "development"), - ]) - .into_iter() - .chain(config.into_iter()) - .collect::>(), - ); - - let application = Application::start(&configuration) - .await - .expect("Failed to start app"); - let address = format!("http://localhost:{}", application.port()); - tokio::spawn(application.spawn()); - - let client = MongoClient::with_uri_str(url) - .await - .expect("Failed to create database client") - .database(uuid.as_str()); - - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - - Self { - client: Client::new(), - address, - configuration, - mongo: client, - } - } - - pub async fn insert_connection_definition(&self) { - let mut stripe_model_config: ConnectionModelDefinition = Faker.fake(); - - stripe_model_config.id = *ID; - - let _ = self - .mongo - .collection("connection_model_definition") - .insert_one(stripe_model_config, None) - .await - .expect("Failed to insert into database"); - } - - pub async fn insert_event_access(&self) -> EventAccess { - let mut event_access: EventAccess = Faker.fake(); - - let access_key = self.access_key_encoded(); - - event_access.access_key = access_key; - event_access.paths = Default::default(); - event_access.throughput = Default::default(); - event_access.record_metadata = Default::default(); - - let _ = self - .mongo - .collection::(Store::EventAccess.to_string().as_str()) - .insert_one(event_access.clone(), None) - .await - .expect("Failed to insert into database"); - - event_access - } - - fn access_key_encoded(&self) -> String { - let access_key = AccessKey { - prefix: AccessKeyPrefix { - environment: Environment::Test, - event_type: EventType::SecretKey, - version: 1, - }, - data: AccessKeyData { - id: Id::now(IdPrefix::EventAccess).to_string(), - namespace: "namespace".to_string(), - event_type: "event_type".to_string(), - group: "group".to_string(), - event_path: "event_path".to_string(), - event_object_id_path: None, - timestamp_path: None, - parent_access_key: None, - }, - }; - - let access_key_encoded = access_key - .encode(&EVENT_ACCESS_PASSWORD, &IV) - .expect("Failed to encode access key"); - - access_key_encoded.to_string() - } - - pub fn client(&self) -> &Client { - &self.client - } - - pub fn address(&self) -> &str { - &self.address - } - - pub fn configuration(&self) -> &Configuration { - &self.configuration - } - - pub fn mongo(&self) -> &Database { - &self.mongo - } -}