├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── assets └── img │ ├── death_screen.png │ ├── sim_running_screen.png │ └── start_screen.png ├── output.txt └── src ├── main.rs └── tui.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | debug.log* 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "allocator-api2" 22 | version = "0.2.20" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" 25 | 26 | [[package]] 27 | name = "anstream" 28 | version = "0.6.18" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 31 | dependencies = [ 32 | "anstyle", 33 | "anstyle-parse", 34 | "anstyle-query", 35 | "anstyle-wincon", 36 | "colorchoice", 37 | "is_terminal_polyfill", 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle" 43 | version = "1.0.10" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 46 | 47 | [[package]] 48 | name = "anstyle-parse" 49 | version = "0.2.6" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 52 | dependencies = [ 53 | "utf8parse", 54 | ] 55 | 56 | [[package]] 57 | name = "anstyle-query" 58 | version = "1.1.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 61 | dependencies = [ 62 | "windows-sys 0.59.0", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-wincon" 67 | version = "3.0.6" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 70 | dependencies = [ 71 | "anstyle", 72 | "windows-sys 0.59.0", 73 | ] 74 | 75 | [[package]] 76 | name = "arc-swap" 77 | version = "1.7.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 80 | 81 | [[package]] 82 | name = "async-trait" 83 | version = "0.1.83" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 86 | dependencies = [ 87 | "proc-macro2", 88 | "quote", 89 | "syn 2.0.87", 90 | ] 91 | 92 | [[package]] 93 | name = "autocfg" 94 | version = "1.4.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 97 | 98 | [[package]] 99 | name = "backtrace" 100 | version = "0.3.71" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 103 | dependencies = [ 104 | "addr2line", 105 | "cc", 106 | "cfg-if", 107 | "libc", 108 | "miniz_oxide", 109 | "object", 110 | "rustc-demangle", 111 | ] 112 | 113 | [[package]] 114 | name = "bitflags" 115 | version = "2.6.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 118 | 119 | [[package]] 120 | name = "byteorder" 121 | version = "1.5.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 124 | 125 | [[package]] 126 | name = "bytes" 127 | version = "1.8.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 130 | 131 | [[package]] 132 | name = "cassowary" 133 | version = "0.3.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 136 | 137 | [[package]] 138 | name = "castaway" 139 | version = "0.2.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 142 | dependencies = [ 143 | "rustversion", 144 | ] 145 | 146 | [[package]] 147 | name = "cc" 148 | version = "1.2.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" 151 | dependencies = [ 152 | "shlex", 153 | ] 154 | 155 | [[package]] 156 | name = "cfg-if" 157 | version = "1.0.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 160 | 161 | [[package]] 162 | name = "clap" 163 | version = "4.5.21" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 166 | dependencies = [ 167 | "clap_builder", 168 | "clap_derive", 169 | ] 170 | 171 | [[package]] 172 | name = "clap_builder" 173 | version = "4.5.21" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 176 | dependencies = [ 177 | "anstream", 178 | "anstyle", 179 | "clap_lex", 180 | "strsim", 181 | ] 182 | 183 | [[package]] 184 | name = "clap_derive" 185 | version = "4.5.18" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 188 | dependencies = [ 189 | "heck", 190 | "proc-macro2", 191 | "quote", 192 | "syn 2.0.87", 193 | ] 194 | 195 | [[package]] 196 | name = "clap_lex" 197 | version = "0.7.3" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 200 | 201 | [[package]] 202 | name = "color-eyre" 203 | version = "0.6.3" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 206 | dependencies = [ 207 | "backtrace", 208 | "color-spantrace", 209 | "eyre", 210 | "indenter", 211 | "once_cell", 212 | "owo-colors", 213 | "tracing-error", 214 | ] 215 | 216 | [[package]] 217 | name = "color-spantrace" 218 | version = "0.2.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 221 | dependencies = [ 222 | "once_cell", 223 | "owo-colors", 224 | "tracing-core", 225 | "tracing-error", 226 | ] 227 | 228 | [[package]] 229 | name = "colorchoice" 230 | version = "1.0.3" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 233 | 234 | [[package]] 235 | name = "combine" 236 | version = "4.6.7" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 239 | dependencies = [ 240 | "bytes", 241 | "futures-core", 242 | "memchr", 243 | "pin-project-lite", 244 | "tokio", 245 | "tokio-util", 246 | ] 247 | 248 | [[package]] 249 | name = "compact_str" 250 | version = "0.8.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" 253 | dependencies = [ 254 | "castaway", 255 | "cfg-if", 256 | "itoa", 257 | "rustversion", 258 | "ryu", 259 | "static_assertions", 260 | ] 261 | 262 | [[package]] 263 | name = "crossbeam-channel" 264 | version = "0.5.13" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 267 | dependencies = [ 268 | "crossbeam-utils", 269 | ] 270 | 271 | [[package]] 272 | name = "crossbeam-utils" 273 | version = "0.8.20" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 276 | 277 | [[package]] 278 | name = "crossterm" 279 | version = "0.28.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 282 | dependencies = [ 283 | "bitflags", 284 | "crossterm_winapi", 285 | "mio", 286 | "parking_lot", 287 | "rustix", 288 | "signal-hook", 289 | "signal-hook-mio", 290 | "winapi", 291 | ] 292 | 293 | [[package]] 294 | name = "crossterm_winapi" 295 | version = "0.9.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 298 | dependencies = [ 299 | "winapi", 300 | ] 301 | 302 | [[package]] 303 | name = "darling" 304 | version = "0.20.10" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 307 | dependencies = [ 308 | "darling_core", 309 | "darling_macro", 310 | ] 311 | 312 | [[package]] 313 | name = "darling_core" 314 | version = "0.20.10" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 317 | dependencies = [ 318 | "fnv", 319 | "ident_case", 320 | "proc-macro2", 321 | "quote", 322 | "strsim", 323 | "syn 2.0.87", 324 | ] 325 | 326 | [[package]] 327 | name = "darling_macro" 328 | version = "0.20.10" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 331 | dependencies = [ 332 | "darling_core", 333 | "quote", 334 | "syn 2.0.87", 335 | ] 336 | 337 | [[package]] 338 | name = "deranged" 339 | version = "0.3.11" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 342 | dependencies = [ 343 | "powerfmt", 344 | ] 345 | 346 | [[package]] 347 | name = "diff" 348 | version = "0.1.13" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 351 | 352 | [[package]] 353 | name = "displaydoc" 354 | version = "0.2.5" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 357 | dependencies = [ 358 | "proc-macro2", 359 | "quote", 360 | "syn 2.0.87", 361 | ] 362 | 363 | [[package]] 364 | name = "dst" 365 | version = "0.1.0" 366 | dependencies = [ 367 | "async-trait", 368 | "clap", 369 | "color-eyre", 370 | "futures", 371 | "rand", 372 | "rand_chacha", 373 | "ratatui", 374 | "rdkafka", 375 | "redis", 376 | "tokio", 377 | "tracing", 378 | "tracing-appender", 379 | "tracing-subscriber", 380 | ] 381 | 382 | [[package]] 383 | name = "either" 384 | version = "1.13.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 387 | 388 | [[package]] 389 | name = "equivalent" 390 | version = "1.0.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 393 | 394 | [[package]] 395 | name = "errno" 396 | version = "0.3.9" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 399 | dependencies = [ 400 | "libc", 401 | "windows-sys 0.52.0", 402 | ] 403 | 404 | [[package]] 405 | name = "eyre" 406 | version = "0.6.12" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 409 | dependencies = [ 410 | "indenter", 411 | "once_cell", 412 | ] 413 | 414 | [[package]] 415 | name = "fnv" 416 | version = "1.0.7" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 419 | 420 | [[package]] 421 | name = "foldhash" 422 | version = "0.1.3" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" 425 | 426 | [[package]] 427 | name = "form_urlencoded" 428 | version = "1.2.1" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 431 | dependencies = [ 432 | "percent-encoding", 433 | ] 434 | 435 | [[package]] 436 | name = "futures" 437 | version = "0.3.31" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 440 | dependencies = [ 441 | "futures-channel", 442 | "futures-core", 443 | "futures-executor", 444 | "futures-io", 445 | "futures-sink", 446 | "futures-task", 447 | "futures-util", 448 | ] 449 | 450 | [[package]] 451 | name = "futures-channel" 452 | version = "0.3.31" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 455 | dependencies = [ 456 | "futures-core", 457 | "futures-sink", 458 | ] 459 | 460 | [[package]] 461 | name = "futures-core" 462 | version = "0.3.31" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 465 | 466 | [[package]] 467 | name = "futures-executor" 468 | version = "0.3.31" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 471 | dependencies = [ 472 | "futures-core", 473 | "futures-task", 474 | "futures-util", 475 | ] 476 | 477 | [[package]] 478 | name = "futures-io" 479 | version = "0.3.31" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 482 | 483 | [[package]] 484 | name = "futures-macro" 485 | version = "0.3.31" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 488 | dependencies = [ 489 | "proc-macro2", 490 | "quote", 491 | "syn 2.0.87", 492 | ] 493 | 494 | [[package]] 495 | name = "futures-sink" 496 | version = "0.3.31" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 499 | 500 | [[package]] 501 | name = "futures-task" 502 | version = "0.3.31" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 505 | 506 | [[package]] 507 | name = "futures-util" 508 | version = "0.3.31" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 511 | dependencies = [ 512 | "futures-channel", 513 | "futures-core", 514 | "futures-io", 515 | "futures-macro", 516 | "futures-sink", 517 | "futures-task", 518 | "memchr", 519 | "pin-project-lite", 520 | "pin-utils", 521 | "slab", 522 | ] 523 | 524 | [[package]] 525 | name = "getrandom" 526 | version = "0.2.15" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 529 | dependencies = [ 530 | "cfg-if", 531 | "libc", 532 | "wasi", 533 | ] 534 | 535 | [[package]] 536 | name = "gimli" 537 | version = "0.28.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 540 | 541 | [[package]] 542 | name = "hashbrown" 543 | version = "0.15.1" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 546 | dependencies = [ 547 | "allocator-api2", 548 | "equivalent", 549 | "foldhash", 550 | ] 551 | 552 | [[package]] 553 | name = "heck" 554 | version = "0.5.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 557 | 558 | [[package]] 559 | name = "hermit-abi" 560 | version = "0.3.9" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 563 | 564 | [[package]] 565 | name = "icu_collections" 566 | version = "1.5.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 569 | dependencies = [ 570 | "displaydoc", 571 | "yoke", 572 | "zerofrom", 573 | "zerovec", 574 | ] 575 | 576 | [[package]] 577 | name = "icu_locid" 578 | version = "1.5.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 581 | dependencies = [ 582 | "displaydoc", 583 | "litemap", 584 | "tinystr", 585 | "writeable", 586 | "zerovec", 587 | ] 588 | 589 | [[package]] 590 | name = "icu_locid_transform" 591 | version = "1.5.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 594 | dependencies = [ 595 | "displaydoc", 596 | "icu_locid", 597 | "icu_locid_transform_data", 598 | "icu_provider", 599 | "tinystr", 600 | "zerovec", 601 | ] 602 | 603 | [[package]] 604 | name = "icu_locid_transform_data" 605 | version = "1.5.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 608 | 609 | [[package]] 610 | name = "icu_normalizer" 611 | version = "1.5.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 614 | dependencies = [ 615 | "displaydoc", 616 | "icu_collections", 617 | "icu_normalizer_data", 618 | "icu_properties", 619 | "icu_provider", 620 | "smallvec", 621 | "utf16_iter", 622 | "utf8_iter", 623 | "write16", 624 | "zerovec", 625 | ] 626 | 627 | [[package]] 628 | name = "icu_normalizer_data" 629 | version = "1.5.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 632 | 633 | [[package]] 634 | name = "icu_properties" 635 | version = "1.5.1" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 638 | dependencies = [ 639 | "displaydoc", 640 | "icu_collections", 641 | "icu_locid_transform", 642 | "icu_properties_data", 643 | "icu_provider", 644 | "tinystr", 645 | "zerovec", 646 | ] 647 | 648 | [[package]] 649 | name = "icu_properties_data" 650 | version = "1.5.0" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 653 | 654 | [[package]] 655 | name = "icu_provider" 656 | version = "1.5.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 659 | dependencies = [ 660 | "displaydoc", 661 | "icu_locid", 662 | "icu_provider_macros", 663 | "stable_deref_trait", 664 | "tinystr", 665 | "writeable", 666 | "yoke", 667 | "zerofrom", 668 | "zerovec", 669 | ] 670 | 671 | [[package]] 672 | name = "icu_provider_macros" 673 | version = "1.5.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 676 | dependencies = [ 677 | "proc-macro2", 678 | "quote", 679 | "syn 2.0.87", 680 | ] 681 | 682 | [[package]] 683 | name = "ident_case" 684 | version = "1.0.1" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 687 | 688 | [[package]] 689 | name = "idna" 690 | version = "1.0.3" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 693 | dependencies = [ 694 | "idna_adapter", 695 | "smallvec", 696 | "utf8_iter", 697 | ] 698 | 699 | [[package]] 700 | name = "idna_adapter" 701 | version = "1.2.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 704 | dependencies = [ 705 | "icu_normalizer", 706 | "icu_properties", 707 | ] 708 | 709 | [[package]] 710 | name = "indenter" 711 | version = "0.3.3" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 714 | 715 | [[package]] 716 | name = "indexmap" 717 | version = "2.6.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 720 | dependencies = [ 721 | "equivalent", 722 | "hashbrown", 723 | ] 724 | 725 | [[package]] 726 | name = "indoc" 727 | version = "2.0.5" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 730 | 731 | [[package]] 732 | name = "instability" 733 | version = "0.3.3" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" 736 | dependencies = [ 737 | "darling", 738 | "indoc", 739 | "pretty_assertions", 740 | "proc-macro2", 741 | "quote", 742 | "syn 2.0.87", 743 | ] 744 | 745 | [[package]] 746 | name = "is_terminal_polyfill" 747 | version = "1.70.1" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 750 | 751 | [[package]] 752 | name = "itertools" 753 | version = "0.13.0" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 756 | dependencies = [ 757 | "either", 758 | ] 759 | 760 | [[package]] 761 | name = "itoa" 762 | version = "1.0.11" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 765 | 766 | [[package]] 767 | name = "lazy_static" 768 | version = "1.5.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 771 | 772 | [[package]] 773 | name = "libc" 774 | version = "0.2.162" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 777 | 778 | [[package]] 779 | name = "libz-sys" 780 | version = "1.1.20" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" 783 | dependencies = [ 784 | "cc", 785 | "libc", 786 | "pkg-config", 787 | "vcpkg", 788 | ] 789 | 790 | [[package]] 791 | name = "linux-raw-sys" 792 | version = "0.4.14" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 795 | 796 | [[package]] 797 | name = "litemap" 798 | version = "0.7.3" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 801 | 802 | [[package]] 803 | name = "lock_api" 804 | version = "0.4.12" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 807 | dependencies = [ 808 | "autocfg", 809 | "scopeguard", 810 | ] 811 | 812 | [[package]] 813 | name = "log" 814 | version = "0.4.22" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 817 | 818 | [[package]] 819 | name = "lru" 820 | version = "0.12.5" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 823 | dependencies = [ 824 | "hashbrown", 825 | ] 826 | 827 | [[package]] 828 | name = "memchr" 829 | version = "2.7.4" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 832 | 833 | [[package]] 834 | name = "miniz_oxide" 835 | version = "0.7.4" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 838 | dependencies = [ 839 | "adler", 840 | ] 841 | 842 | [[package]] 843 | name = "mio" 844 | version = "1.0.2" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 847 | dependencies = [ 848 | "hermit-abi", 849 | "libc", 850 | "log", 851 | "wasi", 852 | "windows-sys 0.52.0", 853 | ] 854 | 855 | [[package]] 856 | name = "nu-ansi-term" 857 | version = "0.46.0" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 860 | dependencies = [ 861 | "overload", 862 | "winapi", 863 | ] 864 | 865 | [[package]] 866 | name = "num-bigint" 867 | version = "0.4.6" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 870 | dependencies = [ 871 | "num-integer", 872 | "num-traits", 873 | ] 874 | 875 | [[package]] 876 | name = "num-conv" 877 | version = "0.1.0" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 880 | 881 | [[package]] 882 | name = "num-integer" 883 | version = "0.1.46" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 886 | dependencies = [ 887 | "num-traits", 888 | ] 889 | 890 | [[package]] 891 | name = "num-traits" 892 | version = "0.2.19" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 895 | dependencies = [ 896 | "autocfg", 897 | ] 898 | 899 | [[package]] 900 | name = "num_enum" 901 | version = "0.5.11" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" 904 | dependencies = [ 905 | "num_enum_derive", 906 | ] 907 | 908 | [[package]] 909 | name = "num_enum_derive" 910 | version = "0.5.11" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" 913 | dependencies = [ 914 | "proc-macro-crate", 915 | "proc-macro2", 916 | "quote", 917 | "syn 1.0.109", 918 | ] 919 | 920 | [[package]] 921 | name = "num_threads" 922 | version = "0.1.7" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 925 | dependencies = [ 926 | "libc", 927 | ] 928 | 929 | [[package]] 930 | name = "object" 931 | version = "0.32.2" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 934 | dependencies = [ 935 | "memchr", 936 | ] 937 | 938 | [[package]] 939 | name = "once_cell" 940 | version = "1.20.2" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 943 | 944 | [[package]] 945 | name = "overload" 946 | version = "0.1.1" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 949 | 950 | [[package]] 951 | name = "owo-colors" 952 | version = "3.5.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 955 | 956 | [[package]] 957 | name = "parking_lot" 958 | version = "0.12.3" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 961 | dependencies = [ 962 | "lock_api", 963 | "parking_lot_core", 964 | ] 965 | 966 | [[package]] 967 | name = "parking_lot_core" 968 | version = "0.9.10" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 971 | dependencies = [ 972 | "cfg-if", 973 | "libc", 974 | "redox_syscall", 975 | "smallvec", 976 | "windows-targets", 977 | ] 978 | 979 | [[package]] 980 | name = "paste" 981 | version = "1.0.15" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 984 | 985 | [[package]] 986 | name = "percent-encoding" 987 | version = "2.3.1" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 990 | 991 | [[package]] 992 | name = "pin-project-lite" 993 | version = "0.2.15" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 996 | 997 | [[package]] 998 | name = "pin-utils" 999 | version = "0.1.0" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1002 | 1003 | [[package]] 1004 | name = "pkg-config" 1005 | version = "0.3.31" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1008 | 1009 | [[package]] 1010 | name = "powerfmt" 1011 | version = "0.2.0" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1014 | 1015 | [[package]] 1016 | name = "ppv-lite86" 1017 | version = "0.2.20" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1020 | dependencies = [ 1021 | "zerocopy", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "pretty_assertions" 1026 | version = "1.4.1" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" 1029 | dependencies = [ 1030 | "diff", 1031 | "yansi", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "proc-macro-crate" 1036 | version = "1.3.1" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 1039 | dependencies = [ 1040 | "once_cell", 1041 | "toml_edit", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "proc-macro2" 1046 | version = "1.0.89" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 1049 | dependencies = [ 1050 | "unicode-ident", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "quote" 1055 | version = "1.0.37" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1058 | dependencies = [ 1059 | "proc-macro2", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "rand" 1064 | version = "0.8.5" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1067 | dependencies = [ 1068 | "libc", 1069 | "rand_chacha", 1070 | "rand_core", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "rand_chacha" 1075 | version = "0.3.1" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1078 | dependencies = [ 1079 | "ppv-lite86", 1080 | "rand_core", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "rand_core" 1085 | version = "0.6.4" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1088 | dependencies = [ 1089 | "getrandom", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "ratatui" 1094 | version = "0.29.0" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 1097 | dependencies = [ 1098 | "bitflags", 1099 | "cassowary", 1100 | "compact_str", 1101 | "crossterm", 1102 | "indoc", 1103 | "instability", 1104 | "itertools", 1105 | "lru", 1106 | "paste", 1107 | "strum", 1108 | "time", 1109 | "unicode-segmentation", 1110 | "unicode-truncate", 1111 | "unicode-width 0.2.0", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "rdkafka" 1116 | version = "0.36.2" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "1beea247b9a7600a81d4cc33f659ce1a77e1988323d7d2809c7ed1c21f4c316d" 1119 | dependencies = [ 1120 | "futures-channel", 1121 | "futures-util", 1122 | "libc", 1123 | "log", 1124 | "rdkafka-sys", 1125 | "serde", 1126 | "serde_derive", 1127 | "serde_json", 1128 | "slab", 1129 | "tokio", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "rdkafka-sys" 1134 | version = "4.7.0+2.3.0" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "55e0d2f9ba6253f6ec72385e453294f8618e9e15c2c6aba2a5c01ccf9622d615" 1137 | dependencies = [ 1138 | "libc", 1139 | "libz-sys", 1140 | "num_enum", 1141 | "pkg-config", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "redis" 1146 | version = "0.27.5" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" 1149 | dependencies = [ 1150 | "arc-swap", 1151 | "async-trait", 1152 | "bytes", 1153 | "combine", 1154 | "futures-util", 1155 | "itoa", 1156 | "num-bigint", 1157 | "percent-encoding", 1158 | "pin-project-lite", 1159 | "ryu", 1160 | "sha1_smol", 1161 | "socket2", 1162 | "tokio", 1163 | "tokio-util", 1164 | "url", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "redox_syscall" 1169 | version = "0.5.7" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 1172 | dependencies = [ 1173 | "bitflags", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "rustc-demangle" 1178 | version = "0.1.24" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1181 | 1182 | [[package]] 1183 | name = "rustix" 1184 | version = "0.38.40" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" 1187 | dependencies = [ 1188 | "bitflags", 1189 | "errno", 1190 | "libc", 1191 | "linux-raw-sys", 1192 | "windows-sys 0.52.0", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "rustversion" 1197 | version = "1.0.18" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 1200 | 1201 | [[package]] 1202 | name = "ryu" 1203 | version = "1.0.18" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1206 | 1207 | [[package]] 1208 | name = "scopeguard" 1209 | version = "1.2.0" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1212 | 1213 | [[package]] 1214 | name = "serde" 1215 | version = "1.0.215" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 1218 | dependencies = [ 1219 | "serde_derive", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "serde_derive" 1224 | version = "1.0.215" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 1227 | dependencies = [ 1228 | "proc-macro2", 1229 | "quote", 1230 | "syn 2.0.87", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "serde_json" 1235 | version = "1.0.132" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 1238 | dependencies = [ 1239 | "itoa", 1240 | "memchr", 1241 | "ryu", 1242 | "serde", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "sha1_smol" 1247 | version = "1.0.1" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" 1250 | 1251 | [[package]] 1252 | name = "sharded-slab" 1253 | version = "0.1.7" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1256 | dependencies = [ 1257 | "lazy_static", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "shlex" 1262 | version = "1.3.0" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1265 | 1266 | [[package]] 1267 | name = "signal-hook" 1268 | version = "0.3.17" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1271 | dependencies = [ 1272 | "libc", 1273 | "signal-hook-registry", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "signal-hook-mio" 1278 | version = "0.2.4" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1281 | dependencies = [ 1282 | "libc", 1283 | "mio", 1284 | "signal-hook", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "signal-hook-registry" 1289 | version = "1.4.2" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1292 | dependencies = [ 1293 | "libc", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "slab" 1298 | version = "0.4.9" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1301 | dependencies = [ 1302 | "autocfg", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "smallvec" 1307 | version = "1.13.2" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1310 | 1311 | [[package]] 1312 | name = "socket2" 1313 | version = "0.5.7" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1316 | dependencies = [ 1317 | "libc", 1318 | "windows-sys 0.52.0", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "stable_deref_trait" 1323 | version = "1.2.0" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1326 | 1327 | [[package]] 1328 | name = "static_assertions" 1329 | version = "1.1.0" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1332 | 1333 | [[package]] 1334 | name = "strsim" 1335 | version = "0.11.1" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1338 | 1339 | [[package]] 1340 | name = "strum" 1341 | version = "0.26.3" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1344 | dependencies = [ 1345 | "strum_macros", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "strum_macros" 1350 | version = "0.26.4" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1353 | dependencies = [ 1354 | "heck", 1355 | "proc-macro2", 1356 | "quote", 1357 | "rustversion", 1358 | "syn 2.0.87", 1359 | ] 1360 | 1361 | [[package]] 1362 | name = "syn" 1363 | version = "1.0.109" 1364 | source = "registry+https://github.com/rust-lang/crates.io-index" 1365 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1366 | dependencies = [ 1367 | "proc-macro2", 1368 | "quote", 1369 | "unicode-ident", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "syn" 1374 | version = "2.0.87" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 1377 | dependencies = [ 1378 | "proc-macro2", 1379 | "quote", 1380 | "unicode-ident", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "synstructure" 1385 | version = "0.13.1" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1388 | dependencies = [ 1389 | "proc-macro2", 1390 | "quote", 1391 | "syn 2.0.87", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "thiserror" 1396 | version = "1.0.69" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1399 | dependencies = [ 1400 | "thiserror-impl", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "thiserror-impl" 1405 | version = "1.0.69" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1408 | dependencies = [ 1409 | "proc-macro2", 1410 | "quote", 1411 | "syn 2.0.87", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "thread_local" 1416 | version = "1.1.8" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1419 | dependencies = [ 1420 | "cfg-if", 1421 | "once_cell", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "time" 1426 | version = "0.3.36" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1429 | dependencies = [ 1430 | "deranged", 1431 | "itoa", 1432 | "libc", 1433 | "num-conv", 1434 | "num_threads", 1435 | "powerfmt", 1436 | "serde", 1437 | "time-core", 1438 | "time-macros", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "time-core" 1443 | version = "0.1.2" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1446 | 1447 | [[package]] 1448 | name = "time-macros" 1449 | version = "0.2.18" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1452 | dependencies = [ 1453 | "num-conv", 1454 | "time-core", 1455 | ] 1456 | 1457 | [[package]] 1458 | name = "tinystr" 1459 | version = "0.7.6" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1462 | dependencies = [ 1463 | "displaydoc", 1464 | "zerovec", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "tokio" 1469 | version = "1.41.1" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" 1472 | dependencies = [ 1473 | "backtrace", 1474 | "bytes", 1475 | "libc", 1476 | "mio", 1477 | "parking_lot", 1478 | "pin-project-lite", 1479 | "signal-hook-registry", 1480 | "socket2", 1481 | "tokio-macros", 1482 | "windows-sys 0.52.0", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "tokio-macros" 1487 | version = "2.4.0" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1490 | dependencies = [ 1491 | "proc-macro2", 1492 | "quote", 1493 | "syn 2.0.87", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "tokio-util" 1498 | version = "0.7.12" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 1501 | dependencies = [ 1502 | "bytes", 1503 | "futures-core", 1504 | "futures-sink", 1505 | "pin-project-lite", 1506 | "tokio", 1507 | ] 1508 | 1509 | [[package]] 1510 | name = "toml_datetime" 1511 | version = "0.6.8" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1514 | 1515 | [[package]] 1516 | name = "toml_edit" 1517 | version = "0.19.15" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 1520 | dependencies = [ 1521 | "indexmap", 1522 | "toml_datetime", 1523 | "winnow", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "tracing" 1528 | version = "0.1.40" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1531 | dependencies = [ 1532 | "pin-project-lite", 1533 | "tracing-attributes", 1534 | "tracing-core", 1535 | ] 1536 | 1537 | [[package]] 1538 | name = "tracing-appender" 1539 | version = "0.2.3" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" 1542 | dependencies = [ 1543 | "crossbeam-channel", 1544 | "thiserror", 1545 | "time", 1546 | "tracing-subscriber", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "tracing-attributes" 1551 | version = "0.1.27" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1554 | dependencies = [ 1555 | "proc-macro2", 1556 | "quote", 1557 | "syn 2.0.87", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "tracing-core" 1562 | version = "0.1.32" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1565 | dependencies = [ 1566 | "once_cell", 1567 | "valuable", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "tracing-error" 1572 | version = "0.2.0" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 1575 | dependencies = [ 1576 | "tracing", 1577 | "tracing-subscriber", 1578 | ] 1579 | 1580 | [[package]] 1581 | name = "tracing-log" 1582 | version = "0.2.0" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1585 | dependencies = [ 1586 | "log", 1587 | "once_cell", 1588 | "tracing-core", 1589 | ] 1590 | 1591 | [[package]] 1592 | name = "tracing-subscriber" 1593 | version = "0.3.18" 1594 | source = "registry+https://github.com/rust-lang/crates.io-index" 1595 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1596 | dependencies = [ 1597 | "nu-ansi-term", 1598 | "sharded-slab", 1599 | "smallvec", 1600 | "thread_local", 1601 | "tracing-core", 1602 | "tracing-log", 1603 | ] 1604 | 1605 | [[package]] 1606 | name = "unicode-ident" 1607 | version = "1.0.13" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1610 | 1611 | [[package]] 1612 | name = "unicode-segmentation" 1613 | version = "1.12.0" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1616 | 1617 | [[package]] 1618 | name = "unicode-truncate" 1619 | version = "1.1.0" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 1622 | dependencies = [ 1623 | "itertools", 1624 | "unicode-segmentation", 1625 | "unicode-width 0.1.14", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "unicode-width" 1630 | version = "0.1.14" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1633 | 1634 | [[package]] 1635 | name = "unicode-width" 1636 | version = "0.2.0" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1639 | 1640 | [[package]] 1641 | name = "url" 1642 | version = "2.5.3" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" 1645 | dependencies = [ 1646 | "form_urlencoded", 1647 | "idna", 1648 | "percent-encoding", 1649 | ] 1650 | 1651 | [[package]] 1652 | name = "utf16_iter" 1653 | version = "1.0.5" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1656 | 1657 | [[package]] 1658 | name = "utf8_iter" 1659 | version = "1.0.4" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1662 | 1663 | [[package]] 1664 | name = "utf8parse" 1665 | version = "0.2.2" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1668 | 1669 | [[package]] 1670 | name = "valuable" 1671 | version = "0.1.0" 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" 1673 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1674 | 1675 | [[package]] 1676 | name = "vcpkg" 1677 | version = "0.2.15" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1680 | 1681 | [[package]] 1682 | name = "wasi" 1683 | version = "0.11.0+wasi-snapshot-preview1" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1686 | 1687 | [[package]] 1688 | name = "winapi" 1689 | version = "0.3.9" 1690 | source = "registry+https://github.com/rust-lang/crates.io-index" 1691 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1692 | dependencies = [ 1693 | "winapi-i686-pc-windows-gnu", 1694 | "winapi-x86_64-pc-windows-gnu", 1695 | ] 1696 | 1697 | [[package]] 1698 | name = "winapi-i686-pc-windows-gnu" 1699 | version = "0.4.0" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1702 | 1703 | [[package]] 1704 | name = "winapi-x86_64-pc-windows-gnu" 1705 | version = "0.4.0" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1708 | 1709 | [[package]] 1710 | name = "windows-sys" 1711 | version = "0.52.0" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1714 | dependencies = [ 1715 | "windows-targets", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "windows-sys" 1720 | version = "0.59.0" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1723 | dependencies = [ 1724 | "windows-targets", 1725 | ] 1726 | 1727 | [[package]] 1728 | name = "windows-targets" 1729 | version = "0.52.6" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1732 | dependencies = [ 1733 | "windows_aarch64_gnullvm", 1734 | "windows_aarch64_msvc", 1735 | "windows_i686_gnu", 1736 | "windows_i686_gnullvm", 1737 | "windows_i686_msvc", 1738 | "windows_x86_64_gnu", 1739 | "windows_x86_64_gnullvm", 1740 | "windows_x86_64_msvc", 1741 | ] 1742 | 1743 | [[package]] 1744 | name = "windows_aarch64_gnullvm" 1745 | version = "0.52.6" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1748 | 1749 | [[package]] 1750 | name = "windows_aarch64_msvc" 1751 | version = "0.52.6" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1754 | 1755 | [[package]] 1756 | name = "windows_i686_gnu" 1757 | version = "0.52.6" 1758 | source = "registry+https://github.com/rust-lang/crates.io-index" 1759 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1760 | 1761 | [[package]] 1762 | name = "windows_i686_gnullvm" 1763 | version = "0.52.6" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1766 | 1767 | [[package]] 1768 | name = "windows_i686_msvc" 1769 | version = "0.52.6" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1772 | 1773 | [[package]] 1774 | name = "windows_x86_64_gnu" 1775 | version = "0.52.6" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1778 | 1779 | [[package]] 1780 | name = "windows_x86_64_gnullvm" 1781 | version = "0.52.6" 1782 | source = "registry+https://github.com/rust-lang/crates.io-index" 1783 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1784 | 1785 | [[package]] 1786 | name = "windows_x86_64_msvc" 1787 | version = "0.52.6" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1790 | 1791 | [[package]] 1792 | name = "winnow" 1793 | version = "0.5.40" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 1796 | dependencies = [ 1797 | "memchr", 1798 | ] 1799 | 1800 | [[package]] 1801 | name = "write16" 1802 | version = "1.0.0" 1803 | source = "registry+https://github.com/rust-lang/crates.io-index" 1804 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1805 | 1806 | [[package]] 1807 | name = "writeable" 1808 | version = "0.5.5" 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" 1810 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1811 | 1812 | [[package]] 1813 | name = "yansi" 1814 | version = "1.0.1" 1815 | source = "registry+https://github.com/rust-lang/crates.io-index" 1816 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 1817 | 1818 | [[package]] 1819 | name = "yoke" 1820 | version = "0.7.4" 1821 | source = "registry+https://github.com/rust-lang/crates.io-index" 1822 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 1823 | dependencies = [ 1824 | "serde", 1825 | "stable_deref_trait", 1826 | "yoke-derive", 1827 | "zerofrom", 1828 | ] 1829 | 1830 | [[package]] 1831 | name = "yoke-derive" 1832 | version = "0.7.4" 1833 | source = "registry+https://github.com/rust-lang/crates.io-index" 1834 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 1835 | dependencies = [ 1836 | "proc-macro2", 1837 | "quote", 1838 | "syn 2.0.87", 1839 | "synstructure", 1840 | ] 1841 | 1842 | [[package]] 1843 | name = "zerocopy" 1844 | version = "0.7.35" 1845 | source = "registry+https://github.com/rust-lang/crates.io-index" 1846 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1847 | dependencies = [ 1848 | "byteorder", 1849 | "zerocopy-derive", 1850 | ] 1851 | 1852 | [[package]] 1853 | name = "zerocopy-derive" 1854 | version = "0.7.35" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1857 | dependencies = [ 1858 | "proc-macro2", 1859 | "quote", 1860 | "syn 2.0.87", 1861 | ] 1862 | 1863 | [[package]] 1864 | name = "zerofrom" 1865 | version = "0.1.4" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 1868 | dependencies = [ 1869 | "zerofrom-derive", 1870 | ] 1871 | 1872 | [[package]] 1873 | name = "zerofrom-derive" 1874 | version = "0.1.4" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 1877 | dependencies = [ 1878 | "proc-macro2", 1879 | "quote", 1880 | "syn 2.0.87", 1881 | "synstructure", 1882 | ] 1883 | 1884 | [[package]] 1885 | name = "zerovec" 1886 | version = "0.10.4" 1887 | source = "registry+https://github.com/rust-lang/crates.io-index" 1888 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1889 | dependencies = [ 1890 | "yoke", 1891 | "zerofrom", 1892 | "zerovec-derive", 1893 | ] 1894 | 1895 | [[package]] 1896 | name = "zerovec-derive" 1897 | version = "0.10.3" 1898 | source = "registry+https://github.com/rust-lang/crates.io-index" 1899 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1900 | dependencies = [ 1901 | "proc-macro2", 1902 | "quote", 1903 | "syn 2.0.87", 1904 | ] 1905 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dst" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = "0.1.83" 8 | clap = { version = "4.5.21", features = ["derive"] } 9 | color-eyre = "0.6.3" 10 | futures = "0.3.31" 11 | rand = "0.8.5" 12 | rand_chacha = "0.3.1" 13 | ratatui = { version = "0.29.0", features = ["all-widgets"] } 14 | rdkafka = "0.36.2" 15 | redis = { version = "0.27.5", features = ["aio", "tokio-comp"] } 16 | tokio = { version = "1.41.1", features = ["full", "fs"] } 17 | tracing = "0.1.40" 18 | tracing-appender = "0.2.3" 19 | tracing-subscriber = "0.3.18" 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SimulatIOn 2 | SimulatIOn is a Determinstic Simulation Testing(DST) setup for educational purposes. If you're interested in learning about more about DST, read [this post](https://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html). 3 | 4 | DST works on the basis of [PRNG](https://en.wikipedia.org/wiki/Pseudorandom_number_generator). 5 | 6 | ### Running This Project 7 | * Clone the repo or fork the repo 8 | * Build it with `cargo build` 9 | * Run the simulator with `cargo run -- --simulate`. If you want to pass a specific seed value, `SEED=12363138556869248126 cargo run -- --simulate` 10 | * Run the visualisation engine with `cargo run -- --game`. If you want to pass a specific seed value, `SEED=12363138556869248126 cargo run -- --game` 11 | 12 | ### Modeling Errors 13 | This project models a few standard errors: 14 | * Connection errors (Kafka, Redis, File open) 15 | * Processing errors (Kafka, Redis, File write) 16 | * Corrupted messages via Kafka 17 | 18 | The base idea is that with a specific seed, you can recreate a completely deterministic run. 19 | 20 | ### Screenshots 21 | 22 | 23 | ## Resources 24 | 25 | 1. https://github.com/penberg/hiisi 26 | 2. https://github.com/penberg/limbo 27 | 3. https://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html 28 | 4. https://github.com/tigerbeetle/tigerbeetle/blob/main/src/vopr.zig 29 | -------------------------------------------------------------------------------- /assets/img/death_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redixhumayun/dst/e5187ac9a1c3689c3312fa8913c89cb43d5159ba/assets/img/death_screen.png -------------------------------------------------------------------------------- /assets/img/sim_running_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redixhumayun/dst/e5187ac9a1c3689c3312fa8913c89cb43d5159ba/assets/img/sim_running_screen.png -------------------------------------------------------------------------------- /assets/img/start_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redixhumayun/dst/e5187ac9a1c3689c3312fa8913c89cb43d5159ba/assets/img/start_screen.png -------------------------------------------------------------------------------- /output.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redixhumayun/dst/e5187ac9a1c3689c3312fa8913c89cb43d5159ba/output.txt -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | use std::io::SeekFrom; 3 | use std::{collections::HashMap, path::Path, time::Duration}; 4 | 5 | use async_trait::async_trait; 6 | use clap::Parser; 7 | use futures::stream::StreamExt; 8 | use rand::Rng; 9 | use rand::{seq::SliceRandom, RngCore}; 10 | use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; 11 | use rdkafka::{ 12 | consumer::{stream_consumer::StreamConsumer, Consumer}, 13 | ClientConfig, Message, TopicPartitionList, 14 | }; 15 | use redis::AsyncCommands; 16 | use tokio::io::AsyncReadExt; 17 | use tokio::io::AsyncSeekExt; 18 | use tokio::io::AsyncWriteExt; 19 | use tracing::{error, info, trace, warn}; 20 | use tracing_appender::rolling::{RollingFileAppender, Rotation}; 21 | use tracing_subscriber; 22 | use tracing_subscriber::fmt::writer::MakeWriterExt; 23 | mod tui; 24 | 25 | pub enum Errors { 26 | KafkaConnectionError, 27 | NoKafkaMessage, 28 | InvalidKafkaMessage, 29 | RedisConnectionError, 30 | RedisKeyRetrievalError, 31 | FileOpenError, 32 | FileReadError, 33 | ExpectedFileReadError, 34 | FileWriteError, 35 | FileSyncError, 36 | } 37 | 38 | impl std::fmt::Debug for Errors { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | match self { 41 | Errors::KafkaConnectionError => write!(f, "Kafka connection error"), 42 | Errors::NoKafkaMessage => write!(f, "No Kafka message"), 43 | Errors::InvalidKafkaMessage => write!(f, "Invalid Kafka message"), 44 | Errors::RedisConnectionError => write!(f, "Redis connection error"), 45 | Errors::RedisKeyRetrievalError => write!(f, "Error retrieving redis key"), 46 | Errors::FileOpenError => write!(f, "Failed to open file"), 47 | Errors::FileReadError => write!(f, "Failed to read from file"), 48 | Errors::ExpectedFileReadError => write!(f, "Expected file read error"), 49 | Errors::FileWriteError => write!(f, "Failed to write to file"), 50 | Errors::FileSyncError => write!(f, "Failed to sync file"), 51 | } 52 | } 53 | } 54 | 55 | impl std::fmt::Display for Errors { 56 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 57 | match self { 58 | Errors::KafkaConnectionError => write!(f, "Kafka connection error"), 59 | Errors::NoKafkaMessage => write!(f, "No Kafka message"), 60 | Errors::InvalidKafkaMessage => write!(f, "Invalid Kafka message"), 61 | Errors::RedisConnectionError => write!(f, "Redis connection error"), 62 | Errors::RedisKeyRetrievalError => write!(f, "Error retrieving redis key"), 63 | Errors::FileOpenError => write!(f, "Failed to open file"), 64 | Errors::FileReadError => write!(f, "Failed to read from file"), 65 | Errors::ExpectedFileReadError => write!(f, "Expected file read error"), 66 | Errors::FileWriteError => write!(f, "Failed to write to file"), 67 | Errors::FileSyncError => write!(f, "Failed to sync file"), 68 | } 69 | } 70 | } 71 | 72 | impl std::error::Error for Errors {} 73 | 74 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 75 | enum FaultType { 76 | KafkaConnectionFailure, 77 | KafkaReadFailure, 78 | KafkaInvalidMessage, 79 | RedisConnectionFailure, 80 | RedisReadFailure, 81 | FileOpenFailure, 82 | FileFaultType(FileFaultType), 83 | } 84 | 85 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 86 | enum FileFaultType { 87 | FileReadFailure, 88 | FileWriteFailure, 89 | FileSizeExceededFailure, 90 | FileMetadataSyncFailure, 91 | } 92 | 93 | #[derive(Parser, Debug)] 94 | #[command(name = "SimulatIOn", version = "1.0", author = "Zaid Humayun")] 95 | struct Args { 96 | #[arg(short, long)] 97 | game: bool, 98 | #[arg(short, long)] 99 | simulate: bool, 100 | } 101 | 102 | #[async_trait] 103 | trait Clock { 104 | async fn sleep(&mut self, duration: Duration); 105 | } 106 | 107 | struct RealClock; 108 | 109 | impl RealClock { 110 | fn new() -> Self { 111 | Self {} 112 | } 113 | } 114 | 115 | #[async_trait] 116 | impl Clock for RealClock { 117 | async fn sleep(&mut self, duration: Duration) { 118 | tokio::time::sleep(duration).await; 119 | } 120 | } 121 | 122 | struct SimulatedClock { 123 | current_time: Duration, 124 | } 125 | 126 | impl SimulatedClock { 127 | fn new() -> Self { 128 | Self { 129 | current_time: Duration::ZERO, 130 | } 131 | } 132 | 133 | fn advance(&mut self, duration: Duration) { 134 | self.current_time += duration; 135 | } 136 | } 137 | 138 | #[async_trait] 139 | impl Clock for SimulatedClock { 140 | async fn sleep(&mut self, duration: Duration) { 141 | self.advance(duration); 142 | } 143 | } 144 | 145 | #[async_trait] 146 | trait File { 147 | async fn read(&mut self, size: usize) -> Result, Errors>; 148 | async fn write(&mut self, data: &str) -> Result; 149 | async fn fsync(&mut self) -> Result<(), Errors>; 150 | async fn read_last_n_entries(&mut self, n: usize) -> Result, Errors>; 151 | } 152 | 153 | struct RealFile { 154 | file: Option, 155 | } 156 | 157 | #[async_trait] 158 | impl File for RealFile { 159 | async fn read(&mut self, size: usize) -> Result, Errors> { 160 | let mut buffer = vec![0; size]; 161 | self.file 162 | .as_mut() 163 | .unwrap() 164 | .read(&mut buffer) 165 | .await 166 | .map_err(|_| Errors::FileReadError)?; 167 | Ok(buffer) 168 | } 169 | 170 | async fn write(&mut self, data: &str) -> Result { 171 | self.file 172 | .as_mut() 173 | .unwrap() 174 | .write(data.as_bytes()) 175 | .await 176 | .map_err(|_| Errors::FileWriteError) 177 | } 178 | 179 | async fn fsync(&mut self) -> Result<(), Errors> { 180 | self.file 181 | .as_mut() 182 | .unwrap() 183 | .sync_all() 184 | .await 185 | .map_err(|_| Errors::FileSyncError) 186 | } 187 | 188 | async fn read_last_n_entries(&mut self, n: usize) -> Result, Errors> { 189 | let file = self.file.as_mut().ok_or(Errors::FileReadError)?; 190 | 191 | // Get file size and seek to end 192 | let file_size = file 193 | .metadata() 194 | .await 195 | .map_err(|_| Errors::FileReadError)? 196 | .len() as usize; 197 | file.seek(SeekFrom::End(0)) 198 | .await 199 | .map_err(|_| Errors::FileReadError)?; 200 | 201 | // Read chunks from end until we find n newlines 202 | let mut buffer = Vec::new(); 203 | let mut position = file_size; 204 | let chunk_size = 1024; // Read 1KB at a time 205 | 206 | while position > 0 && buffer.iter().filter(|&&c| c == b'\n').count() <= n { 207 | let read_size = std::cmp::min(position, chunk_size); 208 | position = position.saturating_sub(read_size); 209 | 210 | file.seek(SeekFrom::Start(position as u64)) 211 | .await 212 | .map_err(|_| Errors::FileReadError)?; 213 | 214 | let mut chunk = vec![0; read_size]; 215 | file.read_exact(&mut chunk) 216 | .await 217 | .map_err(|_| Errors::FileReadError)?; 218 | 219 | buffer.splice(0..0, chunk); 220 | } 221 | 222 | // Convert to string and get last n lines 223 | let result = String::from_utf8_lossy(&buffer) 224 | .lines() 225 | .rev() 226 | .take(n) 227 | .map(String::from) 228 | .collect::>() 229 | .into_iter() 230 | .rev() 231 | .collect::>(); 232 | Ok(result) 233 | } 234 | } 235 | 236 | struct SimulatedFile { 237 | rng: ChaCha8Rng, 238 | file_contents: Vec, 239 | synced_contents: Vec, 240 | current_file_size: usize, 241 | max_file_size: usize, 242 | inner: RealFile, 243 | read_position: usize, 244 | write_position: usize, 245 | fault_probabilities: HashMap, 246 | } 247 | 248 | impl SimulatedFile { 249 | fn new(rng: ChaCha8Rng, io: RealFile) -> Self { 250 | let fault_probabilities = HashMap::from([ 251 | (FileFaultType::FileReadFailure, 0.1), 252 | (FileFaultType::FileWriteFailure, 0.1), 253 | (FileFaultType::FileSizeExceededFailure, 0.1), 254 | (FileFaultType::FileMetadataSyncFailure, 0.1), 255 | ]); 256 | Self { 257 | rng, 258 | file_contents: Vec::with_capacity(100000000), 259 | synced_contents: Vec::new(), 260 | current_file_size: 0, 261 | max_file_size: 100000000, 262 | inner: io, 263 | read_position: 0, 264 | write_position: 0, 265 | fault_probabilities, 266 | } 267 | } 268 | 269 | fn should_inject_fault(&mut self, fault_type: &FileFaultType) -> bool { 270 | if let Some(&probability) = self.fault_probabilities.get(fault_type) { 271 | self.rng.gen_bool(probability) 272 | } else { 273 | false 274 | } 275 | } 276 | } 277 | 278 | #[async_trait] 279 | impl File for SimulatedFile { 280 | async fn read(&mut self, size: usize) -> Result, Errors> { 281 | if self.should_inject_fault(&FileFaultType::FileReadFailure) { 282 | warn!("Injecting fault while reading from file"); 283 | return Err(Errors::FileReadError); 284 | } 285 | assert!(size < self.file_contents.len()); 286 | let buffer = self.file_contents[self.read_position..self.read_position + size].to_vec(); 287 | self.read_position += size; 288 | Ok(buffer) 289 | } 290 | 291 | async fn write(&mut self, data: &str) -> Result { 292 | if self.should_inject_fault(&FileFaultType::FileWriteFailure) { 293 | warn!("Injecting fault while writing to file"); 294 | return Err(Errors::FileWriteError); 295 | } 296 | trace!("Not injecting fault while writing to file"); 297 | let data = data.as_bytes(); 298 | let write_size = data.len(); 299 | trace!("making a write of size {:?}", write_size); 300 | if self.current_file_size + write_size > self.max_file_size { 301 | return Err(Errors::FileWriteError); 302 | } 303 | if self.file_contents.len() < self.write_position + write_size { 304 | self.file_contents 305 | .resize(self.write_position + write_size, 0); 306 | } 307 | self.file_contents[self.write_position..self.write_position + write_size] 308 | .copy_from_slice(&data[..write_size]); 309 | self.write_position += write_size; 310 | self.current_file_size += write_size; 311 | Ok(write_size) 312 | } 313 | 314 | async fn fsync(&mut self) -> Result<(), Errors> { 315 | // TODO: Should we inject failure for fsync? Seems excessive. How do people program around that? 316 | self.synced_contents = self.file_contents.clone(); 317 | Ok(()) 318 | } 319 | 320 | async fn read_last_n_entries(&mut self, n: usize) -> Result, Errors> { 321 | // Since we're writing newline-delimited entries, split on newlines 322 | let contents = String::from_utf8_lossy(&self.file_contents); 323 | let entries: Vec = contents 324 | .lines() 325 | .rev() // reverse to get last entries 326 | .take(n) // take last n 327 | .map(String::from) 328 | .map(|line| format!("{}\n", line)) 329 | .collect::>() 330 | .into_iter() 331 | .rev() 332 | .collect(); 333 | Ok(entries) 334 | } 335 | } 336 | 337 | #[async_trait] 338 | trait IO { 339 | async fn create_kafka_consumer( 340 | &mut self, 341 | group_id: &str, 342 | broker: &str, 343 | topic: &str, 344 | partition: i32, 345 | ) -> Result<(), Errors>; 346 | async fn connect_to_redis(&mut self, url: &str) -> Result<(), Errors>; 347 | async fn open_file(&mut self, path: &Path) -> Result<(), Errors>; 348 | async fn read_kafka_message(&mut self) -> Result, Errors>; 349 | async fn get_redis_config(&mut self, key: &str) -> Result; 350 | async fn read_file(&mut self, size: usize) -> Result, Errors>; 351 | async fn read_last_n_entries(&mut self, n: usize) -> Result, Errors>; 352 | async fn write_to_file(&mut self, data: &str) -> Result; 353 | fn generate_jitter(&mut self, base_delay: Duration) -> Duration; 354 | async fn sleep(&mut self, duration: Duration); 355 | fn get_generated_faults(&mut self) -> Vec; 356 | } 357 | 358 | struct RealIO { 359 | consumer: Option, 360 | redis_connection: Option, 361 | file: Option, 362 | pub clock: Box, 363 | } 364 | 365 | impl RealIO { 366 | fn new() -> Self { 367 | let clock = Box::new(RealClock::new()); 368 | Self { 369 | consumer: None, 370 | redis_connection: None, 371 | file: None, 372 | clock, 373 | } 374 | } 375 | } 376 | 377 | #[async_trait] 378 | impl IO for RealIO { 379 | async fn create_kafka_consumer( 380 | &mut self, 381 | group_id: &str, 382 | broker: &str, 383 | topic: &str, 384 | partition: i32, 385 | ) -> Result<(), Errors> { 386 | let consumer: StreamConsumer = ClientConfig::new() 387 | .set("group.id", group_id) 388 | .set("bootstrap.servers", broker) 389 | .create() 390 | .map_err(|_| Errors::KafkaConnectionError)?; 391 | let mut tpl = TopicPartitionList::new(); 392 | tpl.add_partition_offset(topic, partition, rdkafka::Offset::Beginning) 393 | .map_err(|_| Errors::KafkaConnectionError)?; 394 | consumer 395 | .assign(&tpl) 396 | .map_err(|_| Errors::KafkaConnectionError)?; 397 | 398 | self.consumer = Some(consumer); 399 | Ok(()) 400 | } 401 | 402 | async fn connect_to_redis(&mut self, url: &str) -> Result<(), Errors> { 403 | let client = redis::Client::open(url).map_err(|_| Errors::RedisConnectionError)?; 404 | let connection = client 405 | .get_multiplexed_async_connection() 406 | .await 407 | .map_err(|_| Errors::RedisConnectionError)?; 408 | self.redis_connection = Some(connection); 409 | Ok(()) 410 | } 411 | 412 | async fn open_file(&mut self, path: &Path) -> Result<(), Errors> { 413 | let file = tokio::fs::OpenOptions::new() 414 | .create(true) 415 | .write(true) 416 | .append(true) 417 | .open(path) 418 | .await 419 | .map_err(|_| Errors::FileOpenError)?; 420 | self.file = Some(RealFile { file: Some(file) }); 421 | Ok(()) 422 | } 423 | 424 | async fn read_kafka_message(&mut self) -> Result, Errors> { 425 | if let Some(consumer) = &self.consumer { 426 | let message = consumer.stream().next().await; 427 | let msg = match message { 428 | Some(Ok(msg)) => msg 429 | .payload() 430 | .map(|payload| String::from_utf8_lossy(payload).into_owned()), 431 | _ => return Err(Errors::NoKafkaMessage), 432 | }; 433 | return Ok(msg); 434 | } 435 | Ok(None) 436 | } 437 | 438 | async fn get_redis_config(&mut self, key: &str) -> Result { 439 | if let Some(redis_conn) = &mut self.redis_connection { 440 | match redis_conn.get(key).await { 441 | Ok(value) => Ok(value), 442 | Err(_) => Err(Errors::RedisKeyRetrievalError), 443 | } 444 | } else { 445 | Err(Errors::RedisConnectionError) 446 | } 447 | } 448 | 449 | async fn read_file(&mut self, size: usize) -> Result, Errors> { 450 | self.file.as_mut().unwrap().read(size).await 451 | } 452 | 453 | async fn write_to_file(&mut self, data: &str) -> Result { 454 | self.file.as_mut().unwrap().write(data).await 455 | } 456 | 457 | async fn read_last_n_entries(&mut self, n: usize) -> Result, Errors> { 458 | self.file.as_mut().unwrap().read_last_n_entries(n).await 459 | } 460 | 461 | fn generate_jitter(&mut self, base_delay: Duration) -> Duration { 462 | let jitter: u64 = rand::thread_rng().gen_range(0..base_delay.as_millis() as u64); 463 | base_delay + Duration::from_millis(jitter) 464 | } 465 | 466 | async fn sleep(&mut self, duration: Duration) { 467 | self.clock.sleep(duration).await; 468 | } 469 | 470 | fn get_generated_faults(&mut self) -> Vec { 471 | todo!() 472 | } 473 | } 474 | 475 | struct SimulatedIO { 476 | rng: ChaCha8Rng, 477 | fault_probabilities: HashMap, 478 | kafka_messages: Vec, 479 | kafka_attempts: usize, 480 | kafka_failures: usize, 481 | redis_data: HashMap, 482 | file: Option, 483 | clock: Box, 484 | faults_generated: Vec, 485 | } 486 | 487 | impl SimulatedIO { 488 | fn new(seed: u64) -> Self { 489 | let mut rng = ChaCha8Rng::seed_from_u64(seed); 490 | let clock = Box::new(SimulatedClock::new()); 491 | let kafka_messages = vec![ 492 | "simulated_message_1".to_string(), 493 | "simulated_message_2".to_string(), 494 | "simulated_message_3".to_string(), 495 | ]; 496 | let mut redis_data = HashMap::new(); 497 | redis_data.insert( 498 | "config_key".to_string(), 499 | "simulated_config_value".to_string(), 500 | ); 501 | let fault_probabilities = HashMap::from([ 502 | (FaultType::KafkaConnectionFailure, 0.1), 503 | (FaultType::KafkaReadFailure, 0.1), 504 | (FaultType::KafkaInvalidMessage, 0.009), 505 | (FaultType::RedisConnectionFailure, 0.1), 506 | (FaultType::RedisReadFailure, 0.1), 507 | (FaultType::FileOpenFailure, 0.1), 508 | ]); 509 | let kafka_failures = rng.gen_range(1..5); 510 | 511 | Self { 512 | rng, 513 | fault_probabilities, 514 | kafka_messages, 515 | redis_data, 516 | file: None, 517 | kafka_attempts: 0, 518 | kafka_failures, 519 | clock, 520 | faults_generated: Vec::new(), 521 | } 522 | } 523 | 524 | fn should_inject_fault(&mut self, fault_type: &FaultType) -> bool { 525 | if let Some(&probability) = self.fault_probabilities.get(fault_type) { 526 | match self.rng.gen_bool(probability) { 527 | true => { 528 | self.faults_generated.push(fault_type.clone()); 529 | return true; 530 | } 531 | false => false, 532 | } 533 | } else { 534 | false 535 | } 536 | } 537 | } 538 | 539 | #[async_trait] 540 | impl IO for SimulatedIO { 541 | async fn create_kafka_consumer( 542 | &mut self, 543 | _group_id: &str, 544 | _broker: &str, 545 | _topic: &str, 546 | _partition: i32, 547 | ) -> Result<(), Errors> { 548 | self.kafka_attempts += 1; 549 | if self.should_inject_fault(&FaultType::KafkaConnectionFailure) 550 | && self.kafka_attempts <= self.kafka_failures 551 | { 552 | warn!("Injecting fault for Kafka connection error"); 553 | return Err(Errors::KafkaConnectionError); 554 | } 555 | trace!("Not injecting fault for Kafka connection error"); 556 | self.sleep(Duration::from_millis(50)).await; 557 | Ok(()) 558 | } 559 | 560 | async fn connect_to_redis(&mut self, _path: &str) -> Result<(), Errors> { 561 | if self.should_inject_fault(&FaultType::RedisConnectionFailure) { 562 | warn!("Injecting fault for Redis connection error"); 563 | return Err(Errors::RedisConnectionError); 564 | } 565 | trace!("Not injecting fault for Redis connection error"); 566 | self.sleep(Duration::from_millis(50)).await; 567 | Ok(()) 568 | } 569 | 570 | async fn open_file(&mut self, path: &Path) -> Result<(), Errors> { 571 | let file = tokio::fs::OpenOptions::new() 572 | .create(true) 573 | .write(true) 574 | .append(true) 575 | .open(path) 576 | .await 577 | .map_err(|_| Errors::FileOpenError)?; 578 | let sim_file = SimulatedFile::new(self.rng.clone(), RealFile { file: Some(file) }); 579 | self.file = Some(sim_file); 580 | Ok(()) 581 | } 582 | 583 | async fn read_kafka_message(&mut self) -> Result, Errors> { 584 | if self.should_inject_fault(&FaultType::KafkaReadFailure) { 585 | warn!("Injecting fault for Kafka read error"); 586 | return Err(Errors::NoKafkaMessage); 587 | } 588 | if self.should_inject_fault(&FaultType::KafkaInvalidMessage) { 589 | warn!("Injecting fault for invalid Kafka message"); 590 | return Ok(Some("dummy".to_string())); 591 | } 592 | trace!("Not injecting fault for Kafka read error"); 593 | self.sleep(Duration::from_millis(50)).await; 594 | assert!(self.kafka_messages.len() > 0); 595 | if let Some(message) = self.kafka_messages.choose(&mut self.rng) { 596 | return Ok(Some(message.clone())); 597 | } 598 | return Ok(None); 599 | } 600 | 601 | async fn get_redis_config(&mut self, key: &str) -> Result { 602 | if self.should_inject_fault(&FaultType::RedisReadFailure) { 603 | warn!("Injecting fault for Redis read error"); 604 | return Err(Errors::RedisKeyRetrievalError); 605 | } 606 | trace!("Not injecting fault for Redis read error"); 607 | self.sleep(Duration::from_millis(100)).await; 608 | self.redis_data 609 | .get(key) 610 | .ok_or(Errors::RedisKeyRetrievalError) 611 | .cloned() 612 | } 613 | 614 | async fn read_file(&mut self, size: usize) -> Result, Errors> { 615 | match self.file.as_mut().unwrap().read(size).await { 616 | Ok(usize) => Ok(usize), 617 | Err(e) => { 618 | self.faults_generated 619 | .push(FaultType::FileFaultType(FileFaultType::FileReadFailure)); 620 | Err(e) 621 | } 622 | } 623 | } 624 | 625 | async fn write_to_file(&mut self, data: &str) -> Result { 626 | match self.file.as_mut().unwrap().write(data).await { 627 | Ok(usize) => Ok(usize), 628 | Err(e) => { 629 | self.faults_generated 630 | .push(FaultType::FileFaultType(FileFaultType::FileWriteFailure)); 631 | Err(e) 632 | } 633 | } 634 | } 635 | 636 | async fn read_last_n_entries(&mut self, n: usize) -> Result, Errors> { 637 | self.file.as_mut().unwrap().read_last_n_entries(n).await 638 | } 639 | 640 | fn generate_jitter(&mut self, base_delay: Duration) -> Duration { 641 | let jitter: u64 = self.rng.gen_range(0..base_delay.as_millis() as u64); 642 | base_delay + Duration::from_millis(jitter) 643 | } 644 | 645 | async fn sleep(&mut self, duration: Duration) { 646 | self.clock.sleep(duration).await; 647 | } 648 | 649 | fn get_generated_faults(&mut self) -> Vec { 650 | let faults = self.faults_generated.clone(); 651 | self.faults_generated.clear(); 652 | faults 653 | } 654 | } 655 | 656 | fn main() { 657 | let args = Args::parse(); 658 | info!("Starting application with args: {:?}", args); 659 | let runtime = tokio::runtime::Builder::new_current_thread() 660 | .enable_all() 661 | .build() 662 | .unwrap(); 663 | 664 | if args.game { 665 | runtime.block_on(tui::run_tui()); 666 | } else { 667 | runtime.block_on(start_simulation(args)); 668 | } 669 | } 670 | 671 | enum LogOptions { 672 | Console, 673 | File, 674 | } 675 | fn init_tracing(option: LogOptions) { 676 | match option { 677 | LogOptions::Console => { 678 | tracing_subscriber::fmt::init(); 679 | info!("Initialising tracing to write to stdout"); 680 | } 681 | LogOptions::File => { 682 | let file_appender = RollingFileAppender::new(Rotation::DAILY, ".", "debug.log"); 683 | let subscriber = tracing_subscriber::fmt() 684 | .with_writer(file_appender.with_max_level(tracing::Level::TRACE)) 685 | .with_max_level(tracing::Level::TRACE) 686 | .finish(); 687 | tracing::subscriber::set_global_default(subscriber) 688 | .expect("setting default subscriber failed"); 689 | trace!("Initialising tracing to write to a file"); 690 | } 691 | } 692 | } 693 | 694 | async fn start_simulation(args: Args) { 695 | init_tracing(LogOptions::Console); 696 | if args.simulate { 697 | let seed = match std::env::var("SEED") { 698 | Ok(seed) => seed.parse::().unwrap(), 699 | Err(_) => rand::thread_rng().next_u64(), 700 | }; 701 | info!("Running simulator with seed {}", seed); 702 | let mut io = SimulatedIO::new(seed); 703 | init_components(&mut io).await; 704 | run(&mut io).await; 705 | } else { 706 | let mut io = RealIO::new(); 707 | init_components(&mut io).await; 708 | run(&mut io).await; 709 | } 710 | } 711 | 712 | async fn init_components(io: &mut dyn IO) -> Result, Errors> { 713 | let max_retries = 5; 714 | let base_delay = Duration::from_millis(10); 715 | let mut retries = 0; 716 | let mut delay = base_delay; 717 | loop { 718 | match io 719 | .create_kafka_consumer("group_id", "localhost:9092", "dummy_topic", 0) 720 | .await 721 | { 722 | Ok(_) => break, 723 | Err(_) if retries < max_retries => { 724 | retries += 1; 725 | let delay_with_jitter = io.generate_jitter(delay); 726 | io.sleep(delay_with_jitter).await; 727 | delay *= 2; 728 | } 729 | Err(err) => { 730 | eprintln!("failed to create Kafka consumer: {:?}", err); 731 | return Err(Errors::KafkaConnectionError); 732 | } 733 | } 734 | } 735 | 736 | let max_retries = 5; 737 | let base_delay = Duration::from_millis(10); 738 | let mut retries = 0; 739 | let mut delay = base_delay; 740 | loop { 741 | match io.connect_to_redis("redis://127.0.0.1").await { 742 | Ok(_) => break, 743 | Err(_) if retries < max_retries => { 744 | retries += 1; 745 | let delay_with_jitter = io.generate_jitter(delay); 746 | io.sleep(delay_with_jitter).await; 747 | delay *= 2; 748 | } 749 | Err(err) => { 750 | eprintln!("failed to create Kafka consumer: {:?}", err); 751 | return Err(Errors::RedisConnectionError); 752 | } 753 | } 754 | } 755 | 756 | io.open_file(Path::new("output.txt")).await.unwrap(); 757 | Ok(io.get_generated_faults()) 758 | } 759 | 760 | async fn run(io: &mut dyn IO) { 761 | let config_key = "config_key"; 762 | let mut counter = 0; 763 | let mut written_messages = Vec::new(); 764 | let mut failed_writes = Vec::new(); 765 | loop { 766 | run_simulation_step( 767 | io, 768 | config_key, 769 | &mut counter, 770 | &mut written_messages, 771 | &mut failed_writes, 772 | ) 773 | .await 774 | .unwrap(); 775 | } 776 | } 777 | 778 | async fn run_simulation_step( 779 | io: &mut dyn IO, 780 | config_key: &str, 781 | counter: &mut usize, 782 | written_messages: &mut Vec, 783 | failed_writes: &mut Vec, 784 | ) -> Result, Errors> { 785 | *counter += 1; 786 | trace!("Iteration {counter}"); 787 | 788 | // Get Kafka message 789 | let max_retries = 5; 790 | let base_delay = Duration::from_millis(10); 791 | let mut retries = 0; 792 | let mut delay = base_delay; 793 | 794 | let kafka_message = loop { 795 | match io.read_kafka_message().await { 796 | Ok(Some(message)) => { 797 | if message.len() <= 18 { 798 | // found corrupted data 799 | return Err(Errors::InvalidKafkaMessage); 800 | } 801 | break Ok(message); 802 | } 803 | Ok(None) => { 804 | return Err(Errors::NoKafkaMessage); 805 | } 806 | Err(_) if retries < max_retries => { 807 | retries += 1; 808 | let delay_with_jitter = io.generate_jitter(delay); 809 | io.sleep(delay_with_jitter).await; 810 | delay *= 2; 811 | } 812 | Err(err) => return Err(err), 813 | }; 814 | 815 | if retries >= max_retries { 816 | return Err(Errors::NoKafkaMessage); 817 | } 818 | }?; 819 | 820 | // Get Redis config 821 | let max_retries = 5; 822 | let base_delay = Duration::from_millis(10); 823 | let mut retries = 0; 824 | let mut delay = base_delay; 825 | 826 | let redis_config = loop { 827 | match io.get_redis_config(&config_key).await { 828 | Ok(message) => break Ok(message), 829 | Err(_) if retries < max_retries => { 830 | retries += 1; 831 | let delay_with_jitter = io.generate_jitter(delay); 832 | io.sleep(delay_with_jitter).await; 833 | delay *= 2; 834 | } 835 | Err(err) => { 836 | return Err(Errors::RedisKeyRetrievalError); 837 | } 838 | }; 839 | 840 | if retries >= max_retries { 841 | return Err(Errors::RedisKeyRetrievalError); 842 | } 843 | }?; 844 | 845 | let output = format!("Config: {}, Message: {}\n", redis_config, kafka_message); 846 | 847 | // First, always attempt to write the previous failed messages 848 | // For those that succeed put them into written_messages and remove them from failed_writes 849 | { 850 | let mut index = 0; 851 | while index < failed_writes.len() { 852 | let message = &failed_writes[index].clone(); 853 | match io.write_to_file(&message).await { 854 | Ok(_) => { 855 | failed_writes.remove(index); 856 | written_messages.push(message.clone()); 857 | } 858 | Err(e) => { 859 | warn!("failed to write message {:?}", e); 860 | } 861 | } 862 | index += 1; 863 | } 864 | } 865 | 866 | match io.write_to_file(&output).await { 867 | Ok(_) => { 868 | written_messages.push(output.clone()); 869 | if *counter % 5 == 0 && written_messages.len() >= 5 { 870 | match io.read_last_n_entries(5).await { 871 | Ok(read_messages) => { 872 | let expected = &written_messages[written_messages.len() - 5..]; 873 | if read_messages != expected { 874 | return Err(Errors::ExpectedFileReadError); 875 | } 876 | return Ok(io.get_generated_faults()); 877 | } 878 | Err(e) => { 879 | // TODO: Currently this won't be triggered because I'm not injecting any faults 880 | return Err(Errors::FileReadError); 881 | } 882 | } 883 | } 884 | Ok(io.get_generated_faults()) 885 | } 886 | Err(e) => { 887 | error!("failed to write to file: {:?}", e); 888 | failed_writes.push(output.clone()); 889 | Ok(io.get_generated_faults()) 890 | } 891 | } 892 | } 893 | -------------------------------------------------------------------------------- /src/tui.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | default, io, 4 | time::{Duration, Instant, SystemTime}, 5 | }; 6 | 7 | use color_eyre::Result; 8 | use rand::{seq::SliceRandom, RngCore}; 9 | use ratatui::{ 10 | crossterm::event::{self, Event, KeyCode}, 11 | layout::{Alignment, Constraint, Layout}, 12 | style::{Color, Style}, 13 | text::Line, 14 | widgets::{Block, Borders, Paragraph}, 15 | DefaultTerminal, Frame, 16 | }; 17 | use ratatui::{prelude::Stylize, style::Modifier}; 18 | use tracing::{error, info, trace}; 19 | 20 | use crate::{ 21 | init_components, init_tracing, run_simulation_step, FaultType, FileFaultType, SimulatedIO, 22 | }; 23 | 24 | pub async fn run_tui() -> Result<()> { 25 | color_eyre::install()?; 26 | init_tracing(crate::LogOptions::File); 27 | let mut terminal = ratatui::init(); 28 | let seed = match std::env::var("SEED") { 29 | Ok(seed) => seed.parse::().unwrap(), 30 | Err(_) => rand::thread_rng().next_u64(), 31 | }; 32 | info!("Running game loop with seed {}", seed); 33 | let mut io = SimulatedIO::new(seed); 34 | let config_key = "config_key"; 35 | let app_result = App::default() 36 | .run(&mut terminal, &mut io, &config_key, seed) 37 | .await; 38 | ratatui::restore(); 39 | Ok(app_result?) 40 | } 41 | 42 | impl FaultType { 43 | fn to_symbol(&self) -> &str { 44 | match self { 45 | FaultType::KafkaConnectionFailure => "⚔️", 46 | FaultType::KafkaInvalidMessage => "💥", 47 | FaultType::RedisConnectionFailure => "🛡️", 48 | FaultType::KafkaReadFailure => "🔥", 49 | FaultType::RedisReadFailure => "⚡", 50 | FaultType::FileOpenFailure => "💥", 51 | FaultType::FileFaultType(_) => "💣", 52 | } 53 | } 54 | 55 | fn to_log_message(&self) -> String { 56 | match self { 57 | FaultType::KafkaConnectionFailure => "Kafka connection failure injected".to_string(), 58 | FaultType::RedisConnectionFailure => "Redis connection failure injected".to_string(), 59 | FaultType::KafkaReadFailure => "Kafka read failure injected".to_string(), 60 | FaultType::KafkaInvalidMessage => "Kafka invalid message injected".to_string(), 61 | FaultType::RedisReadFailure => "Redis read failure injected".to_string(), 62 | FaultType::FileOpenFailure => "File open failure injected".to_string(), 63 | FaultType::FileFaultType(fault) => match fault { 64 | FileFaultType::FileReadFailure => "File read failure injected".to_string(), 65 | FileFaultType::FileWriteFailure => "File write failure injected".to_string(), 66 | FileFaultType::FileSizeExceededFailure => { 67 | "File size exceeded failure injected".to_string() 68 | } 69 | FileFaultType::FileMetadataSyncFailure => { 70 | "File metadata sync failure injected".to_string() 71 | } 72 | }, 73 | } 74 | } 75 | } 76 | 77 | #[derive(Default, PartialEq)] 78 | enum AppState { 79 | #[default] 80 | StartScreen, 81 | Running, 82 | GameOver, 83 | GameCompleted, 84 | } 85 | 86 | #[derive(Default)] 87 | struct App { 88 | state: AppState, 89 | active_faults: VecDeque<(FaultType, u8)>, 90 | fault_log: VecDeque, 91 | fault_log_counter: usize, 92 | status_log: VecDeque, 93 | status_log_counter: usize, 94 | tick_count: u64, 95 | death_reason: Option, 96 | seed: u64, 97 | } 98 | 99 | impl App { 100 | fn add_fault(&mut self, fault: FaultType) { 101 | self.fault_log_counter += 1; 102 | self.active_faults.push_back((fault.clone(), 0)); 103 | self.fault_log.push_back(fault.to_log_message()); 104 | if self.fault_log.len() > 20 { 105 | self.fault_log.pop_front(); 106 | } 107 | } 108 | 109 | fn add_connection_status_messages(&mut self) { 110 | let messages = [ 111 | format!("[{}] Connected to Kafka", self.status_log_counter), 112 | format!("[{}] Connected to Redis", self.status_log_counter + 1), 113 | format!("[{}] Opened file descriptor", self.status_log_counter + 2), 114 | ]; 115 | 116 | for message in messages { 117 | self.status_log.push_back(message); 118 | } 119 | 120 | while self.status_log.len() > 50 { 121 | self.status_log.pop_front(); 122 | } 123 | self.status_log_counter += 3; 124 | } 125 | 126 | fn add_status_messages(&mut self) { 127 | let messages = [ 128 | format!("[{}] Read messages from Kafka", self.status_log_counter), 129 | format!("[{}] Read messages from Redis", self.status_log_counter + 1), 130 | format!("[{}] Wrote output to file", self.status_log_counter + 2), 131 | ]; 132 | 133 | for msg in messages { 134 | self.status_log.push_back(msg); 135 | } 136 | 137 | while self.status_log.len() > 50 { 138 | // Keep more messages for scrolling effect 139 | self.status_log.pop_front(); 140 | } 141 | self.status_log_counter += 3; 142 | } 143 | 144 | fn tick(&mut self) { 145 | self.tick_count = self.tick_count.wrapping_add(1); 146 | for (_, pos) in self.active_faults.iter_mut() { 147 | *pos = pos.saturating_add(1); 148 | } 149 | while self 150 | .active_faults 151 | .front() 152 | .map_or(false, |(_, pos)| *pos >= 10) 153 | { 154 | let entry = self.active_faults.pop_front(); 155 | if let Some(e) = entry { 156 | trace!("removing fault type {:?}", e.0); 157 | } 158 | } 159 | } 160 | 161 | pub async fn run( 162 | &mut self, 163 | terminal: &mut DefaultTerminal, 164 | io: &mut SimulatedIO, 165 | config_key: &str, 166 | seed: u64, 167 | ) -> io::Result<()> { 168 | self.seed = seed; 169 | let mut last_tick = Instant::now(); 170 | let tick_rate = Duration::from_secs(1); 171 | let mut written_messages = Vec::new(); 172 | let mut failed_writes = Vec::new(); 173 | let mut counter = 0; 174 | let mut has_initialised = false; 175 | 176 | loop { 177 | if event::poll(Duration::from_millis(50))? { 178 | if let Event::Key(key) = event::read()? { 179 | match self.state { 180 | AppState::StartScreen => match key.code { 181 | KeyCode::Enter => { 182 | self.state = AppState::Running; 183 | } 184 | KeyCode::Char(q) => { 185 | break; 186 | } 187 | _ => (), 188 | }, 189 | AppState::Running => { 190 | if key.code == KeyCode::Char('q') { 191 | break; 192 | } 193 | } 194 | AppState::GameCompleted => { 195 | if key.code == KeyCode::Enter { 196 | break; 197 | } 198 | } 199 | AppState::GameOver => { 200 | if key.code == KeyCode::Enter { 201 | break; 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | if self.state == AppState::Running { 209 | if !has_initialised { 210 | match init_components(io).await { 211 | Ok(faults) => { 212 | for fault in faults { 213 | self.add_fault(fault); 214 | } 215 | has_initialised = true; 216 | self.add_connection_status_messages(); 217 | } 218 | Err(e) => { 219 | // TODO: Found an error. What should I do? Log it? 220 | error!("error while initialising components for simulation {:?}", e); 221 | self.death_reason = Some(format!("{:?}", e)); 222 | self.state = AppState::GameOver; 223 | std::thread::sleep(Duration::from_secs(2)); 224 | } 225 | } 226 | } 227 | info!("Done initialising the components while running game loop"); 228 | 229 | match run_simulation_step( 230 | io, 231 | config_key, 232 | &mut counter, 233 | &mut written_messages, 234 | &mut failed_writes, 235 | ) 236 | .await 237 | { 238 | Ok(faults) => { 239 | info!("the generated faults {:?}", faults); 240 | for fault in faults { 241 | self.add_fault(fault); 242 | } 243 | self.add_status_messages(); 244 | } 245 | Err(e) => { 246 | // TODO: Found an error. What should I do? Log it? 247 | error!("error while running run_simulation_step {:?}", e); 248 | self.death_reason = Some(format!("{:?}", e)); 249 | self.state = AppState::GameOver; 250 | std::thread::sleep(Duration::from_secs(2)); 251 | } 252 | } 253 | trace!("ran single step of the simulation"); 254 | 255 | if last_tick.elapsed() >= tick_rate { 256 | self.tick(); 257 | last_tick = Instant::now(); 258 | } 259 | 260 | if self.tick_count == 100 { 261 | self.state = AppState::GameCompleted; 262 | } 263 | } 264 | 265 | terminal.draw(|frame| { 266 | self.draw(frame, seed); 267 | })?; 268 | } 269 | Ok(()) 270 | } 271 | 272 | fn draw(&mut self, frame: &mut Frame, seed: u64) -> io::Result<()> { 273 | trace!("running the draw function"); 274 | match self.state { 275 | AppState::StartScreen => self.render_start_screen(frame), 276 | AppState::Running => self.render_game_screen(frame, seed), 277 | AppState::GameOver => self.render_game_over_screen(frame), 278 | AppState::GameCompleted => self.render_game_completed_screen(frame), 279 | }; 280 | 281 | Ok(()) 282 | } 283 | 284 | fn render_start_screen(&mut self, frame: &mut Frame) -> io::Result<()> { 285 | let area = frame.area(); 286 | 287 | let title_art = vec![ 288 | r"____ _ _ _ ___ ___", 289 | r"/ ___|(_)_ __ ___ _ _| | __ _| |_|_ _/ _ \ _ __ ", 290 | r"\___ \| | '_ ` _ \| | | | |/ _` | __|| | | | | '_ \ ", 291 | r"___) | | | | | | | |_| | | (_| | |_ | | |_| | | | |", 292 | r"|____/|_|_| |_| |_|\__,_|_|\__,_|\__|___\___/|_| |_|", 293 | "", 294 | "Fault Injection Simulator", 295 | ]; 296 | 297 | let robot_art = vec![r" 🤖 ", r" /|\ ", r" / \ "]; 298 | 299 | let instructions = vec![ 300 | "", 301 | "Are you ready to test your error handling?", 302 | "", 303 | "🎮 Press 'Enter' to start", 304 | "🚪 Press 'q' to quit", 305 | ]; 306 | 307 | let all_content = [title_art, robot_art, instructions].concat(); 308 | 309 | let styled_content = all_content 310 | .iter() 311 | .map(|&line| { 312 | Line::styled( 313 | line.to_string(), 314 | Style::default() 315 | .fg(Color::Green) 316 | .add_modifier(Modifier::BOLD), 317 | ) 318 | }) 319 | .collect::>(); 320 | 321 | let paragraph = Paragraph::new(styled_content) 322 | .alignment(Alignment::Center) 323 | .block( 324 | Block::default() 325 | .borders(Borders::ALL) 326 | .border_style( 327 | Style::default() 328 | .fg(Color::Green) 329 | .add_modifier(Modifier::BOLD), 330 | ) 331 | .title("Welcome") 332 | .title_alignment(Alignment::Center), 333 | ); 334 | 335 | frame.render_widget(paragraph, area); 336 | Ok(()) 337 | } 338 | 339 | fn render_game_over_screen(&self, frame: &mut Frame) -> io::Result<()> { 340 | let area = frame.area(); 341 | 342 | let game_over_art = vec![ 343 | r" ▄██████▄ ▄██████▄ ████████▄ ▄████████ ▄████████ ▄████████ ▄██████▄ ", 344 | r" ███ ███ ███ ███ ███ ▀███ ███ ███ ███ ███ ███ ███ ███ ███ ", 345 | r" ███ █▀ ███ ███ ███ ███ ███ █▀ ███ █▀ ███ ███ ███ ███ ", 346 | r" ▄███ ███ ███ ███ ███ ▄███▄▄▄ ▄███▄▄▄ ▄███▄▄▄▄██▄ ███ ███ ", 347 | r"▀▀███ ████▄ ███ ███ ███ ███ ▀▀███▀▀▀ ▀▀███▀▀▀ ▀▀███▀▀▀▀▀ ███ ███ ", 348 | r" ███ ███ ███ ███ ███ ███ ███ █▄ ███ █▄ ▀███████████ ███ ███ ", 349 | r" ███ ███ ███ ███ ███ ▄███ ███ ███ ███ ███ ███ ███ ███ ███ ", 350 | r" ████████▀ ▀██████▀ ████████▀ ██████████ ██████████ ███ ███ ▀██████▀ ", 351 | ]; 352 | 353 | let skull_art = vec![ 354 | r" ███████████ ", 355 | r" ███████████████ ", 356 | r" █████████████████ ", 357 | r" ███████████████████ ", 358 | r" ███ ███████ ███ ", 359 | r" ████ ██████████ ███ ", 360 | r" ████ ██████████ ███ ", 361 | r" ████ ████████ ███ ", 362 | r" ██████████████ ", 363 | r" ████████████ ", 364 | ]; 365 | 366 | let reason = { 367 | let mut str = "".to_string(); 368 | if let Some(reason) = &self.death_reason { 369 | str = format!("⚠️ Reason: {}", reason); 370 | } else { 371 | str = "⚠️ Reason: Unknown error occurred".to_string(); 372 | } 373 | str 374 | }; 375 | let seed_message = format!("Seed: {}", self.seed); 376 | let iterations_message = format!("Iterations: {}", self.tick_count); 377 | let death_message = vec![ 378 | "", 379 | "💀 SIMULATION CRASHED 💀", 380 | "", 381 | reason.as_str(), 382 | "", 383 | seed_message.as_str(), 384 | "", 385 | iterations_message.as_str(), 386 | "", 387 | "Press 'Enter' to exit", 388 | ]; 389 | 390 | let all_content = [ 391 | game_over_art, 392 | vec![""], // spacing 393 | skull_art, 394 | death_message, 395 | ] 396 | .concat(); 397 | 398 | let styled_content = all_content 399 | .iter() 400 | .map(|&line| { 401 | let base_style = Style::default().fg(Color::Red).add_modifier(Modifier::BOLD); 402 | 403 | // Add blinking effect to the skull and "SIMULATION CRASHED" text 404 | let style = if line.contains("💀") { 405 | base_style.add_modifier(Modifier::SLOW_BLINK) 406 | } else { 407 | base_style 408 | }; 409 | 410 | Line::styled(line.to_string(), style) 411 | }) 412 | .collect::>(); 413 | 414 | let paragraph = Paragraph::new(styled_content) 415 | .alignment(Alignment::Center) 416 | .block( 417 | Block::default() 418 | .borders(Borders::ALL) 419 | .border_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)) 420 | .title("Game Over") 421 | .title_alignment(Alignment::Center), 422 | ); 423 | 424 | frame.render_widget(paragraph, area); 425 | Ok(()) 426 | } 427 | 428 | fn render_game_screen(&mut self, frame: &mut Frame, seed: u64) -> io::Result<()> { 429 | let size = frame.area(); 430 | 431 | // Split the screen horizontally into two main sections (top & bottom) 432 | let main_layout = Layout::default() 433 | .direction(ratatui::layout::Direction::Vertical) 434 | .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) 435 | .split(size); 436 | 437 | // Split the top section into one for the progress bar and one for the two windows 438 | let top_split_layout = Layout::default() 439 | .direction(ratatui::layout::Direction::Vertical) 440 | .constraints([Constraint::Percentage(10), Constraint::Percentage(90)]) 441 | .split(main_layout[0]); 442 | 443 | // Split the bottom of the top_split into two separate sections (left & right) 444 | let top_second_split_layout = Layout::default() 445 | .direction(ratatui::layout::Direction::Horizontal) 446 | .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) 447 | .split(top_split_layout[1]); 448 | 449 | let gauge_view = self.render_gauge_view(); 450 | let app_view = self.render_app_view(seed); 451 | let fault_view = self.render_fault_log(); 452 | let status_view = self.render_status_log(); 453 | 454 | frame.render_widget(gauge_view, top_split_layout[0]); 455 | frame.render_widget(app_view, top_second_split_layout[0]); 456 | frame.render_widget(fault_view, top_second_split_layout[1]); 457 | frame.render_widget(status_view, main_layout[1]); 458 | Ok(()) 459 | } 460 | 461 | fn render_app_view<'a>(&self, seed: u64) -> Paragraph<'a> { 462 | let mut lines = vec![]; 463 | lines.push("Your application is under attack".to_string()); 464 | lines.push(format!("Seed: {}", seed)); 465 | 466 | let mut castle_structure = vec![ 467 | " ╔══════════════════════════╗ ".to_string(), // 0 468 | " ║ ▲ ▲ ▲ ▲ ║ ".to_string(), // 1 469 | " ╔════╣ ╠════╗ ".to_string(), // 2 470 | " ║ ║ ║ ║ ".to_string(), // 3 471 | " ║ ║ ║ ║ ".to_string(), // 4 472 | " ║ ║ ║ ║ ".to_string(), // 5 473 | " ║ ║ 🤖 ║ ║ ".to_string(), // 6 474 | " ║ ║ ║ ║ ".to_string(), // 7 475 | " ║ ║ ║ ║ ".to_string(), // 8 476 | " ║ ║ ║ ║ ".to_string(), // 9 477 | " ║ ║ ║ ║ ".to_string(), // 10 478 | " ║ ║ ║ ║ ".to_string(), // 11 479 | " ╚════╣ ╠════╝ ".to_string(), // 12 480 | " ║ ▼ ▼ ▼ ▼ ║ ".to_string(), // 13 481 | " ╚══════════════════════════╝ ".to_string(), // 14 482 | ]; 483 | 484 | // Create attack rows - 3 positions each for top and bottom 485 | let mut top_attacks = vec![" ".to_string(); 8]; 486 | let mut bottom_attacks = vec![" ".to_string(); 8]; 487 | 488 | // Place attacks and impacts 489 | for (fault, pos) in &self.active_faults { 490 | let symbol = fault.to_symbol().to_string(); 491 | let pos = *pos as usize; 492 | 493 | let mut rng = rand::thread_rng(); 494 | let choices = [0, 1, 2]; 495 | let choice = *choices.choose(&mut rng).unwrap(); 496 | 497 | match choice { 498 | 0 => { 499 | // top attack 500 | let choices = [0, 1, 2, 3, 4, 5, 6, 7]; 501 | let choice = *choices.choose(&mut rng).unwrap(); 502 | top_attacks[choice] = symbol; 503 | } 504 | 1 => { 505 | // bottom attack 506 | let choices = [0, 1, 2, 3, 4, 5, 6, 7]; 507 | let choice = *choices.choose(&mut rng).unwrap(); 508 | bottom_attacks[choice] = symbol; 509 | } 510 | 2 => { 511 | let impact = "💢"; 512 | let options = [ 513 | format!( 514 | " ║ {impact} ▲ ▲ ▲ ║ " 515 | ), 516 | format!( 517 | " ║ ▲ {impact} ▲ ▲ ║ " 518 | ), 519 | format!( 520 | " ║ ▲ ▲ {impact} ▲ ║ " 521 | ), 522 | format!( 523 | " ║ ▲ ▲ ▲ {impact} ║ " 524 | ), 525 | ]; 526 | let turret_wall = [1, 13]; 527 | let turret_wall_att = *turret_wall.choose(&mut rng).unwrap(); 528 | castle_structure[turret_wall_att] = options.choose(&mut rng).unwrap().clone(); 529 | } 530 | _ => (), 531 | } 532 | } 533 | 534 | // Build the complete view 535 | // Add top attack row 536 | lines.push(format!( 537 | " {} {} {} {} {} {} {} {} ", 538 | top_attacks[0], 539 | top_attacks[1], 540 | top_attacks[2], 541 | top_attacks[3], 542 | top_attacks[4], 543 | top_attacks[5], 544 | top_attacks[6], 545 | top_attacks[7] 546 | )); 547 | 548 | // Add castle structure 549 | lines.extend(castle_structure); 550 | 551 | // Add bottom attack row 552 | lines.push(format!( 553 | " {} {} {} {} {} {} {} {} ", 554 | bottom_attacks[0], 555 | bottom_attacks[1], 556 | bottom_attacks[2], 557 | bottom_attacks[3], 558 | bottom_attacks[4], 559 | bottom_attacks[5], 560 | bottom_attacks[6], 561 | bottom_attacks[7] 562 | )); 563 | 564 | Paragraph::new(lines.join("\n")) 565 | .alignment(Alignment::Center) 566 | .block(Block::default().borders(Borders::ALL).title("Application")) 567 | } 568 | 569 | fn render_game_completed_screen(&self, frame: &mut Frame) -> io::Result<()> { 570 | let area = frame.area(); 571 | 572 | let trophy_art = vec![ 573 | r" 🏆 ".to_string(), 574 | r" ___________ ".to_string(), 575 | r" '._==_==_=_.' ".to_string(), 576 | r" .-\: /-. ".to_string(), 577 | r" | (|:. |) | ".to_string(), 578 | r" '-|:. |-' ".to_string(), 579 | r" \::. / ".to_string(), 580 | r" '::. .' ".to_string(), 581 | r" ) ( ".to_string(), 582 | r" _.' '._ ".to_string(), 583 | r" '-------' ".to_string(), 584 | ]; 585 | 586 | let success_art = vec![ 587 | r" ____ ".to_string(), 588 | r"/ ___| _ _ ___ ___ ___ ___ ___ ".to_string(), 589 | r"\___ \| | | |/ __/ __/ _ \/ __/ __|".to_string(), 590 | r" ___) | |_| | (_| (_| __/\__ \__ \".to_string(), 591 | r"|____/ \__,_|\___\___\___||___/___/".to_string(), 592 | ]; 593 | 594 | let stats = vec![ 595 | "".to_string(), 596 | "🌟 SIMULATION COMPLETED SUCCESSFULLY 🌟".to_string(), 597 | "".to_string(), 598 | format!("🎯 Total Iterations: {}", self.tick_count).to_string(), 599 | format!("⚡ Faults Handled: {}", self.fault_log_counter).to_string(), 600 | format!("📝 Operations Logged: {}", self.status_log_counter).to_string(), 601 | "".to_string(), 602 | "You've successfully demonstrated the power of".to_string(), 603 | "Deterministic Simulation Testing!".to_string(), 604 | "".to_string(), 605 | "🎮 Press 'Enter' to exit".to_string(), 606 | "🔄 Run again with the same seed to reproduce this exact run!".to_string(), 607 | ]; 608 | 609 | let all_content = [ 610 | vec!["".to_string()], // Initial spacing 611 | success_art, 612 | vec!["".to_string()], // Spacing 613 | trophy_art, 614 | vec!["".to_string()], // Spacing 615 | stats, 616 | ] 617 | .concat(); 618 | 619 | let styled_content = all_content 620 | .iter() 621 | .map(|line| { 622 | let style = if line.contains('🏆') || line.contains('🌟') { 623 | Style::default() 624 | .fg(Color::Yellow) 625 | .add_modifier(Modifier::BOLD | Modifier::SLOW_BLINK) 626 | } else if line.starts_with('🎯') || line.starts_with('⚡') || line.starts_with('📝') 627 | { 628 | Style::default() 629 | .fg(Color::Cyan) 630 | .add_modifier(Modifier::BOLD) 631 | } else if line.contains("VICTORY") { 632 | Style::default() 633 | .fg(Color::Green) 634 | .add_modifier(Modifier::BOLD | Modifier::RAPID_BLINK) 635 | } else { 636 | Style::default() 637 | .fg(Color::Green) 638 | .add_modifier(Modifier::BOLD) 639 | }; 640 | 641 | Line::styled(line.to_string(), style) 642 | }) 643 | .collect::>(); 644 | 645 | let paragraph = Paragraph::new(styled_content) 646 | .alignment(Alignment::Center) 647 | .block( 648 | Block::default() 649 | .borders(Borders::ALL) // Using double borders for more emphasis 650 | .border_style( 651 | Style::default() 652 | .fg(Color::Yellow) // Changed to yellow for celebration 653 | .add_modifier(Modifier::BOLD), 654 | ) 655 | .title("🎉 Mission Accomplished! 🎉") 656 | .title_alignment(Alignment::Center), 657 | ); 658 | 659 | frame.render_widget(paragraph, area); 660 | Ok(()) 661 | } 662 | 663 | fn render_gauge_view<'a>(&self) -> ratatui::widgets::Gauge { 664 | let progress = (self.tick_count % 100) as u16; 665 | ratatui::widgets::Gauge::default() 666 | .block(Block::default().title("Iterations")) 667 | .gauge_style( 668 | Style::default() 669 | .fg(Color::Green) 670 | .bg(Color::Black) 671 | .add_modifier(Modifier::BOLD), 672 | ) 673 | .percent(progress) 674 | } 675 | 676 | fn render_fault_log<'a>(&self) -> Paragraph<'a> { 677 | trace!("rendering the fault log"); 678 | let styled_faults: Vec = self 679 | .fault_log 680 | .iter() 681 | .flat_map(|msg| { 682 | // Create two lines for each fault for bigger appearance 683 | vec![ 684 | Line::styled( 685 | "━━━━━━━━━━━━━━━━━━━━━".to_string(), 686 | Style::default().fg(Color::Red), 687 | ), 688 | Line::styled( 689 | format!(" ⚠️⚠️ {} ", msg), // Double warning emoji 690 | Style::default() 691 | .fg(Color::White) 692 | .bg(Color::Red) 693 | .add_modifier(Modifier::BOLD | Modifier::SLOW_BLINK), 694 | ), 695 | ] 696 | }) 697 | .collect(); 698 | 699 | Paragraph::new(styled_faults).block( 700 | Block::default() 701 | .borders(Borders::ALL) 702 | .border_style(Style::default().fg(Color::Red)) 703 | .title("⚠️ ACTIVE FAULTS ⚠️"), // Added emoji to title 704 | ) 705 | } 706 | 707 | fn render_status_log<'a>(&self) -> Paragraph<'a> { 708 | trace!("rendering the status log"); 709 | let styled_statuses: Vec = self 710 | .status_log 711 | .iter() 712 | .enumerate() 713 | .flat_map(|(idx, msg)| { 714 | // Create two lines for each status for bigger appearance 715 | vec![ 716 | Line::styled( 717 | "─────────────────────".to_string(), 718 | Style::default().fg(Color::Green), 719 | ), 720 | Line::styled( 721 | format!(" ✅✅ {} ", msg), // Double checkmark 722 | Style::default() 723 | .fg(Color::Green) 724 | .add_modifier(Modifier::BOLD) 725 | .add_modifier(if idx >= self.status_log.len().saturating_sub(3) { 726 | Modifier::RAPID_BLINK 727 | } else { 728 | Modifier::empty() 729 | }), 730 | ), 731 | ] 732 | }) 733 | .collect(); 734 | 735 | Paragraph::new(styled_statuses) 736 | .scroll((self.status_log.len().saturating_sub(8) as u16, 0)) 737 | .block( 738 | Block::default() 739 | .borders(Borders::ALL) 740 | .title("✅ SYSTEM STATUS ✅") 741 | .border_style(Style::default().fg(Color::Green)), 742 | ) 743 | } 744 | } 745 | --------------------------------------------------------------------------------