aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2025-08-10 12:55:43 +0200
committerrtkay123 <dev@kanjala.com>2025-08-10 12:55:43 +0200
commitbd31dc85f8e9cb01c1e1a4e49fd4735d24a6da04 (patch)
tree50b63525480da0bee2ce713d69f02617c20bee8d
parent8deeab3e11f707677609047f5577a256cf28ed63 (diff)
downloadwarden-bd31dc85f8e9cb01c1e1a4e49fd4735d24a6da04.tar.bz2
warden-bd31dc85f8e9cb01c1e1a4e49fd4735d24a6da04.zip
chore: collapse stack-up
-rw-r--r--Cargo.lock1285
-rw-r--r--Cargo.toml15
-rw-r--r--README.md2
-rw-r--r--crates/pseudonyms/Cargo.toml14
-rw-r--r--crates/warden/Cargo.toml2
-rw-r--r--crates/warden/src/main.rs2
-rw-r--r--crates/warden/src/server.rs4
-rw-r--r--crates/warden/src/server/routes/processor/pacs008.rs29
-rw-r--r--crates/warden/src/state.rs2
-rw-r--r--lib/warden-core/build.rs10
-rw-r--r--lib/warden-stack/Cargo.toml75
-rw-r--r--lib/warden-stack/LICENSE-APACHE201
-rw-r--r--lib/warden-stack/LICENSE-MIT21
-rw-r--r--lib/warden-stack/README.md22
-rw-r--r--lib/warden-stack/examples/tracing.rs10
-rw-r--r--lib/warden-stack/src/cache.rs292
-rw-r--r--lib/warden-stack/src/cache/cluster.rs52
-rw-r--r--lib/warden-stack/src/cache/sentinel.rs65
-rw-r--r--lib/warden-stack/src/config.rs139
-rw-r--r--lib/warden-stack/src/lib.rs95
-rw-r--r--lib/warden-stack/src/nats.rs61
-rw-r--r--lib/warden-stack/src/postgres.rs137
-rw-r--r--lib/warden-stack/src/tracing.rs66
-rw-r--r--lib/warden-stack/src/tracing/loki.rs29
-rw-r--r--lib/warden-stack/src/tracing/telemetry.rs137
25 files changed, 2715 insertions, 52 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d0bead9..bc2f3f1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -39,6 +39,12 @@ dependencies = [
]
[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
name = "anstream"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -104,6 +110,48 @@ dependencies = [
]
[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
+name = "async-nats"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f6da6d49a956424ca4e28fe93656f790d748b469eaccbc7488fec545315180"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures",
+ "memchr",
+ "nkeys",
+ "nuid",
+ "once_cell",
+ "pin-project",
+ "portable-atomic",
+ "rand 0.8.5",
+ "regex",
+ "ring",
+ "rustls-native-certs",
+ "rustls-pemfile",
+ "rustls-webpki 0.102.8",
+ "serde",
+ "serde_json",
+ "serde_nanos",
+ "serde_repr",
+ "thiserror 1.0.69",
+ "time",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "tokio-websockets",
+ "tracing",
+ "tryhard",
+ "url",
+]
+
+[[package]]
name = "async-trait"
version = "0.1.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -115,12 +163,27 @@ dependencies = [
]
[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
name = "axum"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -187,6 +250,15 @@ dependencies = [
]
[[package]]
+name = "backon"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d"
+dependencies = [
+ "fastrand",
+]
+
+[[package]]
name = "backtrace"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -208,10 +280,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
+name = "base64ct"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
+
+[[package]]
+name = "bb8"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d8b8e1a22743d9241575c6ba822cf9c8fef34771c86ab7e477a4fbfd254e5"
+dependencies = [
+ "futures-util",
+ "parking_lot",
+ "tokio",
+]
+
+[[package]]
+name = "bb8-redis"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5143936af5e1eea1a881e3e3d21b6777da6315e5e307bc3d0c2301c44fa37da9"
+dependencies = [
+ "bb8",
+ "redis",
+]
+
+[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+dependencies = [
+ "serde",
+]
[[package]]
name = "block-buffer"
@@ -254,10 +356,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+dependencies = [
+ "serde",
+]
[[package]]
name = "cc"
@@ -327,6 +438,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "config"
version = "0.15.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -340,6 +474,12 @@ dependencies = [
]
[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -349,6 +489,22 @@ dependencies = [
]
[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -358,6 +514,27 @@ dependencies = [
]
[[package]]
+name = "crc"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
+[[package]]
+name = "crc16"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
+
+[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -376,6 +553,15 @@ dependencies = [
]
[[package]]
+name = "crossbeam-queue"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -392,6 +578,32 @@ dependencies = [
]
[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "darling"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -427,6 +639,23 @@ dependencies = [
]
[[package]]
+name = "data-encoding"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
+
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
name = "deranged"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -454,7 +683,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
+ "const-oid",
"crypto-common",
+ "subtle",
]
[[package]]
@@ -469,10 +700,41 @@ dependencies = [
]
[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "sha2",
+ "signature",
+ "subtle",
+]
+
+[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+dependencies = [
+ "serde",
+]
[[package]]
name = "equivalent"
@@ -491,12 +753,40 @@ dependencies = [
]
[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -514,6 +804,17 @@ dependencies = [
]
[[package]]
+name = "flume"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin",
+]
+
+[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -535,12 +836,27 @@ dependencies = [
]
[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
+ "futures-sink",
]
[[package]]
@@ -561,6 +877,23 @@ dependencies = [
]
[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -589,10 +922,13 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
+ "futures-channel",
"futures-core",
+ "futures-io",
"futures-macro",
"futures-sink",
"futures-task",
+ "memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -666,16 +1002,60 @@ version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
+ "allocator-api2",
+ "equivalent",
"foldhash",
]
[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown",
+]
+
+[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "home"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -756,7 +1136,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
- "webpki-roots",
+ "webpki-roots 1.0.2",
]
[[package]]
@@ -983,6 +1363,9 @@ name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
[[package]]
name = "libc"
@@ -991,6 +1374,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "libz-rs-sys"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1012,6 +1411,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
+name = "lock_api"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1049,6 +1458,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
+[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1075,7 +1494,7 @@ dependencies = [
"metrics",
"metrics-util",
"quanta",
- "thiserror",
+ "thiserror 2.0.12",
]
[[package]]
@@ -1089,7 +1508,7 @@ dependencies = [
"hashbrown",
"metrics",
"quanta",
- "rand",
+ "rand 0.9.2",
"rand_xoshiro",
"sketches-ddsketch",
]
@@ -1137,6 +1556,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
+name = "nkeys"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879011babc47a1c7fdf5a935ae3cfe94f34645ca0cac1c7f6424b36fc743d1bf"
+dependencies = [
+ "data-encoding",
+ "ed25519",
+ "ed25519-dalek",
+ "getrandom 0.2.16",
+ "log",
+ "rand 0.8.5",
+ "signatory",
+]
+
+[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1147,12 +1581,78 @@ dependencies = [
]
[[package]]
+name = "nuid"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc895af95856f929163a0aa20c26a78d26bfdc839f51b9d5aa7a5b79e52b7e83"
+dependencies = [
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+dependencies = [
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand 0.8.5",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1174,6 +1674,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
name = "opentelemetry"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1183,7 +1689,7 @@ dependencies = [
"futures-sink",
"js-sys",
"pin-project-lite",
- "thiserror",
+ "thiserror 2.0.12",
"tracing",
]
@@ -1211,7 +1717,7 @@ dependencies = [
"opentelemetry-proto",
"opentelemetry_sdk",
"prost 0.13.5",
- "thiserror",
+ "thiserror 2.0.12",
"tokio",
"tonic 0.13.1",
]
@@ -1245,8 +1751,8 @@ dependencies = [
"futures-util",
"opentelemetry",
"percent-encoding",
- "rand",
- "thiserror",
+ "rand 0.9.2",
+ "thiserror 2.0.12",
"tokio",
"tokio-stream",
]
@@ -1258,6 +1764,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1270,6 +1805,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1318,6 +1862,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1501,7 +2072,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2 0.5.10",
- "thiserror",
+ "thiserror 2.0.12",
"tokio",
"tracing",
"web-time",
@@ -1516,13 +2087,13 @@ dependencies = [
"bytes",
"getrandom 0.3.3",
"lru-slab",
- "rand",
+ "rand 0.9.2",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
- "thiserror",
+ "thiserror 2.0.12",
"tinyvec",
"tracing",
"web-time",
@@ -1559,12 +2130,33 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
- "rand_chacha",
- "rand_core",
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
]
[[package]]
@@ -1574,7 +2166,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
]
[[package]]
@@ -1592,7 +2193,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
dependencies = [
- "rand_core",
+ "rand_core 0.9.3",
]
[[package]]
@@ -1605,6 +2206,43 @@ dependencies = [
]
[[package]]
+name = "redis"
+version = "0.32.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd3650deebc68526b304898b192fa4102a4ef0b9ada24da096559cb60e0eef8"
+dependencies = [
+ "arc-swap",
+ "backon",
+ "bytes",
+ "cfg-if",
+ "combine",
+ "crc16",
+ "futures-channel",
+ "futures-sink",
+ "futures-util",
+ "itoa",
+ "log",
+ "num-bigint",
+ "percent-encoding",
+ "pin-project-lite",
+ "rand 0.9.2",
+ "ryu",
+ "socket2 0.6.0",
+ "tokio",
+ "tokio-util",
+ "url",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1683,7 +2321,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "webpki-roots",
+ "webpki-roots 1.0.2",
]
[[package]]
@@ -1701,6 +2339,26 @@ dependencies = [
]
[[package]]
+name = "rsa"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "rust-embed"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1747,6 +2405,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
name = "rustix"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1768,12 +2435,34 @@ dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
- "rustls-webpki",
+ "rustls-webpki 0.103.4",
"subtle",
"zeroize",
]
[[package]]
+name = "rustls-native-certs"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1785,6 +2474,16 @@ dependencies = [
[[package]]
name = "rustls-webpki"
+version = "0.102.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+dependencies = [
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustls-webpki"
version = "0.103.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
@@ -1816,6 +2515,60 @@ dependencies = [
]
[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "secrecy"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
+dependencies = [
+ "serde",
+ "zeroize",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1848,6 +2601,15 @@ dependencies = [
]
[[package]]
+name = "serde_nanos"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a93142f0367a4cc53ae0fead1bcda39e85beccfad3dcd717656cacab94b12985"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "serde_path_to_error"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1858,6 +2620,17 @@ dependencies = [
]
[[package]]
+name = "serde_repr"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "serde_spanned"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1879,6 +2652,17 @@ dependencies = [
]
[[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 = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1914,6 +2698,28 @@ dependencies = [
]
[[package]]
+name = "signatory"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31"
+dependencies = [
+ "pkcs8",
+ "rand_core 0.6.4",
+ "signature",
+ "zeroize",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core 0.6.4",
+]
+
+[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1936,6 +2742,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+dependencies = [
+ "serde",
+]
[[package]]
name = "snap"
@@ -1964,29 +2773,227 @@ dependencies = [
]
[[package]]
-name = "stable_deref_trait"
-version = "1.2.0"
+name = "spin"
+version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
[[package]]
-name = "stack-up"
-version = "0.1.0"
-source = "git+https://github.com/rtkay123/stack-up#3bd3765d8354e9cdcda01febafd26f79c94688e7"
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
- "bon",
- "opentelemetry",
- "opentelemetry-http",
- "opentelemetry-otlp",
- "opentelemetry-semantic-conventions",
- "opentelemetry_sdk",
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
+dependencies = [
+ "base64",
+ "bytes",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown",
+ "hashlink",
+ "indexmap",
+ "log",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
"serde",
"serde_json",
- "thiserror",
+ "sha2",
+ "smallvec",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-stream",
"tracing",
- "tracing-loki",
- "tracing-opentelemetry",
- "tracing-subscriber",
+ "url",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
+dependencies = [
+ "dotenvy",
+ "either",
+ "heck",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx-core",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+ "syn",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-mysql"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
+dependencies = [
+ "atoi",
+ "base64",
+ "bitflags",
+ "byteorder",
+ "bytes",
+ "crc",
+ "digest",
+ "dotenvy",
+ "either",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "generic-array",
+ "hex",
+ "hkdf",
+ "hmac",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rand 0.8.5",
+ "rsa",
+ "serde",
+ "sha1",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.12",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
+dependencies = [
+ "atoi",
+ "base64",
+ "bitflags",
+ "byteorder",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.12",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-sqlite"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
+dependencies = [
+ "atoi",
+ "flume",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "serde",
+ "serde_urlencoded",
+ "sqlx-core",
+ "thiserror 2.0.12",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
]
[[package]]
@@ -2047,11 +3054,31 @@ dependencies = [
[[package]]
name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -2141,6 +3168,7 @@ dependencies = [
"io-uring",
"libc",
"mio",
+ "parking_lot",
"pin-project-lite",
"signal-hook-registry",
"slab",
@@ -2195,6 +3223,27 @@ dependencies = [
]
[[package]]
+name = "tokio-websockets"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f591660438b3038dd04d16c938271c79e7e06260ad2ea2885a4861bfb238605d"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "httparse",
+ "rand 0.8.5",
+ "ring",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "webpki-roots 0.26.11",
+]
+
+[[package]]
name = "toml"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2500,6 +3549,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
+name = "tryhard"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fe58ebd5edd976e0fe0f8a14d2a04b7c81ef153ea9a54eebc42e67c2c23b4e5"
+dependencies = [
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2512,12 +3571,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
+name = "unicode-normalization"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-properties"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
+
+[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2538,6 +3618,7 @@ dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
+ "serde",
]
[[package]]
@@ -2661,6 +3742,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2697,7 +3784,6 @@ dependencies = [
"metrics-exporter-prometheus",
"serde",
"serde_json",
- "stack-up",
"time",
"tokio",
"tower",
@@ -2710,6 +3796,7 @@ dependencies = [
"utoipa-scalar",
"utoipa-swagger-ui",
"warden-core",
+ "warden-stack",
]
[[package]]
@@ -2731,7 +3818,46 @@ dependencies = [
name = "warden-pseudonyms"
version = "0.1.0"
dependencies = [
+ "anyhow",
+ "clap",
+ "config",
+ "metrics",
+ "metrics-exporter-prometheus",
+ "serde",
+ "serde_json",
+ "time",
+ "tokio",
+ "tracing",
"warden-core",
+ "warden-stack",
+]
+
+[[package]]
+name = "warden-stack"
+version = "0.1.0"
+dependencies = [
+ "async-nats",
+ "bb8",
+ "bb8-redis",
+ "bon",
+ "opentelemetry",
+ "opentelemetry-http",
+ "opentelemetry-otlp",
+ "opentelemetry-semantic-conventions",
+ "opentelemetry_sdk",
+ "redis",
+ "secrecy",
+ "serde",
+ "serde_json",
+ "sqlx",
+ "thiserror 2.0.12",
+ "tokio",
+ "tonic 0.14.0",
+ "tracing",
+ "tracing-loki",
+ "tracing-opentelemetry",
+ "tracing-subscriber",
+ "url",
]
[[package]]
@@ -2750,6 +3876,12 @@ dependencies = [
]
[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
+[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2842,6 +3974,15 @@ dependencies = [
[[package]]
name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.2",
+]
+
+[[package]]
+name = "webpki-roots"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
@@ -2850,6 +3991,16 @@ dependencies = [
]
[[package]]
+name = "whoami"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
+dependencies = [
+ "redox_syscall",
+ "wasite",
+]
+
+[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2888,6 +4039,15 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
@@ -2915,6 +4075,21 @@ dependencies = [
[[package]]
name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
@@ -2948,6 +4123,12 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
@@ -2960,6 +4141,12 @@ checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
@@ -2972,6 +4159,12 @@ checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
@@ -2996,6 +4189,12 @@ checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
@@ -3008,6 +4207,12 @@ checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
@@ -3020,6 +4225,12 @@ checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
@@ -3032,6 +4243,12 @@ checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
index ef6e297..2a7a764 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,15 +10,27 @@ description = "A rule-based fraud detection platform"
[workspace.dependencies]
anyhow = "1.0.98"
+async-nats = "0.42.0"
axum = "0.8.4"
+bon = "3.4.0"
clap = "4.5.43"
config = { version = "0.15.13", default-features = false }
metrics = { version = "0.24.2", default-features = false }
metrics-exporter-prometheus = { version = "0.17.2", default-features = false }
+opentelemetry = { version = "0.30.0", default-features = false }
+opentelemetry-http = "0.30.0"
+opentelemetry-otlp = { version = "0.30.0", default-features = false }
+opentelemetry-semantic-conventions = { version = "0.30.0", default-features = false }
+opentelemetry_sdk = { version = "0.30.0", default-features = false }
prost = "0.14.1"
+redis = { version = "0.32.5", default-features = false }
+secrecy = "0.10.3"
serde = "1.0.219"
serde_json = "1.0.142"
-stack-up = { git = "https://github.com/rtkay123/stack-up" }
+sqlx = { version = "0.8.6", default-features = false }
+thiserror = "2.0.12"
+tracing-opentelemetry = "0.31.0"
+url = "2.5.4"
time = "0.3.41"
tokio = "1.47.1"
tonic = "0.14.0"
@@ -32,3 +44,4 @@ utoipa-redoc = "6.0.0"
utoipa-scalar = "0.3.0"
utoipa-swagger-ui = "9.0.2"
warden-core = { path = "lib/warden-core" }
+warden-stack = { path = "lib/warden-stack" }
diff --git a/README.md b/README.md
index a6b42c2..7f5d3cb 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# warden
```sh
-git clone git://kanjala.com/warden.git
+git clone https://github.com/rtkay123/warden.git
cd warden
git submodule update --init --depth 1 --recommend-shallow
```
diff --git a/crates/pseudonyms/Cargo.toml b/crates/pseudonyms/Cargo.toml
index 4ddf179..02ecddc 100644
--- a/crates/pseudonyms/Cargo.toml
+++ b/crates/pseudonyms/Cargo.toml
@@ -8,4 +8,18 @@ documentation.workspace = true
description.workspace = true
[dependencies]
+anyhow.workspace = true
+clap = { workspace = true, features = ["derive"] }
+config = { workspace = true, features = ["convert-case", "toml"] }
+metrics.workspace = true
+metrics-exporter-prometheus.workspace = true
+serde = { workspace = true, features = ["derive"] }
+serde_json.workspace = true
+time.workspace = true
+tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] }
+tracing.workspace = true
warden-core = { workspace = true, features = ["pseudonyms", "serde-time"] }
+
+[dependencies.warden-stack]
+workspace = true
+features = ["api", "cache", "postgres", "opentelemetry", "tracing-loki"]
diff --git a/crates/warden/Cargo.toml b/crates/warden/Cargo.toml
index b10ed45..dde983b 100644
--- a/crates/warden/Cargo.toml
+++ b/crates/warden/Cargo.toml
@@ -42,6 +42,6 @@ scalar = ["dep:utoipa-scalar", "utoipa-scalar/axum"]
[dev-dependencies]
tower = { workspace = true, features = ["util"] }
-[dependencies.stack-up]
+[dependencies.warden-stack]
workspace = true
features = ["api", "opentelemetry", "tracing-loki"]
diff --git a/crates/warden/src/main.rs b/crates/warden/src/main.rs
index b649280..68b185c 100644
--- a/crates/warden/src/main.rs
+++ b/crates/warden/src/main.rs
@@ -7,7 +7,7 @@ mod version;
use std::net::{Ipv6Addr, SocketAddr};
use clap::{Parser, command};
-use stack_up::{Configuration, tracing::Tracing};
+use warden_stack::{Configuration, tracing::Tracing};
use tracing::info;
use crate::state::AppState;
diff --git a/crates/warden/src/server.rs b/crates/warden/src/server.rs
index ce01fb8..6712516 100644
--- a/crates/warden/src/server.rs
+++ b/crates/warden/src/server.rs
@@ -58,8 +58,8 @@ pub async fn health_check() -> impl axum::response::IntoResponse {
}
#[cfg(test)]
-pub(crate) fn test_config() -> stack_up::Configuration {
- use stack_up::Configuration;
+pub(crate) fn test_config() -> warden_stack::Configuration {
+ use warden_stack::Configuration;
let config_path = "warden.toml";
diff --git a/crates/warden/src/server/routes/processor/pacs008.rs b/crates/warden/src/server/routes/processor/pacs008.rs
index 87f1fee..3efc7f1 100644
--- a/crates/warden/src/server/routes/processor/pacs008.rs
+++ b/crates/warden/src/server/routes/processor/pacs008.rs
@@ -1,10 +1,10 @@
use axum::{extract::State, response::IntoResponse};
-use stack_up::tracing_opentelemetry::OpenTelemetrySpanExt;
+use warden_stack::tracing_opentelemetry::OpenTelemetrySpanExt;
use tracing::{debug, error, trace, warn};
use warden_core::{
google::r#type::Money,
- iso20022::{TransactionType, pacs008::Pacs008Document},
- message::DataCache,
+ iso20022::{pacs008::Pacs008Document, TransactionType},
+ message::DataCache, pseudonyms::transaction_relationship::{CreatePseudonymRequest, TransactionRelationship},
};
use crate::{error::AppError, server::routes::PACS008_001_12, state::AppHandle, version::Version};
@@ -99,6 +99,29 @@ pub(super) async fn post_pacs008(
None
};
+ let transaction_relationship = TransactionRelationship {
+ from: data_cache.dbtr_acct_id.to_string(),
+ to: data_cache.cdtr_acct_id.to_string(),
+ amt: money,
+ cre_dt_tm: data_cache.cre_dt_tm,
+ end_to_end_id: end_to_end_id.to_string(),
+ msg_id: msg_id.to_string(),
+ pmt_inf_id: pmt_inf_id.into(),
+ tx_tp: tx_tp.to_owned(),
+ ..Default::default()
+ };
+
+ let request = CreatePseudonymRequest {
+ transaction_relationship: Some(transaction_relationship),
+ debtor_id: data_cache.dbtr_id.to_string(),
+ debtor_account_id: data_cache.dbtr_acct_id.to_string(),
+ creditor_id: data_cache.cdtr_id.to_string(),
+ creditor_account_id: data_cache.cdtr_acct_id.to_string(),
+ };
+
+
+ debug!(%msg_id, %end_to_end_id, "constructed transaction relationship");
+
Ok(String::default())
}
diff --git a/crates/warden/src/state.rs b/crates/warden/src/state.rs
index aabcaab..8e5b182 100644
--- a/crates/warden/src/state.rs
+++ b/crates/warden/src/state.rs
@@ -1,4 +1,4 @@
-use stack_up::{Configuration, Environment};
+use warden_stack::{Configuration, Environment};
use std::{ops::Deref, sync::Arc};
use crate::{cnfg::LocalConfig, error::AppError};
diff --git a/lib/warden-core/build.rs b/lib/warden-core/build.rs
index 57e20e0..6d5efbb 100644
--- a/lib/warden-core/build.rs
+++ b/lib/warden-core/build.rs
@@ -9,17 +9,11 @@ enum Entity {
#[cfg(any(feature = "message", feature = "pseudonyms"))]
impl Entity {
fn protos(&self) -> Vec<&'static str> {
- let mut res: Vec<&'static str> = vec![
- // "proto/googleapis/google/type/date.proto",
- // "proto/googleapis/google/type/money.proto",
- // "proto/googleapis/google/type/latlng.proto",
- ];
+ let mut res: Vec<&'static str> = vec![];
#[cfg(feature = "message")]
fn iso20022_protos() -> Vec<&'static str> {
vec![
- // "proto/iso20022/pacs_008_001_12.proto",
- // "proto/iso20022/pacs_002_001_12.proto",
"proto/warden_message.proto",
]
}
@@ -60,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "pseudonyms")]
protos.extend(Entity::Pseudonyms.protos());
-#[cfg(any(feature = "message", feature = "pseudonyms"))]
+ #[cfg(any(feature = "message", feature = "pseudonyms"))]
build_proto(&protos)?;
Ok(())
diff --git a/lib/warden-stack/Cargo.toml b/lib/warden-stack/Cargo.toml
new file mode 100644
index 0000000..d7c1eb8
--- /dev/null
+++ b/lib/warden-stack/Cargo.toml
@@ -0,0 +1,75 @@
+[package]
+name = "warden-stack"
+version = "0.1.0"
+edition = "2024"
+license = "MIT OR Apache-2.0"
+
+[dependencies]
+async-nats = { workspace = true, optional = true }
+bb8 = { version = "0.9.0", optional = true }
+bb8-redis = { version = "0.24.0", optional = true }
+bon.workspace = true
+opentelemetry = { workspace = true, optional = true }
+opentelemetry-http = { workspace = true, optional = true }
+opentelemetry-otlp = { workspace = true, optional = true }
+opentelemetry-semantic-conventions = { workspace = true, optional = true }
+opentelemetry_sdk = { workspace = true, optional = true }
+redis = { workspace = true, optional = true }
+secrecy = { workspace = true, optional = true }
+serde = { workspace = true, features = ["derive", "rc"] }
+serde_json.workspace = true
+sqlx = { workspace = true, optional = true }
+thiserror.workspace = true
+tokio = { workspace = true, optional = true }
+tonic = { workspace = true, optional = true }
+tracing = { workspace = true, optional = true }
+tracing-loki = { version = "0.2.6", optional = true, default-features = false, features = ["compat-0-2-1", "rustls"] }
+tracing-opentelemetry = { workspace = true, optional = true }
+tracing-subscriber = { version = "0.3.19", optional = true }
+url = { workspace = true, optional = true }
+
+[features]
+default = []
+api = []
+cache = [
+ "dep:redis",
+ "redis/cluster-async",
+ "redis/connection-manager",
+ "redis/tokio-comp",
+ "redis/sentinel",
+ "tokio/sync",
+ "dep:bb8",
+ "dep:bb8-redis",
+ "url/serde",
+]
+nats-core = ["dep:async-nats"]
+nats-jetstream = ["dep:async-nats"]
+opentelemetry = [
+ "dep:opentelemetry",
+ "dep:tracing-opentelemetry",
+ "tracing",
+ "opentelemetry_sdk/rt-tokio",
+ "opentelemetry_sdk/trace",
+ "opentelemetry/trace",
+ "opentelemetry-http",
+ "opentelemetry-otlp/grpc-tonic",
+ "opentelemetry-otlp/http-proto",
+ "opentelemetry-semantic-conventions/semconv_experimental",
+]
+postgres = ["sqlx/postgres", "url/serde", "secrecy/serde"]
+tracing = ["dep:tracing", "tracing-subscriber/env-filter"]
+opentelemetry-tonic = ["dep:tonic"]
+tracing-loki = ["dep:tracing-loki", "tracing"]
+
+[[example]]
+name = "tracing"
+path = "examples/tracing.rs"
+required-features = ["tracing"]
+
+[dev-dependencies]
+tokio = { version = "*", features = ["macros", "rt"] }
+sqlx = { version = "*", features = ["runtime-tokio"] }
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
diff --git a/lib/warden-stack/LICENSE-APACHE b/lib/warden-stack/LICENSE-APACHE
new file mode 100644
index 0000000..4c986ff
--- /dev/null
+++ b/lib/warden-stack/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2025 rtkay
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/lib/warden-stack/LICENSE-MIT b/lib/warden-stack/LICENSE-MIT
new file mode 100644
index 0000000..7eed760
--- /dev/null
+++ b/lib/warden-stack/LICENSE-MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 rtkay
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/warden-stack/README.md b/lib/warden-stack/README.md
new file mode 100644
index 0000000..12f9f3a
--- /dev/null
+++ b/lib/warden-stack/README.md
@@ -0,0 +1,22 @@
+warden-stack!
+
+A crate centralising the configuration and initialisation of various services I find myself using in a lot of projects.
+
+# TL:DR
+Find a feature, activate it, initialise it in your builder, then you're good to go
+
+## Features
+
+Each feature unlocks configuration methods on the `ServicesBuilder`, allowing you to selectively wire up what you need.
+
+| Feature | Description |
+|----------------|-----------------------------------------------------------|
+| `api` | Enables `port` configuration |
+| `cache` | Enables redis/valkey caching support |
+| `nats-core` | Enables core NATS messaging via `async-nats` |
+| `nats-jetstream`| Enables NATS JetStream support via `async-nats` |
+| `opentelemetry`| Enables distributed tracing with OpenTelemetry |
+| `postgres` | Enables PostgreSQL support using `sqlx` |
+| `tracing` | Enables tracing setup via `tracing` and `tracing-subscriber` |
+| `opentelemetry-tonic` | Enables opentelemetry injector and extractor utilities for `tonic` |
+| `tracing-loki` | Enables tracing output to Loki. |
diff --git a/lib/warden-stack/examples/tracing.rs b/lib/warden-stack/examples/tracing.rs
new file mode 100644
index 0000000..7ebe41e
--- /dev/null
+++ b/lib/warden-stack/examples/tracing.rs
@@ -0,0 +1,10 @@
+use warden_stack::{Monitoring, tracing::TracingBuilder};
+
+fn main() {
+ let config = Monitoring {
+ log_level: "info".to_string(),
+ };
+ let _tracing = TracingBuilder::default().build(&config);
+
+ tracing::info!("hello from tracing");
+}
diff --git a/lib/warden-stack/src/cache.rs b/lib/warden-stack/src/cache.rs
new file mode 100644
index 0000000..9be3778
--- /dev/null
+++ b/lib/warden-stack/src/cache.rs
@@ -0,0 +1,292 @@
+// https://github.com/svix/svix-webhooks/tree/4ede01a3209658615bb8d3153965c5c3a2e1b7ff/server/svix-server/src/redis
+pub mod cluster;
+pub mod sentinel;
+
+use std::{sync::Arc, time::Duration};
+
+use bb8::{Pool, RunError};
+use bb8_redis::RedisConnectionManager;
+use redis::{
+ AsyncConnectionConfig, ProtocolVersion, RedisConnectionInfo, RedisError, TlsMode,
+ aio::ConnectionManagerConfig, sentinel::SentinelNodeConnectionInfo,
+};
+use sentinel::{RedisSentinelConnectionManager, SentinelConfig};
+use serde::Deserialize;
+use tokio::sync::Mutex;
+
+use crate::{
+ ServiceError, ServicesBuilder,
+ services_builder::{IsUnset, SetCache, State},
+};
+
+pub use self::cluster::RedisClusterConnectionManager;
+
+pub const REDIS_CONN_TIMEOUT: Duration = Duration::from_secs(2);
+
+impl<S: State> ServicesBuilder<S> {
+ pub async fn cache(
+ self,
+ config: &CacheConfig,
+ ) -> Result<ServicesBuilder<SetCache<S>>, crate::ServiceError>
+ where
+ S::Cache: IsUnset,
+ {
+ Ok(self.cache_internal(RedisManager::new(config).await?))
+ }
+}
+
+fn default_max_conns() -> u16 {
+ 100
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub struct CacheConfig {
+ #[serde(rename = "dsn")]
+ redis_dsn: Arc<str>,
+ #[serde(default)]
+ pooled: bool,
+ #[serde(rename = "type")]
+ kind: RedisVariant,
+ #[serde(default = "default_max_conns")]
+ #[serde(rename = "max-connections")]
+ max_connections: u16,
+}
+
+#[derive(Debug, Deserialize, Clone)]
+#[serde(rename_all = "kebab-case")]
+pub enum RedisVariant {
+ Clustered,
+ NonClustered,
+ Sentinel(SentinelConfig),
+}
+
+#[derive(Clone)]
+pub enum RedisManager {
+ Clustered(Pool<RedisClusterConnectionManager>),
+ NonClustered(Pool<RedisConnectionManager>),
+ Sentinel(Pool<RedisSentinelConnectionManager>),
+ ClusteredUnpooled(redis::cluster_async::ClusterConnection),
+ NonClusteredUnpooled(redis::aio::ConnectionManager),
+ SentinelUnpooled(Arc<Mutex<redis::sentinel::SentinelClient>>),
+}
+
+impl RedisManager {
+ pub async fn new(config: &CacheConfig) -> Result<Self, ServiceError> {
+ if config.pooled {
+ Self::new_pooled(
+ config.redis_dsn.as_ref(),
+ &config.kind,
+ config.max_connections,
+ )
+ .await
+ } else {
+ Self::new_unpooled(config.redis_dsn.as_ref(), &config.kind).await
+ }
+ }
+ async fn new_pooled(
+ dsn: &str,
+ variant: &RedisVariant,
+ max_conns: u16,
+ ) -> Result<Self, ServiceError> {
+ match variant {
+ RedisVariant::Clustered => {
+ let mgr = RedisClusterConnectionManager::new(dsn)?;
+ let pool = bb8::Pool::builder()
+ .max_size(max_conns.into())
+ .build(mgr)
+ .await?;
+ Ok(RedisManager::Clustered(pool))
+ }
+ RedisVariant::NonClustered => {
+ let mgr = RedisConnectionManager::new(dsn)?;
+ let pool = bb8::Pool::builder()
+ .max_size(max_conns.into())
+ .build(mgr)
+ .await?;
+ Ok(RedisManager::NonClustered(pool))
+ }
+ RedisVariant::Sentinel(cfg) => {
+ let tls_mode = cfg.redis_tls_mode_secure.then_some(TlsMode::Secure);
+ let protocol = if cfg.redis_use_resp3 {
+ ProtocolVersion::RESP3
+ } else {
+ ProtocolVersion::default()
+ };
+ let mgr = RedisSentinelConnectionManager::new(
+ vec![dsn],
+ cfg.service_name.clone(),
+ Some(SentinelNodeConnectionInfo {
+ tls_mode,
+ redis_connection_info: Some(RedisConnectionInfo {
+ db: cfg.redis_db.unwrap_or(0),
+ username: cfg.redis_username.clone(),
+ password: cfg.redis_password.clone(),
+ protocol,
+ }),
+ }),
+ )?;
+ let pool = bb8::Pool::builder()
+ .max_size(max_conns.into())
+ .build(mgr)
+ .await?;
+ Ok(RedisManager::Sentinel(pool))
+ }
+ }
+ }
+
+ async fn new_unpooled(dsn: &str, variant: &RedisVariant) -> Result<Self, ServiceError> {
+ match variant {
+ RedisVariant::Clustered => {
+ let cli = redis::cluster::ClusterClient::builder(vec![dsn])
+ .retries(1)
+ .connection_timeout(REDIS_CONN_TIMEOUT)
+ .build()?;
+ let con = cli.get_async_connection().await?;
+ Ok(RedisManager::ClusteredUnpooled(con))
+ }
+ RedisVariant::NonClustered => {
+ let cli = redis::Client::open(dsn)?;
+ let con = redis::aio::ConnectionManager::new_with_config(
+ cli,
+ ConnectionManagerConfig::new()
+ .set_number_of_retries(1)
+ .set_connection_timeout(REDIS_CONN_TIMEOUT),
+ )
+ .await?;
+ Ok(RedisManager::NonClusteredUnpooled(con))
+ }
+ RedisVariant::Sentinel(cfg) => {
+ let tls_mode = cfg.redis_tls_mode_secure.then_some(TlsMode::Secure);
+ let protocol = if cfg.redis_use_resp3 {
+ ProtocolVersion::RESP3
+ } else {
+ ProtocolVersion::default()
+ };
+ let cli = redis::sentinel::SentinelClient::build(
+ vec![dsn],
+ cfg.service_name.clone(),
+ Some(SentinelNodeConnectionInfo {
+ tls_mode,
+ redis_connection_info: Some(RedisConnectionInfo {
+ db: cfg.redis_db.unwrap_or(0),
+ username: cfg.redis_username.clone(),
+ password: cfg.redis_password.clone(),
+ protocol,
+ }),
+ }),
+ redis::sentinel::SentinelServerType::Master,
+ )?;
+
+ Ok(RedisManager::SentinelUnpooled(Arc::new(Mutex::new(cli))))
+ }
+ }
+ }
+
+ pub async fn get(&self) -> Result<RedisConnection<'_>, RunError<RedisError>> {
+ match self {
+ Self::Clustered(pool) => Ok(RedisConnection::Clustered(pool.get().await?)),
+ Self::NonClustered(pool) => Ok(RedisConnection::NonClustered(pool.get().await?)),
+ Self::Sentinel(pool) => Ok(RedisConnection::SentinelPooled(pool.get().await?)),
+ Self::ClusteredUnpooled(conn) => Ok(RedisConnection::ClusteredUnpooled(conn.clone())),
+ Self::NonClusteredUnpooled(conn) => {
+ Ok(RedisConnection::NonClusteredUnpooled(conn.clone()))
+ }
+ Self::SentinelUnpooled(conn) => {
+ let mut conn = conn.lock().await;
+ let con = conn
+ .get_async_connection_with_config(
+ &AsyncConnectionConfig::new().set_response_timeout(REDIS_CONN_TIMEOUT),
+ )
+ .await?;
+ Ok(RedisConnection::SentinelUnpooled(con))
+ }
+ }
+ }
+}
+
+pub enum RedisConnection<'a> {
+ Clustered(bb8::PooledConnection<'a, RedisClusterConnectionManager>),
+ NonClustered(bb8::PooledConnection<'a, RedisConnectionManager>),
+ SentinelPooled(bb8::PooledConnection<'a, RedisSentinelConnectionManager>),
+ ClusteredUnpooled(redis::cluster_async::ClusterConnection),
+ NonClusteredUnpooled(redis::aio::ConnectionManager),
+ SentinelUnpooled(redis::aio::MultiplexedConnection),
+}
+
+impl redis::aio::ConnectionLike for RedisConnection<'_> {
+ fn req_packed_command<'a>(
+ &'a mut self,
+ cmd: &'a redis::Cmd,
+ ) -> redis::RedisFuture<'a, redis::Value> {
+ match self {
+ RedisConnection::Clustered(conn) => conn.req_packed_command(cmd),
+ RedisConnection::NonClustered(conn) => conn.req_packed_command(cmd),
+ RedisConnection::ClusteredUnpooled(conn) => conn.req_packed_command(cmd),
+ RedisConnection::NonClusteredUnpooled(conn) => conn.req_packed_command(cmd),
+ RedisConnection::SentinelPooled(conn) => conn.req_packed_command(cmd),
+ RedisConnection::SentinelUnpooled(conn) => conn.req_packed_command(cmd),
+ }
+ }
+
+ fn req_packed_commands<'a>(
+ &'a mut self,
+ cmd: &'a redis::Pipeline,
+ offset: usize,
+ count: usize,
+ ) -> redis::RedisFuture<'a, Vec<redis::Value>> {
+ match self {
+ RedisConnection::Clustered(conn) => conn.req_packed_commands(cmd, offset, count),
+ RedisConnection::NonClustered(conn) => conn.req_packed_commands(cmd, offset, count),
+ RedisConnection::ClusteredUnpooled(conn) => {
+ conn.req_packed_commands(cmd, offset, count)
+ }
+ RedisConnection::NonClusteredUnpooled(conn) => {
+ conn.req_packed_commands(cmd, offset, count)
+ }
+ RedisConnection::SentinelPooled(conn) => conn.req_packed_commands(cmd, offset, count),
+ RedisConnection::SentinelUnpooled(conn) => conn.req_packed_commands(cmd, offset, count),
+ }
+ }
+
+ fn get_db(&self) -> i64 {
+ match self {
+ RedisConnection::Clustered(conn) => conn.get_db(),
+ RedisConnection::NonClustered(conn) => conn.get_db(),
+ RedisConnection::ClusteredUnpooled(conn) => conn.get_db(),
+ RedisConnection::NonClusteredUnpooled(conn) => conn.get_db(),
+ RedisConnection::SentinelPooled(conn) => conn.get_db(),
+ RedisConnection::SentinelUnpooled(conn) => conn.get_db(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use redis::AsyncCommands;
+
+ use crate::cache::CacheConfig;
+
+ use super::RedisManager;
+
+ // Ensure basic set/get works -- should test sharding as well:
+ #[tokio::test]
+ // run with `cargo test -- --ignored redis` only when redis is up and configured
+ #[ignore]
+ async fn test_set_read_random_keys() {
+ let config = CacheConfig {
+ redis_dsn: "redis://localhost:6379".into(),
+ pooled: false,
+ kind: crate::cache::RedisVariant::NonClustered,
+ max_connections: 10,
+ };
+ let mgr = RedisManager::new(&config).await.unwrap();
+ let mut conn = mgr.get().await.unwrap();
+
+ for (val, key) in "abcdefghijklmnopqrstuvwxyz".chars().enumerate() {
+ let key = key.to_string();
+ let _: () = conn.set(key.clone(), val).await.unwrap();
+ assert_eq!(conn.get::<_, usize>(&key).await.unwrap(), val);
+ }
+ }
+}
diff --git a/lib/warden-stack/src/cache/cluster.rs b/lib/warden-stack/src/cache/cluster.rs
new file mode 100644
index 0000000..91e3b24
--- /dev/null
+++ b/lib/warden-stack/src/cache/cluster.rs
@@ -0,0 +1,52 @@
+use redis::{
+ ErrorKind, FromRedisValue, IntoConnectionInfo, RedisError,
+ cluster::{ClusterClient, ClusterClientBuilder},
+ cluster_routing::{MultipleNodeRoutingInfo, ResponsePolicy, RoutingInfo},
+};
+
+/// ConnectionManager that implements `bb8::ManageConnection` and supports
+/// asynchronous clustered connections via `redis_cluster_async::Connection`
+#[derive(Clone)]
+pub struct RedisClusterConnectionManager {
+ client: ClusterClient,
+}
+
+impl RedisClusterConnectionManager {
+ pub fn new<T: IntoConnectionInfo>(
+ info: T,
+ ) -> Result<RedisClusterConnectionManager, RedisError> {
+ Ok(RedisClusterConnectionManager {
+ client: ClusterClientBuilder::new(vec![info]).retries(0).build()?,
+ })
+ }
+}
+
+impl bb8::ManageConnection for RedisClusterConnectionManager {
+ type Connection = redis::cluster_async::ClusterConnection;
+ type Error = RedisError;
+
+ async fn connect(&self) -> Result<Self::Connection, Self::Error> {
+ self.client.get_async_connection().await
+ }
+
+ async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
+ let pong = conn
+ .route_command(
+ &redis::cmd("PING"),
+ RoutingInfo::MultiNode((
+ MultipleNodeRoutingInfo::AllMasters,
+ Some(ResponsePolicy::OneSucceeded),
+ )),
+ )
+ .await
+ .and_then(|v| String::from_redis_value(&v))?;
+ match pong.as_str() {
+ "PONG" => Ok(()),
+ _ => Err((ErrorKind::ResponseError, "ping request").into()),
+ }
+ }
+
+ fn has_broken(&self, _: &mut Self::Connection) -> bool {
+ false
+ }
+}
diff --git a/lib/warden-stack/src/cache/sentinel.rs b/lib/warden-stack/src/cache/sentinel.rs
new file mode 100644
index 0000000..c9f787a
--- /dev/null
+++ b/lib/warden-stack/src/cache/sentinel.rs
@@ -0,0 +1,65 @@
+use redis::{
+ ErrorKind, IntoConnectionInfo, RedisError,
+ sentinel::{SentinelClient, SentinelNodeConnectionInfo, SentinelServerType},
+};
+use serde::Deserialize;
+use tokio::sync::Mutex;
+
+struct LockedSentinelClient(pub(crate) Mutex<SentinelClient>);
+
+/// ConnectionManager that implements `bb8::ManageConnection` and supports
+/// asynchronous Sentinel connections via `redis::sentinel::SentinelClient`
+pub struct RedisSentinelConnectionManager {
+ client: LockedSentinelClient,
+}
+
+impl RedisSentinelConnectionManager {
+ pub fn new<T: IntoConnectionInfo>(
+ info: Vec<T>,
+ service_name: String,
+ node_connection_info: Option<SentinelNodeConnectionInfo>,
+ ) -> Result<RedisSentinelConnectionManager, RedisError> {
+ Ok(RedisSentinelConnectionManager {
+ client: LockedSentinelClient(Mutex::new(SentinelClient::build(
+ info,
+ service_name,
+ node_connection_info,
+ SentinelServerType::Master,
+ )?)),
+ })
+ }
+}
+
+impl bb8::ManageConnection for RedisSentinelConnectionManager {
+ type Connection = redis::aio::MultiplexedConnection;
+ type Error = RedisError;
+
+ async fn connect(&self) -> Result<Self::Connection, Self::Error> {
+ self.client.0.lock().await.get_async_connection().await
+ }
+
+ async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
+ let pong: String = redis::cmd("PING").query_async(conn).await?;
+ match pong.as_str() {
+ "PONG" => Ok(()),
+ _ => Err((ErrorKind::ResponseError, "ping request").into()),
+ }
+ }
+
+ fn has_broken(&self, _: &mut Self::Connection) -> bool {
+ false
+ }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
+pub struct SentinelConfig {
+ #[serde(rename = "sentinel_service_name")]
+ pub service_name: String,
+ #[serde(default)]
+ pub redis_tls_mode_secure: bool,
+ pub redis_db: Option<i64>,
+ pub redis_username: Option<String>,
+ pub redis_password: Option<String>,
+ #[serde(default)]
+ pub redis_use_resp3: bool,
+}
diff --git a/lib/warden-stack/src/config.rs b/lib/warden-stack/src/config.rs
new file mode 100644
index 0000000..9f42f43
--- /dev/null
+++ b/lib/warden-stack/src/config.rs
@@ -0,0 +1,139 @@
+use serde::Deserialize;
+
+use std::{fmt::Display, sync::Arc};
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct AppConfig {
+ #[serde(skip)]
+ pub name: Arc<str>,
+ #[serde(skip)]
+ pub version: Arc<str>,
+ #[serde(default)]
+ pub env: Environment,
+ #[cfg(feature = "api")]
+ #[serde(default = "default_port")]
+ pub port: u16,
+}
+
+#[cfg(feature = "api")]
+pub(crate) fn default_port() -> u16 {
+ 2210
+}
+
+#[cfg(feature = "tracing")]
+pub(crate) fn default_log() -> String {
+ #[cfg(debug_assertions)]
+ return "debug".into();
+ #[cfg(not(debug_assertions))]
+ "info".into()
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Configuration {
+ pub application: AppConfig,
+ #[cfg(feature = "postgres")]
+ pub database: crate::postgres::PostgresConfig,
+ #[cfg(feature = "cache")]
+ pub cache: crate::cache::CacheConfig,
+ #[serde(default)]
+ pub misc: serde_json::Value,
+ #[cfg(feature = "tracing")]
+ pub monitoring: Monitoring,
+ #[cfg(any(feature = "nats-core", feature = "nats-jetstream"))]
+ pub nats: crate::nats::NatsConfig,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Monitoring {
+ #[serde(rename = "log-level")]
+ #[cfg(feature = "tracing")]
+ #[serde(default = "default_log")]
+ pub log_level: String,
+ #[cfg(feature = "opentelemetry")]
+ #[serde(rename = "opentelemetry-endpoint")]
+ #[serde(default = "default_opentelemetry")]
+ pub opentelemetry_endpoint: Arc<str>,
+ #[cfg(feature = "tracing-loki")]
+ #[serde(rename = "loki-endpoint")]
+ #[serde(default = "default_loki")]
+ pub loki_endpoint: Arc<str>,
+}
+
+#[cfg(feature = "tracing-loki")]
+pub(crate) fn default_loki() -> Arc<str> {
+ "http://localhost:3100".into()
+}
+
+#[cfg(feature = "opentelemetry")]
+pub(crate) fn default_opentelemetry() -> Arc<str> {
+ "http://localhost:4317".into()
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Default)]
+#[cfg_attr(test, derive(serde::Serialize))]
+#[serde(rename_all = "lowercase")]
+pub enum Environment {
+ #[default]
+ Development,
+ Production,
+}
+
+impl Display for Environment {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Environment::Development => "development",
+ Environment::Production => "production",
+ }
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Test that the enum is correctly serialized and deserialized
+ #[test]
+ fn test_environment_serialization() {
+ // Test serialization for Development
+ let dev = Environment::Development;
+ let dev_json = serde_json::to_string(&dev).unwrap();
+ assert_eq!(dev_json, "\"development\"");
+
+ // Test serialization for Production
+ let prod = Environment::Production;
+ let prod_json = serde_json::to_string(&prod).unwrap();
+ assert_eq!(prod_json, "\"production\"");
+
+ // Test deserialization for Development
+ let dev_str = "\"development\"";
+ let deserialized_dev: Environment = serde_json::from_str(dev_str).unwrap();
+ assert_eq!(deserialized_dev, Environment::Development);
+
+ // Test deserialization for Production
+ let prod_str = "\"production\"";
+ let deserialized_prod: Environment = serde_json::from_str(prod_str).unwrap();
+ assert_eq!(deserialized_prod, Environment::Production);
+ }
+
+ // Test Display implementation
+ #[test]
+ fn test_environment_display() {
+ let dev = Environment::Development;
+ assert_eq!(format!("{}", dev), "development");
+
+ let prod = Environment::Production;
+ assert_eq!(format!("{}", prod), "production");
+ }
+
+ #[test]
+ #[cfg(feature = "api")]
+ fn test_port() {
+ let listen_address =
+ std::net::SocketAddr::from((std::net::Ipv6Addr::UNSPECIFIED, default_port()));
+ assert_eq!(listen_address.port(), default_port());
+ }
+}
diff --git a/lib/warden-stack/src/lib.rs b/lib/warden-stack/src/lib.rs
new file mode 100644
index 0000000..efd6862
--- /dev/null
+++ b/lib/warden-stack/src/lib.rs
@@ -0,0 +1,95 @@
+#![cfg_attr(docsrs, feature(doc_cfg))]
+
+#[cfg(feature = "tracing")]
+#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
+pub mod tracing;
+
+#[cfg(feature = "cache")]
+#[cfg_attr(docsrs, doc(cfg(feature = "cache")))]
+pub mod cache;
+
+#[cfg(feature = "cache")]
+pub use redis;
+
+#[cfg(feature = "postgres")]
+pub use sqlx;
+
+#[cfg(any(feature = "nats-core", feature = "nats-jetstream"))]
+pub use async_nats;
+
+#[cfg(feature = "opentelemetry")]
+mod otel {
+ pub use opentelemetry;
+ pub use opentelemetry_http;
+ pub use opentelemetry_otlp;
+ pub use opentelemetry_sdk;
+ pub use opentelemetry_semantic_conventions;
+ pub use tracing_opentelemetry;
+}
+
+#[cfg(feature = "opentelemetry")]
+pub use otel::*;
+
+#[cfg(feature = "postgres")]
+#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
+pub mod postgres;
+
+#[cfg(any(feature = "nats-core", feature = "nats-jetstream"))]
+#[cfg_attr(
+ docsrs,
+ doc(cfg(any(feature = "nats-core", feature = "nats-jetstream")))
+)]
+pub mod nats;
+
+mod config;
+pub use config::*;
+
+#[derive(Clone, bon::Builder)]
+pub struct Services {
+ #[cfg(feature = "postgres")]
+ #[builder(setters(vis = "", name = pg_internal))]
+ pub postgres: Option<sqlx::PgPool>,
+ #[cfg(feature = "cache")]
+ #[builder(setters(vis = "", name = cache_internal))]
+ pub cache: Option<cache::RedisManager>,
+ #[cfg(feature = "nats-core")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "nats-core")))]
+ #[builder(setters(vis = "", name = nats_internal))]
+ /// NATS connection handle
+ pub nats: Option<async_nats::Client>,
+ #[cfg(feature = "nats-jetstream")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "nats-jetstream")))]
+ #[builder(setters(vis = "", name = jetstream_internal))]
+ /// NATS-Jetstream connection handle
+ pub jetstream: Option<async_nats::jetstream::Context>,
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum ServiceError {
+ #[error("service was not initialised")]
+ NotInitialised,
+ #[error("unknown data store error")]
+ Unknown,
+ #[error("invalid config `{0}`")]
+ Configuration(String),
+ #[cfg(feature = "postgres")]
+ #[error(transparent)]
+ /// Postgres error
+ Postgres(#[from] sqlx::Error),
+ #[cfg(feature = "cache")]
+ #[error(transparent)]
+ /// Redis error
+ Cache(#[from] redis::RedisError),
+ #[cfg(feature = "opentelemetry")]
+ #[error(transparent)]
+ /// When creating the tracing layer
+ Opentelemetry(#[from] opentelemetry_sdk::trace::TraceError),
+ #[cfg(any(feature = "nats-core", feature = "nats-jetstream"))]
+ #[error(transparent)]
+ /// NATS error
+ Nats(#[from] async_nats::error::Error<async_nats::ConnectErrorKind>),
+ #[cfg(feature = "tracing-loki")]
+ #[error(transparent)]
+ /// When creating the tracing layer
+ Loki(#[from] tracing_loki::Error),
+}
diff --git a/lib/warden-stack/src/nats.rs b/lib/warden-stack/src/nats.rs
new file mode 100644
index 0000000..952490c
--- /dev/null
+++ b/lib/warden-stack/src/nats.rs
@@ -0,0 +1,61 @@
+use std::sync::Arc;
+
+use serde::Deserialize;
+
+#[derive(Deserialize, Clone, Debug)]
+/// Nats configuration
+pub struct NatsConfig {
+ /// Hosts dsn
+ #[serde(default = "nats")]
+ pub hosts: Arc<[String]>,
+}
+
+pub(crate) fn nats() -> Arc<[String]> {
+ let hosts = vec!["nats://localhost:4222".to_string()];
+ hosts.into()
+}
+
+impl NatsConfig {
+ fn hosts(&self) -> Vec<String> {
+ self.hosts.iter().map(ToString::to_string).collect()
+ }
+}
+
+use crate::{
+ ServiceError, ServicesBuilder,
+ services_builder::{IsUnset, State},
+};
+
+#[cfg(feature = "nats-jetstream")]
+impl<S: State> ServicesBuilder<S> {
+ /// create a Jetstream Context using the provided [NatsConfig]
+ pub async fn nats_jetstream(
+ self,
+ config: &NatsConfig,
+ ) -> Result<ServicesBuilder<crate::services_builder::SetJetstream<S>>, ServiceError>
+ where
+ S::Jetstream: IsUnset,
+ {
+ let hosts = config.hosts();
+ let client = async_nats::connect(hosts).await?;
+
+ Ok(self.jetstream_internal(async_nats::jetstream::new(client)))
+ }
+}
+
+#[cfg(feature = "nats-core")]
+impl<S: State> ServicesBuilder<S> {
+ /// create a NATS connection using the provided [NatsConfig]
+ pub async fn nats(
+ self,
+ config: &NatsConfig,
+ ) -> Result<ServicesBuilder<crate::services_builder::SetNats<S>>, ServiceError>
+ where
+ S::Nats: IsUnset,
+ {
+ let hosts = config.hosts();
+ let client = async_nats::connect(hosts).await?;
+
+ Ok(self.nats_internal(client))
+ }
+}
diff --git a/lib/warden-stack/src/postgres.rs b/lib/warden-stack/src/postgres.rs
new file mode 100644
index 0000000..3264368
--- /dev/null
+++ b/lib/warden-stack/src/postgres.rs
@@ -0,0 +1,137 @@
+use std::sync::Arc;
+
+use secrecy::{ExposeSecret, SecretString};
+use serde::Deserialize;
+use url::Url;
+
+use crate::{
+ ServicesBuilder,
+ services_builder::{IsUnset, SetPostgres, State},
+};
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct PostgresConfig {
+ #[serde(default = "default_pool_size")]
+ pool_size: u32,
+ #[serde(default = "default_port")]
+ port: u32,
+ name: Arc<str>,
+ host: Arc<str>,
+ #[serde(default = "user")]
+ user: Arc<str>,
+ password: SecretString,
+}
+
+fn default_pool_size() -> u32 {
+ 100
+}
+
+fn user() -> Arc<str> {
+ "postgres".into()
+}
+
+fn default_port() -> u32 {
+ 5432
+}
+
+impl PostgresConfig {
+ // Getter for size
+ pub fn pool_size(&self) -> u32 {
+ self.pool_size
+ }
+
+ // Getter for port
+ pub fn port(&self) -> u32 {
+ self.port
+ }
+
+ // Getter for name
+ pub fn name(&self) -> &str {
+ self.name.as_ref()
+ }
+
+ // Getter for host
+ pub fn host(&self) -> &str {
+ self.host.as_ref()
+ }
+
+ // Getter for username
+ pub fn username(&self) -> &str {
+ self.user.as_ref()
+ }
+
+ // Getter for password (you may want to return a reference or handle it differently)
+ pub fn password(&self) -> &SecretString {
+ &self.password
+ }
+
+ pub(crate) fn connection_string(&self) -> Result<Url, crate::ServiceError> {
+ Url::parse(&format!(
+ "postgres://{}:{}@{}:{}/{}",
+ self.user,
+ self.password.expose_secret(),
+ self.host,
+ self.port,
+ self.name
+ ))
+ .map_err(|e| crate::ServiceError::Configuration(e.to_string()))
+ }
+}
+
+impl<S: State> ServicesBuilder<S> {
+ pub async fn postgres(
+ self,
+ config: &PostgresConfig,
+ ) -> Result<ServicesBuilder<SetPostgres<S>>, crate::ServiceError>
+ where
+ S::Postgres: IsUnset,
+ {
+ let pg = sqlx::postgres::PgPoolOptions::new()
+ // The default connection limit for a Postgres server is 100 connections, with 3 reserved for superusers.
+ //
+ // If you're deploying your application with multiple replicas, then the total
+ // across all replicas should not exceed the Postgres connection limit
+ // (max_connections postgresql.conf).
+ .max_connections(config.pool_size)
+ .connect(config.connection_string()?.as_ref())
+ .await?;
+ Ok(self.pg_internal(pg))
+ }
+}
+
+#[cfg(all(test, target_os = "linux"))]
+mod test {
+ use super::*;
+ use crate::Services;
+
+ #[tokio::test]
+ async fn docker_stack_db() {
+ let port = default_port();
+ let name = "";
+ let host = "localhost";
+ let user = user();
+ let pool_size = default_pool_size();
+ let password = "postgres";
+
+ let config = PostgresConfig {
+ pool_size,
+ port,
+ name: name.into(),
+ host: host.into(),
+ user: user.clone(),
+ password: secrecy::SecretString::new(password.into()),
+ };
+
+ assert_eq!(config.name(), name);
+ assert_eq!(config.pool_size(), pool_size);
+ assert_eq!(config.username(), user.as_ref());
+ assert_eq!(config.host(), host);
+ assert_eq!(config.port(), port);
+
+ assert_eq!(config.password().expose_secret(), password);
+
+ let service = Services::builder().postgres(&config).await;
+
+ assert!(service.is_ok());
+ }
+}
diff --git a/lib/warden-stack/src/tracing.rs b/lib/warden-stack/src/tracing.rs
new file mode 100644
index 0000000..1a40f4b
--- /dev/null
+++ b/lib/warden-stack/src/tracing.rs
@@ -0,0 +1,66 @@
+#[cfg(feature = "opentelemetry")]
+pub mod telemetry;
+
+#[cfg(feature = "opentelemetry")]
+pub use opentelemetry_sdk::trace::SdkTracerProvider;
+
+#[cfg(feature = "tracing-loki")]
+mod loki;
+
+use tracing_subscriber::{
+ EnvFilter, Layer, Registry, layer::SubscriberExt, util::SubscriberInitExt,
+};
+
+/// Telemetry handle
+#[derive(bon::Builder)]
+#[builder(finish_fn(vis = "", name = build_internal))]
+pub struct Tracing {
+ #[builder(field = vec![tracing_subscriber::fmt::layer().boxed()])]
+ layers: Vec<Box<dyn Layer<Registry> + Sync + Send>>,
+ #[cfg(feature = "tracing-loki")]
+ #[builder(setters(vis = "", name = loki_internal))]
+ pub loki_task: tracing_loki::BackgroundTask,
+ #[cfg(feature = "opentelemetry")]
+ #[builder(setters(vis = "", name = otel_internal))]
+ pub otel_provider: opentelemetry_sdk::trace::SdkTracerProvider,
+}
+
+// Define a custom finishing function as a method on the `UserBuilder`.
+// The builder's state must implement the `IsComplete` trait.
+// See details about it in the tip below this example.
+impl<S: tracing_builder::IsComplete> TracingBuilder<S> {
+ pub fn build(self, config: &crate::Monitoring) -> Tracing {
+ // Delegate to `build_internal()` to get the instance of user.
+ let mut tracing = self.build_internal();
+
+ let layers = std::mem::take(&mut tracing.layers);
+ tracing_subscriber::registry()
+ .with(layers)
+ .with(
+ EnvFilter::try_from_default_env()
+ .unwrap_or_else(|_| config.log_level.to_string().into()),
+ )
+ .try_init()
+ .ok();
+ tracing
+ }
+}
+
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+//
+// #[test]
+// fn build() {
+// let builder = Tracing::builder().build();
+// let level = crate::Monitoring {
+// log_level: "info".to_string(),
+// #[cfg(feature = "opentelemetry")]
+// opentelemetry_endpoint: "http://localhost:4317".into(),
+// #[cfg(feature = "tracing-loki")]
+// loki_endpoint: "http://localhost:3100".into(),
+// };
+// builder.init(&level);
+// builder.loki_task
+// }
+// }
diff --git a/lib/warden-stack/src/tracing/loki.rs b/lib/warden-stack/src/tracing/loki.rs
new file mode 100644
index 0000000..cbf4e40
--- /dev/null
+++ b/lib/warden-stack/src/tracing/loki.rs
@@ -0,0 +1,29 @@
+use crate::Monitoring;
+
+use super::TracingBuilder;
+use super::tracing_builder::{IsUnset, SetLokiTask, State};
+use tracing_subscriber::Layer;
+
+impl<S: State> TracingBuilder<S> {
+ pub fn loki(
+ mut self,
+ config: &crate::AppConfig,
+ monitoring: &Monitoring,
+ ) -> Result<TracingBuilder<SetLokiTask<S>>, crate::ServiceError>
+ where
+ S::LokiTask: IsUnset,
+ {
+ use std::str::FromStr;
+ let url = FromStr::from_str(&monitoring.loki_endpoint.as_ref())
+ .map_err(|_e| crate::ServiceError::Unknown)?;
+
+ let (layer, task) = tracing_loki::builder()
+ .label("service_name", config.name.as_ref())?
+ .extra_field("pid", format!("{}", std::process::id()))?
+ .build_url(url)?;
+
+ self.layers.push(layer.boxed());
+
+ Ok(self.loki_internal(task))
+ }
+}
diff --git a/lib/warden-stack/src/tracing/telemetry.rs b/lib/warden-stack/src/tracing/telemetry.rs
new file mode 100644
index 0000000..b024937
--- /dev/null
+++ b/lib/warden-stack/src/tracing/telemetry.rs
@@ -0,0 +1,137 @@
+#[cfg(any(feature = "nats-jetstream", feature = "nats-core"))]
+pub mod nats {
+ pub mod extractor {
+ pub struct HeaderMap<'a>(pub &'a async_nats::HeaderMap);
+
+ impl opentelemetry::propagation::Extractor for HeaderMap<'_> {
+ fn get(&self, key: &str) -> Option<&str> {
+ self.0
+ .get(async_nats::header::IntoHeaderName::into_header_name(key))
+ .map(|value| value.as_str())
+ }
+
+ fn keys(&self) -> Vec<&str> {
+ self.0.iter().map(|(n, _v)| n.as_ref()).collect()
+ }
+ }
+ }
+
+ pub mod injector {
+ pub struct HeaderMap<'a>(pub &'a mut async_nats::HeaderMap);
+
+ impl opentelemetry::propagation::Injector for HeaderMap<'_> {
+ fn set(&mut self, key: &str, value: String) {
+ self.0.insert(key, value);
+ }
+ }
+ }
+}
+
+#[cfg(feature = "opentelemetry-tonic")]
+pub mod tonic {
+ pub mod extractor {
+ pub struct MetadataMap<'a>(pub &'a tonic::metadata::MetadataMap);
+ impl opentelemetry::propagation::Extractor for MetadataMap<'_> {
+ fn get(&self, key: &str) -> Option<&str> {
+ self.0.get(key).and_then(|metadata| metadata.to_str().ok())
+ }
+
+ /// Collect all the keys from the MetadataMap.
+ fn keys(&self) -> Vec<&str> {
+ self.0
+ .keys()
+ .map(|key| match key {
+ tonic::metadata::KeyRef::Ascii(v) => v.as_str(),
+ tonic::metadata::KeyRef::Binary(v) => v.as_str(),
+ })
+ .collect::<Vec<_>>()
+ }
+ }
+ }
+
+ pub mod injector {
+ pub struct MetadataMap<'a>(pub &'a mut tonic::metadata::MetadataMap);
+
+ impl opentelemetry::propagation::Injector for MetadataMap<'_> {
+ /// Set a key and value in the MetadataMap. Does nothing if the key or value are not valid inputs
+ fn set(&mut self, key: &str, value: String) {
+ if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.as_bytes()) {
+ if let Ok(val) = tonic::metadata::MetadataValue::try_from(&value) {
+ self.0.insert(key, val);
+ }
+ }
+ }
+ }
+ }
+}
+
+use crate::Monitoring;
+
+use super::TracingBuilder;
+use super::tracing_builder::{IsUnset, SetOtelProvider, State};
+use tracing_subscriber::Layer;
+
+impl<S: State> TracingBuilder<S> {
+ pub fn opentelemetry(
+ mut self,
+ config: &crate::AppConfig,
+ monitoring: &Monitoring,
+ ) -> Result<TracingBuilder<SetOtelProvider<S>>, crate::ServiceError>
+ where
+ S::OtelProvider: IsUnset,
+ {
+ use opentelemetry::{
+ KeyValue,
+ global::{self},
+ trace::TracerProvider,
+ };
+ use opentelemetry_otlp::WithExportConfig;
+ use opentelemetry_sdk::{
+ Resource,
+ trace::{RandomIdGenerator, Sampler, SdkTracerProvider},
+ };
+ use opentelemetry_semantic_conventions::{
+ SCHEMA_URL,
+ resource::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_NAME, SERVICE_VERSION},
+ };
+ use tracing_opentelemetry::OpenTelemetryLayer;
+
+ global::set_text_map_propagator(
+ opentelemetry_sdk::propagation::TraceContextPropagator::new(),
+ );
+
+ let resource = Resource::builder()
+ .with_schema_url(
+ [
+ KeyValue::new(SERVICE_NAME, config.name.to_owned()),
+ KeyValue::new(SERVICE_VERSION, config.version.to_owned()),
+ KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, config.env.to_string()),
+ ],
+ SCHEMA_URL,
+ )
+ .with_service_name(config.name.to_string())
+ .build();
+
+ let exporter = opentelemetry_otlp::SpanExporter::builder()
+ .with_tonic()
+ .with_endpoint(monitoring.opentelemetry_endpoint.as_ref())
+ .build()
+ .unwrap();
+
+ let provider = SdkTracerProvider::builder()
+ .with_batch_exporter(exporter)
+ .with_resource(resource)
+ .with_id_generator(RandomIdGenerator::default())
+ .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(
+ 1.0,
+ ))))
+ .build();
+
+ global::set_tracer_provider(provider.clone());
+
+ let layer = OpenTelemetryLayer::new(provider.tracer(config.name.as_ref().to_string()));
+ self.layers.push(layer.boxed());
+
+ Ok(self.otel_internal(provider))
+ }
+}