From a8e8ff76ae27b5ad859ba7353baa666d5a0c4634 Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 00:04:19 +0200 Subject: [PATCH 01/12] some stuff tm --- .vscode/settings.json | 5 + Cargo.lock | 1516 +---------------- Cargo.toml | 9 + examples/prototype/Cargo.toml | 7 +- features/wrapp-config/Cargo.toml | 8 +- features/wrapp-di/Cargo.toml | 8 +- .../main.rs => examples/manual-di-impl.rs} | 25 +- features/wrapp-di/src/builder.rs | 6 - features/wrapp-di/src/factories.rs | 4 +- features/wrapp-di/src/initiator.rs | 15 +- features/wrapp-di/src/lib.rs | 6 +- features/wrapp-di/src/resolver/lazy.rs | 4 +- features/wrapp-router/Cargo.toml | 8 +- features/wrapp-strategies/Cargo.toml | 8 +- strategies/wrapp-actix/Cargo.toml | 8 +- wrapp/Cargo.toml | 8 +- 16 files changed, 147 insertions(+), 1498 deletions(-) create mode 100644 .vscode/settings.json rename features/wrapp-di/{src/main.rs => examples/manual-di-impl.rs} (58%) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f4903e8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "oneshot" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index fc9bd48..a98fd16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,471 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "actix-codec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "base64", - "bitflags", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "foldhash", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" -dependencies = [ - "bytestring", - "cfg-if", - "http", - "regex", - "regex-lite", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.10", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-utils", - "actix-web-codegen", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "foldhash", - "futures-core", - "futures-util", - "impl-more", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "regex-lite", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.6.4", - "time", - "tracing", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" - -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "brotli" -version = "8.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bytes" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" - -[[package]] -name = "bytestring" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" -dependencies = [ - "bytes", -] - -[[package]] -name = "cc" -version = "1.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core", -] - -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", -] - -[[package]] -name = "displaydoc" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - [[package]] name = "futures" version = "0.3.32" @@ -502,576 +37,24 @@ checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" name = "futures-executor" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core", -] - -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" -dependencies = [ - "typenum", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "impl-more" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "local-channel" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" -dependencies = [ - "futures-core", - "futures-sink", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "memchr" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prototype" -version = "0.1.0" -dependencies = [ - "actix-web", - "tokio", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ - "serde_core", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "futures-io" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] -name = "serde_derive" -version = "1.0.228" +name = "futures-macro" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1079,100 +62,75 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.11.0" +name = "futures-sink" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] -name = "shlex" -version = "2.0.1" +name = "futures-task" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] -name = "signal-hook-registry" -version = "1.4.8" +name = "futures-util" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "errno", - "libc", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", ] [[package]] -name = "simd-adler32" -version = "0.3.9" +name = "memchr" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] -name = "slab" -version = "0.4.12" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "smallvec" -version = "1.15.2" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "socket2" -version = "0.5.10" +name = "proc-macro2" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ - "libc", - "windows-sys 0.52.0", + "unicode-ident", ] [[package]] -name = "socket2" -version = "0.6.4" +name = "quote" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ - "libc", - "windows-sys 0.61.2", + "proc-macro2", ] [[package]] -name = "stable_deref_trait" -version = "1.2.1" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "syn" @@ -1185,17 +143,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thiserror" version = "2.0.18" @@ -1216,94 +163,12 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.3.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2 0.6.4", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1329,178 +194,32 @@ dependencies = [ "once_cell", ] -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "wrapp" -version = "0.1.0" +version = "0.0.0" [[package]] name = "wrapp-actix" -version = "0.1.0" +version = "0.0.0" [[package]] name = "wrapp-config" -version = "0.1.0" +version = "0.0.0" +dependencies = [ + "thiserror", + "tracing", + "wrapp-di", +] [[package]] name = "wrapp-di" -version = "0.1.0" +version = "0.0.0" dependencies = [ "futures", "futures-channel", @@ -1511,125 +230,8 @@ dependencies = [ [[package]] name = "wrapp-router" -version = "0.1.0" +version = "0.0.0" [[package]] name = "wrapp-strategies" -version = "0.1.0" - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] +version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index c36dc3a..440b99c 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,10 @@ [workspace] resolver = "2" members = ["examples/*", "features/*", "strategies/*", "wrapp"] +exclude = ["examples/prototype"] + +package.edition = "2024" +package.version = "0.0.0" [workspace.dependencies] @@ -13,3 +17,8 @@ futures-channel = "0.3" tracing = "0.1" pin-project-lite = "0.2" thiserror = "2.0" + + +[workspace.lints.clippy] +pedantic = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } diff --git a/examples/prototype/Cargo.toml b/examples/prototype/Cargo.toml index eb1b223..8a49534 100644 --- a/examples/prototype/Cargo.toml +++ b/examples/prototype/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "prototype" -version = "0.1.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,3 +12,6 @@ tokio = { version = "1.43.0", features = ["full"] } [[bin]] name = "prototype" path = "main.rs" + +[lints] +workspace = true diff --git a/features/wrapp-config/Cargo.toml b/features/wrapp-config/Cargo.toml index 257aacf..4e7f770 100644 --- a/features/wrapp-config/Cargo.toml +++ b/features/wrapp-config/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "wrapp-config" -version = "0.1.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,3 +10,7 @@ wrapp-di.workspace = true thiserror.workspace = true tracing.workspace = true + + +[lints] +workspace = true diff --git a/features/wrapp-di/Cargo.toml b/features/wrapp-di/Cargo.toml index b370ee6..ec3faf9 100755 --- a/features/wrapp-di/Cargo.toml +++ b/features/wrapp-di/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "wrapp-di" -version = "0.1.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,3 +11,7 @@ futures-channel.workspace = true tracing.workspace = true pin-project-lite.workspace = true thiserror.workspace = true + + +[lints] +workspace = true diff --git a/features/wrapp-di/src/main.rs b/features/wrapp-di/examples/manual-di-impl.rs similarity index 58% rename from features/wrapp-di/src/main.rs rename to features/wrapp-di/examples/manual-di-impl.rs index d6994b6..384c634 100644 --- a/features/wrapp-di/src/main.rs +++ b/features/wrapp-di/examples/manual-di-impl.rs @@ -1,3 +1,9 @@ +//! An example of how a manual DI implementation can be done using the Wrapp DI framework. +//! +//! Usually you should use the wrapp-di procmacros to generate the factory for you, +//! but this example shows how to do it manually to help understanding what happens under the hood. +#![allow(dead_code)] + use std::{error::Error, fmt::Debug, sync::Arc}; use wrapp_di::{ @@ -12,11 +18,13 @@ use wrapp_di::{ }; fn main() { + // Create a new DI builder and register instances and factories let app = DiBuilder::new() .add_instance(124_i128) .add_instance("test".to_string()) .add_factory(TestFactory); + // Build the DI container and handle any errors that may occur let app = futures::executor::block_on(app.build()); let app = match app { Ok(app) => app, @@ -27,13 +35,11 @@ fn main() { }; println!("{:?}", app); - let t = app.require::(); - println!("{:?}", t); - - let a = Arc::new("String".to_owned()); - let b: &dyn Debug = &a; + let test_instance = app.require::(); + println!("{:?}", test_instance); } +/// Test struct requiring an Arc, a Lazy and a LazyOption as dependencies. #[derive(Debug)] struct Test { a: Arc, @@ -43,7 +49,9 @@ struct Test { struct TestFactory; impl InstanceFactory for TestFactory { type Provides = Test; - fn get_dependencies() -> Vec { + + /// Defines the dependencies required by the TestFactory. + fn dependencies() -> Vec { vec![ Arc::::dependency_info(), Lazy::::dependency_info(), @@ -51,13 +59,18 @@ impl InstanceFactory for TestFactory { ] } + /// Constructs a new instance of the Test struct with dependencies injected from the DI container. + #[allow(refining_impl_trait)] async fn construct( &mut self, mut di: DiHandle, ) -> Result> { + // Resolve the dependencies from the DI container let str = Arc::::resolve(&mut di).await?; let str2 = Lazy::::resolve(&mut di).await?; let str3 = LazyOption::::resolve(&mut di).await?; + + // Return a new instance of Test with the resolved dependencies Ok(Test { a: str, b: str2, diff --git a/features/wrapp-di/src/builder.rs b/features/wrapp-di/src/builder.rs index 9801144..fd62ea0 100644 --- a/features/wrapp-di/src/builder.rs +++ b/features/wrapp-di/src/builder.rs @@ -8,12 +8,6 @@ use crate::{ types::{Injectable, Instance, TypeInfo}, }; -////////////////////////////////////////////////////////////////////// -/// -/// The DI Consist of three Parts. -/// 1. The AppBuilder where one registers all factories and instances -/// 2. Then for initialization - pub struct DiBuilder { diff --git a/features/wrapp-di/src/factories.rs b/features/wrapp-di/src/factories.rs index c9a965f..977feb5 100644 --- a/features/wrapp-di/src/factories.rs +++ b/features/wrapp-di/src/factories.rs @@ -15,7 +15,7 @@ pub trait InstanceFactory: Send + Sync { } /// Returns a list of dependencies the factory requires to supply it's type - fn get_dependencies() -> Vec; + fn dependencies() -> Vec; /// Constructs a new instance of the factory's provided type /// @@ -61,7 +61,7 @@ impl> DynFactory f } fn dependencies(&self) -> Vec { - SpecificFactory::get_dependencies() + SpecificFactory::dependencies() } fn construct( diff --git a/features/wrapp-di/src/initiator.rs b/features/wrapp-di/src/initiator.rs index 6b355b0..2a1c938 100644 --- a/features/wrapp-di/src/initiator.rs +++ b/features/wrapp-di/src/initiator.rs @@ -284,15 +284,12 @@ impl DiInitiator { } // Check if we already have a result - match self.instances.get(&info.type_id) { - Some((_, result)) => { - let _ = match result { - Some(instance) => response_channel.send(Ok(instance.clone())), - None => response_channel.send(Err(RequireError::TypeDisabled(info.type_name))), - }; - return; - } - None => {} // No result yet + if let Some((_, result)) = self.instances.get(&info.type_id) { + let _ = match result { + Some(instance) => response_channel.send(Ok(instance.clone())), + None => response_channel.send(Err(RequireError::TypeDisabled(info.type_name))), + }; + return; } // Otherwise add the request to the waiters list diff --git a/features/wrapp-di/src/lib.rs b/features/wrapp-di/src/lib.rs index 7fa6065..cc7be9e 100644 --- a/features/wrapp-di/src/lib.rs +++ b/features/wrapp-di/src/lib.rs @@ -1,8 +1,10 @@ -//! Wrapp DI consists of following components: +//! Wrapp DI +//! +//! Wrapp DI consists of following components: //! //! 1. DiBuilder - for registering all factories and instances. //! 2. DiInitiator - which executes all factories, and handles injection requests during their initialization. -//! 2.1 Injection Handles - which are valid during the AppInit Phase, and allow a component to request its dependencies. +//! 2.1. Injection Handles - which are valid during the AppInit Phase, and allow a component to request its dependencies. //! 3. DIContainer - which is the final container that holds all created instances. //! //! General logic: diff --git a/features/wrapp-di/src/resolver/lazy.rs b/features/wrapp-di/src/resolver/lazy.rs index d7bc601..a512c7c 100644 --- a/features/wrapp-di/src/resolver/lazy.rs +++ b/features/wrapp-di/src/resolver/lazy.rs @@ -131,7 +131,7 @@ impl Lazy { /// /// Must not be waited on during module construction // Note: Maybe Add a second DI stage (Injection, Pre Start) - where this is allowed - pub fn wait_result(&self) -> LazyFuture { + pub fn wait_result<'a>(&'a self) -> LazyFuture<'a, T> { LazyFuture { lazy: &self.0 } } } @@ -151,7 +151,7 @@ impl Lazy { } pin_project! { - struct LazyFuture<'a, T:Injectable> { + pub struct LazyFuture<'a, T:Injectable> { #[pin] lazy: &'a LazyInner, } diff --git a/features/wrapp-router/Cargo.toml b/features/wrapp-router/Cargo.toml index 3194615..b3e12e0 100644 --- a/features/wrapp-router/Cargo.toml +++ b/features/wrapp-router/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "wrapp-router" -version = "0.1.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + + +[lints] +workspace = true diff --git a/features/wrapp-strategies/Cargo.toml b/features/wrapp-strategies/Cargo.toml index 9b1d373..8884af2 100644 --- a/features/wrapp-strategies/Cargo.toml +++ b/features/wrapp-strategies/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "wrapp-strategies" -version = "0.1.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + + +[lints] +workspace = true diff --git a/strategies/wrapp-actix/Cargo.toml b/strategies/wrapp-actix/Cargo.toml index d050981..a05c071 100644 --- a/strategies/wrapp-actix/Cargo.toml +++ b/strategies/wrapp-actix/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "wrapp-actix" -version = "0.1.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + + +[lints] +workspace = true diff --git a/wrapp/Cargo.toml b/wrapp/Cargo.toml index 22d42e0..702bc9f 100644 --- a/wrapp/Cargo.toml +++ b/wrapp/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "wrapp" -version = "0.1.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + + +[lints] +workspace = true From c7a70788a8f282e5e8da58109e94f202509e20f4 Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 00:32:58 +0200 Subject: [PATCH 02/12] clippy fix --- Cargo.toml | 4 ++ .../examples/using-config-provider.rs | 9 +-- features/wrapp-config/src/provider.rs | 12 ++-- features/wrapp-config/src/resolver.rs | 6 +- features/wrapp-di/examples/manual-di-impl.rs | 10 ++-- features/wrapp-di/src/builder.rs | 3 +- features/wrapp-di/src/container.rs | 1 + features/wrapp-di/src/dependency_graph.rs | 4 +- features/wrapp-di/src/factories.rs | 5 +- features/wrapp-di/src/initiator.rs | 55 +++++++++---------- features/wrapp-di/src/lib.rs | 26 ++++----- features/wrapp-di/src/resolver/arc.rs | 4 +- features/wrapp-di/src/resolver/lazy.rs | 20 ++++--- features/wrapp-di/src/types.rs | 11 +++- features/wrapp-router/src/lib.rs | 3 +- features/wrapp-strategies/src/lib.rs | 3 +- strategies/wrapp-actix/src/lib.rs | 3 +- wrapp/src/lib.rs | 3 +- 18 files changed, 97 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 440b99c..14d3679 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,7 @@ thiserror = "2.0" [workspace.lints.clippy] pedantic = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } + +option_if_let_else = { level = "allow" } # More readable +items_after_statements = { level = "allow" } # More readable +match_bool = { level = "allow" } # Personal preference diff --git a/features/wrapp-config/examples/using-config-provider.rs b/features/wrapp-config/examples/using-config-provider.rs index 1aabab1..6e0da83 100644 --- a/features/wrapp-config/examples/using-config-provider.rs +++ b/features/wrapp-config/examples/using-config-provider.rs @@ -23,12 +23,9 @@ fn main() { } }; - let retrieved_config = match config_provider.config::() { - Some(c) => c, - None => { - eprintln!("Could not find config type"); - return; - } + let retrieved_config = if let Some(c) = config_provider.config::() { c } else { + eprintln!("Could not find config type"); + return; }; assert_eq!(app_config.host, retrieved_config.host); diff --git a/features/wrapp-config/src/provider.rs b/features/wrapp-config/src/provider.rs index 88c4d97..06ef071 100644 --- a/features/wrapp-config/src/provider.rs +++ b/features/wrapp-config/src/provider.rs @@ -20,6 +20,7 @@ pub struct ConfigProvider { impl ConfigProvider { /// Initializes an empty Config Provider + #[must_use] pub fn new() -> Self { Self { configs: HashMap::new(), @@ -33,13 +34,10 @@ impl ConfigProvider { let config = self.configs .get(&type_id)?; - match config.clone().downcast::() { - Ok(config) => Some(config), - Err(_) => { - debug_assert!(false, "Config Provider contained invalid type in slot for type: {:?}", TypeInfo::of::()); - tracing::error!("Config Provider contained invalid type in slot for type: {:?}", TypeInfo::of::()); - None - }, + if let Ok(config) = config.clone().downcast::() { Some(config) } else { + debug_assert!(false, "Config Provider contained invalid type in slot for type: {:?}", TypeInfo::of::()); + tracing::error!("Config Provider contained invalid type in slot for type: {:?}", TypeInfo::of::()); + None } } diff --git a/features/wrapp-config/src/resolver.rs b/features/wrapp-config/src/resolver.rs index b5a1b72..845df2f 100644 --- a/features/wrapp-config/src/resolver.rs +++ b/features/wrapp-config/src/resolver.rs @@ -58,10 +58,12 @@ impl Deref for Config { } } impl Config { + #[must_use] pub fn inner(&self) -> Arc { self.inner.clone() } + #[must_use] pub fn into_inner(self) -> Arc { self.inner } @@ -79,12 +81,12 @@ impl Resolver for Config { .config() .ok_or_else(|| InjectError::RequireError(RequireError::TypeMissing(config_name)))?; - Ok(Config { inner: config }) + Ok(Self { inner: config }) } fn dependency_info() -> DependencyInfo { DependencyInfo { - type_info: TypeInfo::of::>(), + type_info: TypeInfo::of::(), optional: false, lazy: false, } diff --git a/features/wrapp-di/examples/manual-di-impl.rs b/features/wrapp-di/examples/manual-di-impl.rs index 384c634..b591e02 100644 --- a/features/wrapp-di/examples/manual-di-impl.rs +++ b/features/wrapp-di/examples/manual-di-impl.rs @@ -29,17 +29,17 @@ fn main() { let app = match app { Ok(app) => app, Err(e) => { - println!("{}", e); + println!("{e}"); return; } }; - println!("{:?}", app); + println!("{app:?}"); let test_instance = app.require::(); - println!("{:?}", test_instance); + println!("{test_instance:?}"); } -/// Test struct requiring an Arc, a Lazy and a LazyOption as dependencies. +/// Test struct requiring an Arc, a Lazy and a `LazyOption` as dependencies. #[derive(Debug)] struct Test { a: Arc, @@ -50,7 +50,7 @@ struct TestFactory; impl InstanceFactory for TestFactory { type Provides = Test; - /// Defines the dependencies required by the TestFactory. + /// Defines the dependencies required by the `TestFactory`. fn dependencies() -> Vec { vec![ Arc::::dependency_info(), diff --git a/features/wrapp-di/src/builder.rs b/features/wrapp-di/src/builder.rs index fd62ea0..a549ee1 100644 --- a/features/wrapp-di/src/builder.rs +++ b/features/wrapp-di/src/builder.rs @@ -23,8 +23,9 @@ impl Default for DiBuilder { } impl DiBuilder { + #[must_use] pub fn new() -> Self { - DiBuilder { + Self { registered_factories: Vec::new(), registered_instances: HashMap::new(), } diff --git a/features/wrapp-di/src/container.rs b/features/wrapp-di/src/container.rs index 75bbdbe..baac40c 100644 --- a/features/wrapp-di/src/container.rs +++ b/features/wrapp-di/src/container.rs @@ -57,6 +57,7 @@ impl DiContainer { } } + #[must_use] pub fn graph(&self) -> &DependencyGraph { &self.0.graph } diff --git a/features/wrapp-di/src/dependency_graph.rs b/features/wrapp-di/src/dependency_graph.rs index 0d779e9..bfcb1ca 100644 --- a/features/wrapp-di/src/dependency_graph.rs +++ b/features/wrapp-di/src/dependency_graph.rs @@ -94,7 +94,7 @@ impl DependencyGraph { // Skip other checks if already checked if !checked.insert(entry.info.type_id) { return; - }; + } dependency_chain.push(entry.info); @@ -149,7 +149,7 @@ impl std::fmt::Display for DependencyGraphErrors { let mut display = Vec::new(); display.push("The dependency graph had one or more errors:".to_string()); for error in &self.errors { - display.push(format!("- {}", error)); + display.push(format!("- {error}")); } f.write_str(&display.join("\n")) } diff --git a/features/wrapp-di/src/factories.rs b/features/wrapp-di/src/factories.rs index 977feb5..0c649f5 100644 --- a/features/wrapp-di/src/factories.rs +++ b/features/wrapp-di/src/factories.rs @@ -10,6 +10,7 @@ pub trait InstanceFactory: Send + Sync { type Provides: Injectable; /// Returns the typeinfo about the factory's provided type + #[must_use] fn supplies() -> TypeInfo { TypeInfo::of::() } @@ -73,7 +74,7 @@ impl> DynFactory f SpecificFactory::construct(self, di) .await .map(Instance::new) - .map_err(|e| e.into()) + .map_err(std::convert::Into::into) }; Box::new(construction_fut) @@ -87,7 +88,7 @@ impl> DynFactory f // Forward the call to the specific implementation SpecificFactory::is_enabled(self, di) .await - .map_err(|e| e.into()) + .map_err(std::convert::Into::into) }; Box::new(future) diff --git a/features/wrapp-di/src/initiator.rs b/features/wrapp-di/src/initiator.rs index 2a1c938..1548314 100644 --- a/features/wrapp-di/src/initiator.rs +++ b/features/wrapp-di/src/initiator.rs @@ -18,7 +18,7 @@ use crate::{ types::{DynError, Instance, TypeInfo}, }; -/// Initiates the DiContainer +/// Initiates the `DiContainer` pub(crate) struct DiInitiator { request_rx: mpsc::Receiver, request_tx: mpsc::Sender, @@ -27,16 +27,16 @@ pub(crate) struct DiInitiator { /// Waiters for instance results instance_waiters: HashMap>>, - /// Waiters for the final DiContainer + /// Waiters for the final `DiContainer` container_waiters: Vec>, /// All produced instances - None = Type is known but disabled instances: HashMap)>, } impl DiInitiator { - pub(crate) fn new() -> DiInitiator { + pub(crate) fn new() -> Self { let (injection_request_sender, injection_request_receiver) = mpsc::channel(10); - DiInitiator { + Self { request_rx: injection_request_receiver, request_tx: injection_request_sender, all_registered_type_ids: HashSet::new(), @@ -59,7 +59,7 @@ impl DiInitiator { sleep(timeout); let _ = timeout_tx.send(()); }); - }; + } // Build and check Graph let graph = DependencyGraph::new(&blueprint).map_err(|error| DependencyGraphErrors { @@ -73,7 +73,7 @@ impl DiInitiator { // On fail - inform all waiters let msg = Err(e.clone().into()); for (_, waiters) in self.instance_waiters { - for waiter in waiters.into_iter() { + for waiter in waiters { let _ = waiter.send(msg.clone()); } } @@ -115,7 +115,7 @@ impl DiInitiator { // Add all pre build instances to the results - for (info, instance) in registered_instances.into_iter() { + for (info, instance) in registered_instances { self.all_registered_type_ids.insert(info.type_id); self.instances.insert(info.type_id, (info, Some(instance))); } @@ -134,14 +134,11 @@ impl DiInitiator { let supply_info = factory.supplies(); let result = async { // Check if the factory is enabled - match Box::into_pin(factory.is_enabled(handle.clone())).await? { - true => { - tracing::debug!("Factory for {} is enabled", supply_info.type_name) - } - false => { - tracing::debug!("Factory for {} is disabled", supply_info.type_name); - return Ok(None); - } + if Box::into_pin(factory.is_enabled(handle.clone())).await? { + tracing::debug!("Factory for {} is enabled", supply_info.type_name); + } else { + tracing::debug!("Factory for {} is disabled", supply_info.type_name); + return Ok(None); } // Construct factory @@ -193,17 +190,14 @@ impl DiInitiator { &mut self, result: Option<(TypeInfo, Result, DynError>)>, ) -> Result { - let (info, result) = match result { - Some(result) => result, - None => { - // If no more tasks are left, exit the loop - // all injection requests must now also be handled as nothing is left to be build - debug_assert!( - self.instance_waiters.is_empty(), - "Not all waiters were satisfied" - ); - return Ok(true); - } + let (info, result) = if let Some(result) = result { result } else { + // If no more tasks are left, exit the loop + // all injection requests must now also be handled as nothing is left to be build + debug_assert!( + self.instance_waiters.is_empty(), + "Not all waiters were satisfied" + ); + return Ok(true); }; match result { @@ -217,7 +211,7 @@ impl DiInitiator { error: Arc::new(err), }); } - }; + } return Ok(false); @@ -248,7 +242,7 @@ impl DiInitiator { } } - /// Get a handle to the DiInitiator + /// Get a handle to the `DiInitiator` /// /// The handle is only valid before and during Initiation. pub fn get_handle(&self) -> DiHandle { @@ -305,6 +299,7 @@ impl DiInitiator { } /// DI Handle for resolving dependencies and getting instances from the registry. +/// /// The DI Handle is only valid during instantiation of the Application. /// Afterwards the DI Container can be used directly for dependency injection. #[derive(Clone)] @@ -320,14 +315,14 @@ impl DiHandle { pub type DiResponseSender = oneshot::Sender>; pub type DiResponseReceiver = oneshot::Receiver>; -/// Requests between [DiHandle] and [DiInitiator] +/// Requests between [`DiHandle`] and [`DiInitiator`] pub enum DiRequest { /// Requires an instance of a specific type Require { type_info: TypeInfo, response_channel: DiResponseSender, }, - /// Requires a reference to the [DiContainer] once it has been build + /// Requires a reference to the [`DiContainer`] once it has been build RequireApp { response_channel: DiResponseSender, }, diff --git a/features/wrapp-di/src/lib.rs b/features/wrapp-di/src/lib.rs index cc7be9e..80d2ff8 100644 --- a/features/wrapp-di/src/lib.rs +++ b/features/wrapp-di/src/lib.rs @@ -2,28 +2,28 @@ //! //! Wrapp DI consists of following components: //! -//! 1. DiBuilder - for registering all factories and instances. -//! 2. DiInitiator - which executes all factories, and handles injection requests during their initialization. -//! 2.1. Injection Handles - which are valid during the AppInit Phase, and allow a component to request its dependencies. -//! 3. DIContainer - which is the final container that holds all created instances. +//! 1. `DiBuilder` - for registering all factories and instances. +//! 2. `DiInitiator` - which executes all factories, and handles injection requests during their initialization. +//! 2.1. Injection Handles - which are valid during the `AppInit` Phase, and allow a component to request its dependencies. +//! 3. `DIContainer` - which is the final container that holds all created instances. //! //! General logic: -//! AppBuilder calls BUild +//! `AppBuilder` calls `BUild` //! -//! AppGraph queries all factories for their dependencies and creates a graph. -//! AppGraph checks the graph for circular dependencies, and aborts DI if: +//! `AppGraph` queries all factories for their dependencies and creates a graph. +//! `AppGraph` checks the graph for circular dependencies, and aborts DI if: //! - Any required Dependencies are missing //! - Any Circular Dependencies were found //! -//! AppInitiator creates tasks for all factories. +//! `AppInitiator` creates tasks for all factories. //! Factories start asking for their dependencies. //! Some Factory will not require a dependency (or just requires already existing instances), and as such will complete -//! After Completion, AppInitiator gets the result of the completed task. -//! AppInitiator then informs all factories which were waiting for this dependency. +//! After Completion, `AppInitiator` gets the result of the completed task. +//! `AppInitiator` then informs all factories which were waiting for this dependency. //! Step by step all factories will complete -//! After All factories have completed, AppInitiator creates the final DIContainer. -//! AppInitiator publishes the DIcontainer through a channel to all dependencies. -//! DiContainer wil also be given to the caller of AppBuilder.build() +//! After All factories have completed, `AppInitiator` creates the final `DIContainer`. +//! `AppInitiator` publishes the `DIcontainer` through a channel to all dependencies. +//! `DiContainer` wil also be given to the caller of `AppBuilder.build()` //! pub mod builder; diff --git a/features/wrapp-di/src/resolver/arc.rs b/features/wrapp-di/src/resolver/arc.rs index 9b95b70..ffc0832 100644 --- a/features/wrapp-di/src/resolver/arc.rs +++ b/features/wrapp-di/src/resolver/arc.rs @@ -50,8 +50,8 @@ impl Resolver for Option { Ok(resolved) => Ok(Some(resolved)), Err(e) => match e { // If the required type is disabled, or not registered Option does not fail - InjectError::RequireError(RequireError::TypeDisabled(_)) - | InjectError::RequireError(RequireError::TypeMissing(_)) => Ok(None), + InjectError::RequireError(RequireError::TypeDisabled(_) | +RequireError::TypeMissing(_)) => Ok(None), _ => Err(e), }, } diff --git a/features/wrapp-di/src/resolver/lazy.rs b/features/wrapp-di/src/resolver/lazy.rs index a512c7c..69a0f32 100644 --- a/features/wrapp-di/src/resolver/lazy.rs +++ b/features/wrapp-di/src/resolver/lazy.rs @@ -66,7 +66,7 @@ impl Resolver for Lazy { // Check if we got an immediate error - Ok(Lazy(Arc::new(LazyInner { + Ok(Self(Arc::new(LazyInner { once: OnceLock::new(), rx: Mutex::new(rx), }))) @@ -85,6 +85,7 @@ impl Lazy { /// /// # Panics /// - When accessed before the DI Container Init has completed + #[must_use] pub fn get(&self) -> &Arc { self.try_get() .expect("Lazy inject accessed before initialized") @@ -99,7 +100,7 @@ impl Lazy { } // Lock receiver, so result is not taken out while we check - let mut recv = self.0.rx.lock().unwrap(); + let mut recv = self.0.rx.lock().expect("we don't handle poisoning"); // Double check once - it might have been set while we waited for the lock if let Some(result) = self.0.once.get() { @@ -109,7 +110,7 @@ impl Lazy { // Otherwise, try to receive result, and set once match recv.try_recv() { Ok(Some(rx)) => { - let res = Lazy::::downcast_recv(rx); + let res = Self::downcast_recv(rx); self.0 .once .set(res) @@ -131,7 +132,8 @@ impl Lazy { /// /// Must not be waited on during module construction // Note: Maybe Add a second DI stage (Injection, Pre Start) - where this is allowed - pub fn wait_result<'a>(&'a self) -> LazyFuture<'a, T> { + #[must_use] + pub fn wait_result(&self) -> LazyFuture<'_, T> { LazyFuture { lazy: &self.0 } } } @@ -206,7 +208,7 @@ impl Resolver for LazyOption { where Self: Sized, { - Ok(LazyOption { + Ok(Self { lazy: Lazy::::resolve(handle).await?, }) } @@ -221,6 +223,7 @@ impl Resolver for LazyOption { } impl LazyOption { /// Accesses the Lazy Dependency - returning an error on access + #[must_use] pub fn try_get(&self) -> Option, &InjectError>> { self.lazy.try_get() } @@ -229,17 +232,18 @@ impl LazyOption { /// /// # Panics /// - If accessed after DI has failed + #[must_use] pub fn get(&self) -> Option<&Arc> { match self.lazy.try_get() { None => None, Some(Ok(result)) => Some(result), Some(Err(err)) => match err { - InjectError::RequireError(RequireError::TypeDisabled(_)) - | InjectError::RequireError(RequireError::TypeMissing(_)) => { + InjectError::RequireError(RequireError::TypeDisabled(_) | +RequireError::TypeMissing(_)) => { None } err => { - panic!("Accessed LazyOption after DI failure: {:?}", err); + panic!("Accessed LazyOption after DI failure: {err:?}"); } }, } diff --git a/features/wrapp-di/src/types.rs b/features/wrapp-di/src/types.rs index 4ad4d04..41b9563 100644 --- a/features/wrapp-di/src/types.rs +++ b/features/wrapp-di/src/types.rs @@ -20,12 +20,16 @@ pub struct Instance { impl Instance { pub(crate) fn new(instance: ExistingInstance) -> Self { - Instance { + Self { info: TypeInfo::of::(), instance: Arc::new(instance), } } + /// Attempts to downcast the instance to the requested type + /// + /// ### Errors + /// - Returns an error if the instance cannot be downcasted to the requested type pub fn downcast(&self) -> Result, &'static str> { match Arc::downcast::(self.instance.clone()) { Ok(downcasted) => Ok(downcasted), @@ -56,8 +60,9 @@ impl std::fmt::Display for TypeInfo { } } impl TypeInfo { - pub fn of() -> TypeInfo { - TypeInfo { + #[must_use] + pub fn of() -> Self { + Self { type_name: std::any::type_name::(), type_id: TypeId::of::(), } diff --git a/features/wrapp-router/src/lib.rs b/features/wrapp-router/src/lib.rs index 7d12d9a..21739bf 100644 --- a/features/wrapp-router/src/lib.rs +++ b/features/wrapp-router/src/lib.rs @@ -1,4 +1,5 @@ -pub fn add(left: usize, right: usize) -> usize { +#[must_use] +pub const fn add(left: usize, right: usize) -> usize { left + right } diff --git a/features/wrapp-strategies/src/lib.rs b/features/wrapp-strategies/src/lib.rs index 7d12d9a..21739bf 100644 --- a/features/wrapp-strategies/src/lib.rs +++ b/features/wrapp-strategies/src/lib.rs @@ -1,4 +1,5 @@ -pub fn add(left: usize, right: usize) -> usize { +#[must_use] +pub const fn add(left: usize, right: usize) -> usize { left + right } diff --git a/strategies/wrapp-actix/src/lib.rs b/strategies/wrapp-actix/src/lib.rs index 7d12d9a..21739bf 100644 --- a/strategies/wrapp-actix/src/lib.rs +++ b/strategies/wrapp-actix/src/lib.rs @@ -1,4 +1,5 @@ -pub fn add(left: usize, right: usize) -> usize { +#[must_use] +pub const fn add(left: usize, right: usize) -> usize { left + right } diff --git a/wrapp/src/lib.rs b/wrapp/src/lib.rs index 7d12d9a..21739bf 100644 --- a/wrapp/src/lib.rs +++ b/wrapp/src/lib.rs @@ -1,4 +1,5 @@ -pub fn add(left: usize, right: usize) -> usize { +#[must_use] +pub const fn add(left: usize, right: usize) -> usize { left + right } From 2dc9acd12ea008f0eaf2db457bc0706eef692afe Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 13:06:01 +0200 Subject: [PATCH 03/12] more linting --- .vscode/settings.json | 3 ++- .vscode/tasks.json | 27 +++++++++++++++++++ features/wrapp-config/src/provider.rs | 3 +++ features/wrapp-di/src/builder.rs | 24 +++++++++++++++++ features/wrapp-di/src/container.rs | 5 +++- features/wrapp-di/src/dependency_graph.rs | 33 ++++++++++++++--------- features/wrapp-di/src/errors.rs | 3 --- features/wrapp-di/src/factories.rs | 4 +-- features/wrapp-di/src/initiator.rs | 9 +++++-- features/wrapp-di/src/resolver/lazy.rs | 5 +++- 10 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/settings.json b/.vscode/settings.json index f4903e8..07040ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ - "oneshot" + "oneshot", + "wrapp" ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1e12bce --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "clippy-watch", + "type": "shell", + "command": "cargo watch -x clippy", + "isBackground": true, + "problemMatcher": { + "base": "$rustc", + "background": { + "activeOnStart": true, + "beginsPattern": "^\\[Running 'cargo clippy'\\]", + "endsPattern": "^\\[Finished running\\. Exit status: [0-9]+\\]" + } + }, + "presentation": { + "reveal": "never", + "panel": "dedicated", + "clear": true + }, + "runOptions": { + "runOn": "folderOpen" + } + } + ] +} \ No newline at end of file diff --git a/features/wrapp-config/src/provider.rs b/features/wrapp-config/src/provider.rs index 06ef071..68556ba 100644 --- a/features/wrapp-config/src/provider.rs +++ b/features/wrapp-config/src/provider.rs @@ -43,6 +43,9 @@ impl ConfigProvider { } /// Add a config to the registry. + /// + /// # Errors + /// - Fails if a config of the same type has already been registered. pub fn add_config( &mut self, config: T, diff --git a/features/wrapp-di/src/builder.rs b/features/wrapp-di/src/builder.rs index a549ee1..543aa4c 100644 --- a/features/wrapp-di/src/builder.rs +++ b/features/wrapp-di/src/builder.rs @@ -32,21 +32,45 @@ impl DiBuilder { } } impl DiBuilder { + /// Adds a instance of a type to the DI container. + /// + /// An instance is a concrete object that has already been created and is ready to be used. + /// These instances are stored in the DI container and can be retrieved by their type during container initialization. + /// + #[must_use] pub fn add_instance(mut self, instance: T) -> Self { self.registered_instances .insert(TypeInfo::of::(), Instance::new(instance)); self } + /// Adds a factory to the DI container. + /// + /// [Factories](InstanceFactory) are responsible for creating instances of a specific type during initialization of the DI container. + /// They can have dependencies on other types, which will be resolved and injected by the DI container during [building](DiBuilder::build). + #[must_use] pub fn add_factory(mut self, factory: Factory) -> Self { self.registered_factories.push(Box::new(factory)); self } + /// Builds the DI container, resolving all dependencies and creating instances as needed. + /// + /// ### Errors + /// - Fails if any factory fails to create an instance. + /// - Fails if there are conflicts in the dependency graph, e.g. circular dependencies or missing required types. + /// - Fails if two factories produce the same type, which would create ambiguity in the dependency graph. pub async fn build(self) -> Result { DiInitiator::new().initiate(self, None).await } + /// Builds the DI container, resolving all dependencies and creating instances as needed. + /// + /// ### Errors + /// - Fails if any factory fails to create an instance. + /// - Fails if there are conflicts in the dependency graph, e.g. circular dependencies or missing required types. + /// - Fails if two factories produce the same type, which would create ambiguity in the dependency graph. + /// - Fails if the initialization takes longer than the specified timeout duration. pub async fn build_timeout(self, timeout: Duration) -> Result { DiInitiator::new().initiate(self, Some(timeout)).await } diff --git a/features/wrapp-di/src/container.rs b/features/wrapp-di/src/container.rs index baac40c..6bfbc2f 100644 --- a/features/wrapp-di/src/container.rs +++ b/features/wrapp-di/src/container.rs @@ -41,7 +41,10 @@ impl DiContainer { Self(Arc::new(DiContainerInner { instances, graph })) } - /// Attempts to get the requested type + /// Attempts to get an instance of type `T` from the DI container. + /// + /// # Errors + /// See [`RequireError`] for possible errors during resolution. pub fn require(&self) -> Result, RequireError> { match self.0.instances.get(&TypeId::of::()) { Some((_, Some(instance))) => { diff --git a/features/wrapp-di/src/dependency_graph.rs b/features/wrapp-di/src/dependency_graph.rs index bfcb1ca..faeba70 100644 --- a/features/wrapp-di/src/dependency_graph.rs +++ b/features/wrapp-di/src/dependency_graph.rs @@ -1,6 +1,5 @@ use std::{ - any::TypeId, - collections::{BTreeMap, HashSet}, + any::TypeId, collections::{BTreeMap, HashSet}, }; use thiserror::Error; @@ -16,9 +15,13 @@ pub struct DependencyGraph { map: BTreeMap, } impl DependencyGraph { + /// Creates a new `DependencyGraph` from the given `DiBuilder` + /// + /// # Errors + /// See [`DependencyGraphError`] for possible errors during graph creation. pub fn new(builder: &DiBuilder) -> Result { let mut graph = Self { - map: Default::default(), + map: BTreeMap::default(), }; for instance in builder.registered_instances.values() { @@ -32,6 +35,10 @@ impl DependencyGraph { Ok(graph) } + /// Adds a new entry to the graph + /// + /// # Errors + /// See [`DependencyGraphError`] for possible errors during graph creation. pub fn add( &mut self, info: TypeInfo, @@ -47,9 +54,10 @@ impl DependencyGraph { Ok(()) } - /// Validate the graph - /// - /// Returns a list of all issues + /// Validates the dependency graph and returns a list of errors if any are found. + /// + /// # Errors + /// See [`DependencyGraphErrors`] for possible errors during graph validation. pub fn check(&self) -> Result<(), DependencyGraphErrors> { let mut checked = HashSet::new(); let mut errors = Vec::new(); @@ -144,6 +152,12 @@ pub enum DependencyGraphError { chain: Vec, }, } + +/// One or more errors in the dependency graph +#[derive(Error, Debug, Clone)] +pub struct DependencyGraphErrors { + pub errors: Vec, +} impl std::fmt::Display for DependencyGraphErrors { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut display = Vec::new(); @@ -153,9 +167,4 @@ impl std::fmt::Display for DependencyGraphErrors { } f.write_str(&display.join("\n")) } -} - -#[derive(Error, Debug, Clone)] -pub struct DependencyGraphErrors { - pub errors: Vec, -} +} \ No newline at end of file diff --git a/features/wrapp-di/src/errors.rs b/features/wrapp-di/src/errors.rs index f2eecc1..70013c7 100644 --- a/features/wrapp-di/src/errors.rs +++ b/features/wrapp-di/src/errors.rs @@ -13,9 +13,6 @@ pub enum InjectError { /// Injection Handle channel was closed #[error("Handle was closed, did you try using it after Initialization?")] HandleClosed, - /// Generic error during Injection - #[error("Error during injection: {0}")] - Other(DynError), } impl From for InjectError { fn from(_: mpsc::SendError) -> Self { diff --git a/features/wrapp-di/src/factories.rs b/features/wrapp-di/src/factories.rs index 0c649f5..ca660f1 100644 --- a/features/wrapp-di/src/factories.rs +++ b/features/wrapp-di/src/factories.rs @@ -6,7 +6,7 @@ use crate::{ }; /// A Factory providing instances of a given type -pub trait InstanceFactory: Send + Sync { +pub trait InstanceFactory: Send { type Provides: Injectable; /// Returns the typeinfo about the factory's provided type @@ -37,7 +37,7 @@ pub trait InstanceFactory: Send + Sync { } /// Wrapper Trait for factories, providing instances of Any -pub trait DynFactory { +pub trait DynFactory: Send { fn supplies(&self) -> TypeInfo; /// Returns a list of dependencies for the factory diff --git a/features/wrapp-di/src/initiator.rs b/features/wrapp-di/src/initiator.rs index 1548314..f8bac2b 100644 --- a/features/wrapp-di/src/initiator.rs +++ b/features/wrapp-di/src/initiator.rs @@ -190,8 +190,8 @@ impl DiInitiator { &mut self, result: Option<(TypeInfo, Result, DynError>)>, ) -> Result { - let (info, result) = if let Some(result) = result { result } else { - // If no more tasks are left, exit the loop + let Some((info, result)) = result else { + // A none result means all tasks are complete, we exit the loop // all injection requests must now also be handled as nothing is left to be build debug_assert!( self.instance_waiters.is_empty(), @@ -199,6 +199,7 @@ impl DiInitiator { ); return Ok(true); }; + match result { Ok(instance) => { @@ -307,6 +308,10 @@ pub struct DiHandle { pub request_sender: mpsc::Sender, } impl DiHandle { + /// Resolves a dependency of type `T` using the DI Handle. + /// + /// # Errors + /// See [`InjectError`] for possible errors during resolution. pub async fn resolve(&mut self) -> Result { T::resolve(self).await } diff --git a/features/wrapp-di/src/resolver/lazy.rs b/features/wrapp-di/src/resolver/lazy.rs index 69a0f32..d89f7d0 100644 --- a/features/wrapp-di/src/resolver/lazy.rs +++ b/features/wrapp-di/src/resolver/lazy.rs @@ -94,6 +94,9 @@ impl Lazy { } /// Try to access the lazy dependency + /// + /// ### Panics + /// - When accessed before the DI Container Init has completed pub fn try_get(&self) -> Option, &InjectError>> { if let Some(result) = self.0.once.get() { return Some(result.as_ref()); @@ -170,7 +173,7 @@ impl<'a, T: Injectable> Future for LazyFuture<'a, T> { return Poll::Ready(result); } - // Poll recevier + // Poll receiver for result match rx.poll_unpin(cx) { Poll::Ready(recv) => { // We have a result, handle it and set once lock From f28c13fd62f49438cdd00527a7a3a5047bf4ff9d Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 13:49:41 +0200 Subject: [PATCH 04/12] tooling: ci --- .github/workflows/ci.yml | 41 ++++++++++++ .gitignore | 5 +- .rustfmt.toml | 21 +++++++ .vscode/settings.json | 5 +- deny.toml | 57 +++++++++++++++++ rust-toolchain.toml | 4 ++ scripts/ci-local.sh | 131 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .rustfmt.toml create mode 100644 deny.toml create mode 100644 rust-toolchain.toml create mode 100755 scripts/ci-local.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..622d835 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + push: + branches: + - main + - dev + pull_request: + +# Only keep the latest run per branch/PR to save CI minutes. +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + rust-fmt: + rust-all: + name: Rust (fmt, clippy, test, doc, shear) + runs-on: ubuntu-latest + env: + # Cache compilation output between runs via sccache. + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + CARGO_INCREMENTAL: "0" + steps: + - uses: actions/checkout@v4 + + # Starts the sccache server used by RUSTC_WRAPPER above. + - name: Start sccache + uses: mozilla-actions/sccache-action@v0.0.9 + + # Runs every default workflow in a single step + - name: Run Rust workflows + uses: aderinom/rust-all-action@v1 + with: + toolchain: stable + # 'all-default' = test, clippy, fmt, doc, shear. + run: fmt, clippy, test, doc, shear, deny diff --git a/.gitignore b/.gitignore index f2f9e58..83d1b8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ target -Cargo.lock \ No newline at end of file +Cargo.lock + +# Vendored rust-all-action bundle for local CI (scripts/ci-local.sh) +/.tools/ \ No newline at end of file diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..ae91028 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,21 @@ +# Requires nightly rustfmt to be installed. + +# Comments Formatting +comment_width = 100 +wrap_comments = true +format_code_in_doc_comments = true +normalize_comments = true # replace `/*comment*/ with `// comment` where possible + +# Imports Formatting +imports_granularity = "Crate" # Groups imports by their source crate +group_imports = "StdExternalCrate" # Orders imports by STD, External, Internal + +# Enum Formatting +enum_discrim_align_threshold = 4 # Vertical Align enum assignments by up to N characters + +# Block and Closure Formatting +force_multiline_blocks = true # Force multiline closures to be wrapped in a block + +# Patterns Formatting +condense_wildcard_suffixes = true # Replace (a, _, _) = (1,2,3) with (a, ..) = (1,2,3) +use_field_init_shorthand = true # A{ x: x } becomes A { x } diff --git a/.vscode/settings.json b/.vscode/settings.json index 07040ce..b9c087f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "cSpell.words": [ "oneshot", "wrapp" - ] + ], + "rust-analyzer.rustfmt.extraArgs": [ + "+nightly" + ], } \ No newline at end of file diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..6ce13f4 --- /dev/null +++ b/deny.toml @@ -0,0 +1,57 @@ +# cargo-deny configuration — https://embarkstudios.github.io/cargo-deny/ + +# Licensing policy for this MIT-licensed project: only permissive, +# MIT-compatible licenses are allowed. Copyleft licenses are rejected by +# omission — anything not in `allow` (or granted a scoped exception) fails. + +[graph] +all-features = true + +[output] +feature-depth = 1 + +[licenses] +version = 2 + +# Permissive, MIT-compatible licenses. Anything NOT listed here (or granted an +# exception below) fails the check. +allow = [ + "MIT", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Zlib", + "Unlicense", + "0BSD", + "BSL-1.0", + "CC0-1.0", +] + +# Licenses intentionally NOT allowed +# Strong copyleft : GPL-2.0, GPL-3.0, AGPL-3.0 +# Weak copyleft : LGPL-2.1, LGPL-3.0, MPL-2.0, EPL-1.0/2.0, CDDL-1.0 + +confidence-threshold = 0.8 + +# Per-crate license exceptions. Scoped so they apply only to the named crate. +exceptions = [{ crate = "unicode-ident", allow = ["Unicode-DFS-2016"] }] + +# Security advisories (RustSec) +[advisories] +version = 2 +ignore = [] + +# Dependency bans / hygiene +[bans] +# Reject crates with multiple versions in the dependency graph +multiple-versions = "warn" +# Reject `version = "*"` requirements +wildcards = "deny" +allow-wildcard-paths = true + +# Crate sources +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..94794ba --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.96.0" +profile = "default" +components = ["rustc", "rust-analyzer", "rustfmt", "clippy"] diff --git a/scripts/ci-local.sh b/scripts/ci-local.sh new file mode 100755 index 0000000..f6ce76f --- /dev/null +++ b/scripts/ci-local.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env sh +# Run the same checks as .github/workflows/ci.yml locally, using the *real* +# aderinom/rust-all-action bundle (no source changes to the action itself). +# +# The action is vendored into a git-ignored, repo-relative folder (.tools/), +# so this never depends on a path outside the repository. +# +# Usage: +# scripts/ci-local.sh # fmt, clippy, shear, deny, test, doc +# RUN=clippy scripts/ci-local.sh # run a single workflow +# RUN=fmt,clippy scripts/ci-local.sh # run a subset (no spaces!) +# ACTION_REF=main scripts/ci-local.sh # pin a different action ref +# VERBOSE=1 scripts/ci-local.sh # also show ::debug:: lines +# NO_COLOR=1 scripts/ci-local.sh # disable colored output +set -eu + +# --- pretty output -------------------------------------------------------- +# The action speaks GitHub's workflow-command dialect (::group::, ::warning::, +# ...). Locally we translate those into readable, colored terminal output. +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + COLOR=1 + C_RESET=$(printf "\033[0m"); C_BOLD=$(printf "\033[1m") + C_CYAN=$(printf "\033[36m"); C_RED=$(printf "\033[31m") + C_GRN=$(printf "\033[32m"); C_YEL=$(printf "\033[33m") +else + COLOR=0 + C_RESET=; C_BOLD=; C_CYAN=; C_RED=; C_GRN=; C_YEL= +fi +VERBOSE="${VERBOSE:-0}" + +say() { printf "%s>>%s %s\n" "$C_BOLD$C_CYAN" "$C_RESET" "$*"; } +die() { printf "%s!!%s %s\n" "$C_BOLD$C_RED" "$C_RESET" "$*" >&2; exit 1; } + +# --- config --------------------------------------------------------------- +ACTION_REPO="https://github.com/aderinom/rust-all-action.git" +ACTION_REF="${ACTION_REF:-v1}" # matches `uses: aderinom/rust-all-action@v1` +RUN="${RUN:-all-default,deny}" # all-default = fmt,clippy,shear,test,doc +TOOLCHAIN="${TOOLCHAIN:-stable}" # rust-toolchain.toml channel still wins + +# --- paths ---------------------------------------------------------------- +REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd) +VENDOR_DIR="$REPO_ROOT/.tools/rust-all-action" +ENTRY="$VENDOR_DIR/dist/index.js" + +# --- fetch / update the vendored action bundle ---------------------------- +if [ ! -d "$VENDOR_DIR/.git" ]; then + say "Cloning rust-all-action@$ACTION_REF into .tools/ ..." + git clone --quiet --depth 1 --branch "$ACTION_REF" "$ACTION_REPO" "$VENDOR_DIR" +else + say "Updating rust-all-action to $ACTION_REF ..." + git -C "$VENDOR_DIR" fetch --quiet --depth 1 origin "$ACTION_REF" + git -C "$VENDOR_DIR" checkout --quiet --force FETCH_HEAD +fi + +[ -f "$ENTRY" ] || die "Bundled entry point not found: $ENTRY" + +# --- run ------------------------------------------------------------------ +# GitHub Actions passes inputs as INPUT_ env vars; the action reads them +# via @actions/core.getInput. cacheKey=no-cache disables the GitHub-only cache +# service calls, leaving tool install + workflow execution intact. +say "Running workflows: $RUN" +cd "$REPO_ROOT" + +# awk filter: translate GitHub workflow commands into readable terminal output +# and indent everything emitted inside a ::group::. +FILTER=' +function decode(s) { + gsub(/%0D/, "\r", s) + gsub(/%0A/, "\n", s) + gsub(/%3A/, ":", s) + gsub(/%2C/, ",", s) + gsub(/%25/, "%", s) + return s +} +BEGIN { + esc = sprintf("%c", 27) + if (color == "1") { + reset = esc "[0m"; bold = esc "[1m"; dim = esc "[2m" + red = esc "[31m"; grn = esc "[32m"; yel = esc "[33m"; cyn = esc "[36m" + } +} +{ + if (substr($0, 1, 2) == "::") { + rest = substr($0, 3) + idx = index(rest, "::") + if (idx > 0) { + header = substr(rest, 1, idx - 1) + msg = decode(substr(rest, idx + 2)) + split(header, parts, " ") + cmd = parts[1] + if (cmd == "group") { printf "\n%s%s▶ %s%s\n", bold, cyn, msg, reset; ingroup = 1; next } + if (cmd == "endgroup") { ingroup = 0; next } + if (cmd == "debug") { if (verbose == "1") printf "%s · %s%s\n", dim, msg, reset; next } + if (cmd == "warning") { if (index(msg, "SCCACHE_PATH is not set") == 0) printf "%s⚠ %s%s\n", yel, msg, reset; next } + if (cmd == "error") { printf "%s✖ %s%s\n", red, msg, reset; next } + if (cmd == "notice") { printf "%s● %s%s\n", grn, msg, reset; next } + next + } + } + if (ingroup == 1) print " " $0; else print $0 +} +' + +# Run the action, but keep ITS exit code (not awk's) so failures still fail. +# POSIX sh has no pipefail, so stash the status in a temp file. +status_file=$(mktemp) +trap 'rm -f "$status_file"' EXIT INT TERM + +set +e +{ + INPUT_PROJECT="." \ + INPUT_RUN="$RUN" \ + INPUT_TOOLCHAIN="$TOOLCHAIN" \ + INPUT_CACHEKEY="no-cache" \ + node "$ENTRY" 2>&1 + echo $? > "$status_file" +} | awk -v color="$COLOR" -v verbose="$VERBOSE" "$FILTER" +set -e + +code=$(cat "$status_file" 2>/dev/null || echo 1) +case "$code" in (""|*[!0-9]*) code=1 ;; esac + +echo +if [ "$code" -eq 0 ]; then + printf "%s✔ All workflows passed%s (%s)\n" "$C_BOLD$C_GRN" "$C_RESET" "$RUN" +elif [ "$code" -eq 130 ]; then + printf "%s■ Interrupted%s\n" "$C_BOLD$C_YEL" "$C_RESET" +else + printf "%s✖ Workflow(s) failed%s (exit %s)\n" "$C_BOLD$C_RED" "$C_RESET" "$code" +fi +exit "$code" From 7fcafcb080ccf8a816614a40eeda53b3644dd97b Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 17:32:35 +0200 Subject: [PATCH 05/12] cleanup, ci, deny, and more --- .github/workflows/ci.yml | 1 - Cargo.toml | 4 +- LICENSE-APACHE | 201 ++++++++++++++++++ LICENSE-MIT | 19 ++ check.sh | 171 +++++++++++++++ deny.toml | 15 +- docs/idea.md | 104 +++++++++ todo => docs/todo | 6 +- examples/prototype/Cargo.toml | 1 + features/wrapp-config/Cargo.toml | 1 + .../examples/using-config-provider.rs | 2 +- features/wrapp-config/src/errors.rs | 1 - features/wrapp-config/src/lib.rs | 14 +- features/wrapp-config/src/provider.rs | 25 ++- features/wrapp-config/src/resolver.rs | 5 +- features/wrapp-di/Cargo.toml | 1 + features/wrapp-di/examples/manual-di-impl.rs | 9 +- features/wrapp-di/src/builder.rs | 33 +-- features/wrapp-di/src/container.rs | 14 +- features/wrapp-di/src/dependency_graph.rs | 15 +- features/wrapp-di/src/factories.rs | 5 +- features/wrapp-di/src/initiator.rs | 11 +- features/wrapp-di/src/lib.rs | 14 +- features/wrapp-di/src/resolver/arc.rs | 23 +- features/wrapp-di/src/resolver/lazy.rs | 65 +++--- features/wrapp-di/src/types.rs | 4 +- features/wrapp-router/Cargo.toml | 1 + features/wrapp-router/src/lib.rs | 2 +- features/wrapp-strategies/Cargo.toml | 1 + features/wrapp-strategies/src/lib.rs | 2 +- ideas.md | 6 - readme.md | 148 +++++++------ scripts/ci-local.sh | 131 ------------ strategies/wrapp-actix/Cargo.toml | 1 + strategies/wrapp-actix/src/lib.rs | 2 +- wrapp/Cargo.toml | 1 + wrapp/src/lib.rs | 2 +- 37 files changed, 722 insertions(+), 339 deletions(-) create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100755 check.sh create mode 100644 docs/idea.md rename todo => docs/todo (75%) delete mode 100644 ideas.md delete mode 100755 scripts/ci-local.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 622d835..b062589 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,5 @@ jobs: - name: Run Rust workflows uses: aderinom/rust-all-action@v1 with: - toolchain: stable # 'all-default' = test, clippy, fmt, doc, shear. run: fmt, clippy, test, doc, shear, deny diff --git a/Cargo.toml b/Cargo.toml index 14d3679..64792f7 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,13 +3,14 @@ resolver = "2" members = ["examples/*", "features/*", "strategies/*", "wrapp"] exclude = ["examples/prototype"] +package.license = "MIT OR Apache-2.0" package.edition = "2024" package.version = "0.0.0" [workspace.dependencies] # Project Modules -wrapp-di = { path = "./features/wrapp-di" } +wrapp-di = { version = "0.0.0", path = "./features/wrapp-di" } # External Crates futures = "0.3" @@ -22,6 +23,7 @@ thiserror = "2.0" [workspace.lints.clippy] pedantic = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } +unwrap_used = { level = "warn" } option_if_let_else = { level = "allow" } # More readable items_after_statements = { level = "allow" } # More readable diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..7b8a625 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2026 Aaron Kelbsch + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..562b605 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2026 Aaron Kelbsch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..8ab5f3c --- /dev/null +++ b/check.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# Executes multiple cargo commands in a workspace + +push_script_dir() { + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + pushd "$SCRIPT_DIR" > /dev/null || exit 1 +} +push_script_dir + + +# print help if -h or --help or help is passed at any position +for arg in "$@"; do + if [[ "$arg" == "-h" || "$arg" == "--help" || "$arg" == "help" ]]; then + echo -e "\e[36mBatch Commands:\e[0m + - \e[33mcheck\e[0m (runs fmt, lint, doc, shear, tests in check mode) + - \e[33mfix\e[0m (runs fmt, lint, shear in fix mode) + " + echo -e "\e[36mAliases:\e[0m + - \e[33mfmt\e[0m (for +nightly fmt) + - \e[33mdoc\e[0m (for doc with CI settings) + - \e[33mlint\e[0m (for clippy with CI settings) + - \e[33mtest\e[0m (for a workspace-wide test run) + " + exit 0 + fi +done + +# if no arguments are passed, fallback to check +if [ $# -eq 0 ]; then + set -- "check" +fi + +######################## +# Batch Commands: +declare -a RESULTS +FAILED=0 + +# resolve aliases (fmt/doc/lint/test) into the full cargo arg list in the REPLY array; +cargo_alias() { + case "$1" in + # fmt should always use +nightly + fmt) REPLY=(+nightly fmt "${@:2}") ;; + # doc gets the full documentation argument set + doc) REPLY=(doc --workspace --all-features --no-deps --document-private-items "${@:2}") ;; + # lint is clippy with the workspace-wide settings + lint) REPLY=(clippy --all-targets "${@:2}") ;; + # test is a workspace-wide test run + test) REPLY=(test --all "${@:2}") ;; + *) REPLY=("$@") ;; + esac +} + +# run_cargo resolves an alias, echoes the "▶ cargo …" line, then runs it live. +run_cargo() { + cargo_alias "$@" + printf '\e[36m▶ cargo %s\e[0m\n' "${REPLY[*]}" + cargo "${REPLY[@]}" +} + +# run_step runs one step live (echo + output) and records pass/fail for the +# summary. Used by the sequential `fix` path. +run_step() { + local name="$1"; shift + local start=$SECONDS + if run_cargo "$@"; then + printf '\e[32m✓ %s\e[0m (%ds)\n\n' "$name" "$((SECONDS - start))" + RESULTS+=(" \e[32m✓\e[0m $name") + else + printf '\e[31m✗ %s\e[0m (%ds)\n\n' "$name" "$((SECONDS - start))" + RESULTS+=(" \e[31m✗\e[0m $name") + FAILED=1 + fi +} + +# launch_step echoes the resolved "▶ cargo …" line live, then starts the step in +# the background, buffering the command's output to a per-step log and recording +# its own wall-clock duration. +launch_step() { + local name="$1"; shift + local idx=${#PIDS[@]} + cargo_alias "$@" + printf '\e[36m▶ cargo %s\e[0m\n' "${REPLY[*]}" + NAMES[idx]="$name" + LOGS[idx]="$STEP_TMPDIR/step_$idx.log" + DURS[idx]="$STEP_TMPDIR/step_$idx.dur" + local cmd=("${REPLY[@]}") + ( + s=$(date +%s) + cargo "${cmd[@]}" > "${LOGS[idx]}" 2>&1 + rc=$? + printf '%s' "$(( $(date +%s) - s ))" > "${DURS[idx]}" + exit "$rc" + ) & + PIDS[idx]=$! +} + +# wait_steps waits for every launched step (in launch order) and reports each +# result: quiet on success, full buffered log on failure. +wait_steps() { + local i status dur + for i in "${!PIDS[@]}"; do + if wait "${PIDS[i]}"; then status=0; else status=1; fi + dur="$(cat "${DURS[i]}" 2>/dev/null)" + if [ "$status" -eq 0 ]; then + printf '\e[32m✓ %s\e[0m (%ss)\n' "${NAMES[i]}" "$dur" + RESULTS+=(" \e[32m✓\e[0m ${NAMES[i]}") + else + printf '\e[31m✗ %s\e[0m (%ss)\n' "${NAMES[i]}" "$dur" + cat "${LOGS[i]}" + RESULTS+=(" \e[31m✗\e[0m ${NAMES[i]}") + FAILED=1 + fi + done +} + +# All Checks +if [ "$1" = "check" ]; then + # Per-run scratch space for buffered step output; only the parallel `check` + # path needs it, so it's created here rather than at the top of the script. + STEP_TMPDIR="$(mktemp -d)" + trap 'rm -rf "$STEP_TMPDIR"' EXIT + declare -a PIDS=() NAMES=() LOGS=() DURS=() + + launch_step "format" fmt --all -- --check + launch_step "doc" doc --quiet --frozen + launch_step "shear" shear + launch_step "lint" lint --quiet + launch_step "test" test --quiet --no-fail-fast + launch_step "deny" deny check + echo -e "\e[36m▶ running ${#PIDS[@]} checks in parallel…\e[0m" + wait_steps + + echo "──────── Summary ────────" + printf '%b\n' "${RESULTS[@]}" + if [ "$FAILED" -eq 0 ]; then + echo -e "\e[32mAll checks passed ✅\e[0m" + else + echo -e "\e[31mSome checks failed ❌\e[0m" + fi + + exit "$FAILED" +fi + + +# All Fixes +if [ "$1" = "fix" ]; then + # Kept sequential on purpose: lint --fix and fmt both rewrite the same .rs + # files, so running them concurrently would race and corrupt the sources. + run_step "lint --fix" lint --fix --allow-dirty + run_step "format" fmt --all + run_step "shear --fix" shear --fix + + echo "──────── Changed files ────────" + git --no-pager diff --stat + + echo "──────── Summary ────────" + printf '%b\n' "${RESULTS[@]}" + [ "$FAILED" -eq 0 ] \ + && echo -e "\e[32mAll fixes applied ✅\e[0m" \ + || echo -e "\e[31mSome fixes failed ❌\e[0m" + + exit "$FAILED" +fi + +################## +# Direct invocation: expand any alias (see run_cargo) and run it. +run_cargo "$@" +if [ $? -ne 0 ]; then + echo -e "\e[31mCommand\e[0m $1 \e[31mfailed\e[0m" + exit 1 +fi diff --git a/deny.toml b/deny.toml index 6ce13f4..50430e2 100644 --- a/deny.toml +++ b/deny.toml @@ -1,8 +1,7 @@ # cargo-deny configuration — https://embarkstudios.github.io/cargo-deny/ # Licensing policy for this MIT-licensed project: only permissive, -# MIT-compatible licenses are allowed. Copyleft licenses are rejected by -# omission — anything not in `allow` (or granted a scoped exception) fails. +# MIT-compatible licenses are allowed. Copyleft licenses are rejected. [graph] all-features = true @@ -13,8 +12,7 @@ feature-depth = 1 [licenses] version = 2 -# Permissive, MIT-compatible licenses. Anything NOT listed here (or granted an -# exception below) fails the check. +# Permissive, MIT-compatible licenses. Anything NOT listed here fails the check. allow = [ "MIT", "Apache-2.0", @@ -25,8 +23,9 @@ allow = [ "Unlicense", "0BSD", "BSL-1.0", - "CC0-1.0", + "Unicode-3.0", ] +unused-allowed-license = "allow" # Licenses intentionally NOT allowed # Strong copyleft : GPL-2.0, GPL-3.0, AGPL-3.0 @@ -34,8 +33,8 @@ allow = [ confidence-threshold = 0.8 -# Per-crate license exceptions. Scoped so they apply only to the named crate. -exceptions = [{ crate = "unicode-ident", allow = ["Unicode-DFS-2016"] }] +# Per-crate license exceptions. +exceptions = [] # Security advisories (RustSec) [advisories] @@ -49,6 +48,8 @@ multiple-versions = "warn" # Reject `version = "*"` requirements wildcards = "deny" allow-wildcard-paths = true +allow-workspace = true + # Crate sources [sources] diff --git a/docs/idea.md b/docs/idea.md new file mode 100644 index 0000000..beb4847 --- /dev/null +++ b/docs/idea.md @@ -0,0 +1,104 @@ +# Why? + +Building systems which are more than one thing at once (e.g. A WebAPI + Kafka Consumer) often forces +developers to either build more bootstrapping glue or to accept their applications to look chaotic. + +E.g., a typical multi-functional application might require: + +1. Setting up a web server for API endpoints +2. Configuring Kafka consumers for message processing +3. Initializing database connections +4. Setting up background job schedulers +5. Implementing logging and monitoring systems + +Each of these components often requires its own initialization logic, configuration management, and integration with the rest of the application. This can lead to: + +- Complex and hard-to-maintain bootstrapping code +- Tight coupling between different parts of the application +- Difficulty in adding or removing features +- Challenges in testing individual components in isolation +- Inconsistent patterns across different parts of the system + +These issues can significantly increase the cognitive load on developers and make the codebase harder to evolve over time. +The wrapp ecosystem aims to address these challenges by providing a modular and extensible framework for building multi-functional applications. + +# How? + +1. Modular Architecture: + Wrapp encourages breaking down the application into smaller, self-contained modules. Each module encapsulates a specific Domain (e.g. User, Todos, Metrics). + Each domain on its own can then use one or multiple Strategies to define how it has to interact with the outside world. + E.g. User could be an Actix and a Kafka module, thus getting input from these sources. + + This approach offers several advantages: + - Improved maintainability: Each module can be developed, tested, and updated independently. + - Flexibility: Domains can easily adopt new strategies or remove existing ones without affecting other parts of the system. + - Scalability: As the application grows, new domains can be added as separate modules without disrupting existing functionality. + - Reusability: Well-defined modules can be reused across different projects or shared within a larger ecosystem. + + The separation of domains and strategies allows for a clean architecture where business logic (domains) is decoupled from the technical implementation details (strategies), promoting better code organization and easier long-term maintenance. + +# Goal of wrapp + +Create an ecosystem which allows applications to compose themselves of different logical modules. +All modules share a DI framework, and strategies which determine how the outside world can interact with them. + +The DI Framework will allow a Module to declare other Modules it depends on - being optional, required or lazily. +The DI Framework will orchestrate the construction of all Modules, injecting dependencies as needed. + +The strategy system will allow modules to bootstrap themselves to e.g. the Webserver started by the application, or the Kafka Cluster connected to the app. +With the strategy system a module can declare itself as a WebModule (or probably more specific as an ActixModule, even though a general WebModule would be really cool). +This WebModule could then declare it's own routes, handlers, middlewares etc. which if the Module is registerd will attach themselves to the Server. +Then the Module could also declare itself as a KafkaModule, or a PostgresNotifyModule or a GRPC module, or a GraphQLModule etc. + +This way reusability components like a MetricsModule, a TaskQueueModule, and many more could be created which then +can simply be registered allowing their functionality work. + +To simplify the creation of Strategies Wrapp should also provide some useful tools. E.g. a simple Router which can be used to quickly build a new strategy. +In general it is a goal to make Strategies and Modules: + + 1. As easy to implement as possible + 2. Backwards compatible for as long as possible + +```mermaid +graph LR; + +wrapp --> wrapp-di +wrapp --> wrapp-strategy +wrapp --> wrapp-router + +wrapp-strategy --> wrapp-kafka +wrapp-strategy --> wrapp-actix +wrapp-strategy --> wrapp-sql-notify + + + +wrapp-di --> Module1 +wrapp-actix --> ActixWebModule --> Module1 --> UserApp +wrapp-kafka --> KafkaModule --> Module1 --> UserApp +wrapp-tauri --> TauriModule --> Module1 --> UserApp + +``` + +wrapp - overarching framework +wrapp-di - DI framework +wrapp-strategy - Trait for implementing new Strategies - e.g. a WebServer would be a strategy, or Listening to Kafka is a strategy +wrapp-router - Optional component to use for strategies - could e.g. allow routing through proc macros, and extracting app state + +E.g. + +wrapp-actixs implements wrapp-strategy +wrapp-actixs thus has hooks to: collect all the routes and config it needs and start the actix server +wrapp-actixs exports a trait wrapp-actix-module, which if implemented by a module, has the functions needed to collect all routes and config +since actix has it's own router, it will not use wrapp-router, but the wrapp-axtic strategy will add the finished DI container to actix's app state, and will provide an etractor like WrappState, to access data from the Wrapp-DI + +or + +wrapp-kafka implements wrapp-strategy +wrapp-kafka has a hook to collect all routes for messages, and has an implementation to start clients to listen to kafka +it exports a trait wrapp-kafka-module, which if implemented... +since this does not have it's own router, it can opt in to use the wrapp router, automatically allowing it to use e.g. middlewares and injection on it's endpoints. + +Possible Issues: + +1. Version conflicts +If module has serverely different versions of e.g. wrapp-actixs, this could lead to annoyances diff --git a/todo b/docs/todo similarity index 75% rename from todo rename to docs/todo index 5813f9c..0d4645c 100644 --- a/todo +++ b/docs/todo @@ -3,9 +3,9 @@ [x] Implementation [] Proc Macro - [] Config - [] Implement Config Provider - [] Implement Injector + [x] Config + [x] Implement Config Provider + [x] Implement Injector [] Strategies [] Figure out how to pass strategies diff --git a/examples/prototype/Cargo.toml b/examples/prototype/Cargo.toml index 8a49534..f655fb2 100644 --- a/examples/prototype/Cargo.toml +++ b/examples/prototype/Cargo.toml @@ -2,6 +2,7 @@ name = "prototype" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/features/wrapp-config/Cargo.toml b/features/wrapp-config/Cargo.toml index 4e7f770..8e0a169 100644 --- a/features/wrapp-config/Cargo.toml +++ b/features/wrapp-config/Cargo.toml @@ -2,6 +2,7 @@ name = "wrapp-config" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/features/wrapp-config/examples/using-config-provider.rs b/features/wrapp-config/examples/using-config-provider.rs index 6e0da83..5feab3e 100644 --- a/features/wrapp-config/examples/using-config-provider.rs +++ b/features/wrapp-config/examples/using-config-provider.rs @@ -23,7 +23,7 @@ fn main() { } }; - let retrieved_config = if let Some(c) = config_provider.config::() { c } else { + let Some(retrieved_config) = config_provider.config::() else { eprintln!("Could not find config type"); return; }; diff --git a/features/wrapp-config/src/errors.rs b/features/wrapp-config/src/errors.rs index a31086f..8279606 100644 --- a/features/wrapp-config/src/errors.rs +++ b/features/wrapp-config/src/errors.rs @@ -2,7 +2,6 @@ use wrapp_di::types::TypeInfo; - /// Errors when trying to register a config #[derive(thiserror::Error, Debug, Clone)] pub enum RegisterConfigError { diff --git a/features/wrapp-config/src/lib.rs b/features/wrapp-config/src/lib.rs index 80a03fb..fae05c8 100755 --- a/features/wrapp-config/src/lib.rs +++ b/features/wrapp-config/src/lib.rs @@ -1,16 +1,18 @@ //! Wrapp Config provides a simple config injection mechanism for Wrapp DI. //! //! ### Overview -//! -//! - [`ConfigProvider`](crate::provider::ConfigProvider) - Registry of configs which can be injected into modules. -//! - [`Config`](crate::resolver::Config) - [`Resolver`](wrapp_di::resolver::Resolver) type which allows for config injections in factories. -//! -//! +//! +//! - [`ConfigProvider`](crate::provider::ConfigProvider) - Registry of configs which can be +//! injected into modules. +//! - [`Config`](crate::resolver::Config) - [`Resolver`](wrapp_di::resolver::Resolver) +//! type which allows for config injections in factories. +//! +//! //! # Examples //! ```rust #![doc = include_str!("../examples/using-config-provider.rs")] //! ``` -pub mod resolver; pub mod errors; pub mod provider; +pub mod resolver; diff --git a/features/wrapp-config/src/provider.rs b/features/wrapp-config/src/provider.rs index 68556ba..89e9e7f 100644 --- a/features/wrapp-config/src/provider.rs +++ b/features/wrapp-config/src/provider.rs @@ -8,7 +8,7 @@ use std::{ use wrapp_di::types::TypeInfo; -use crate::errors::{RegisterConfigError}; +use crate::errors::RegisterConfigError; /// A provider to register all configs. /// @@ -20,7 +20,7 @@ pub struct ConfigProvider { impl ConfigProvider { /// Initializes an empty Config Provider - #[must_use] + #[must_use] pub fn new() -> Self { Self { configs: HashMap::new(), @@ -31,19 +31,26 @@ impl ConfigProvider { pub fn config(&self) -> Option> { let type_id = TypeId::of::(); - let config = self.configs - .get(&type_id)?; + let config = self.configs.get(&type_id)?; - if let Ok(config) = config.clone().downcast::() { Some(config) } else { - debug_assert!(false, "Config Provider contained invalid type in slot for type: {:?}", TypeInfo::of::()); - tracing::error!("Config Provider contained invalid type in slot for type: {:?}", TypeInfo::of::()); + if let Ok(config) = config.clone().downcast::() { + Some(config) + } else { + debug_assert!( + false, + "Config Provider contained invalid type in slot for type: {:?}", + TypeInfo::of::() + ); + tracing::error!( + "Config Provider contained invalid type in slot for type: {:?}", + TypeInfo::of::() + ); None } - } /// Add a config to the registry. - /// + /// /// # Errors /// - Fails if a config of the same type has already been registered. pub fn add_config( diff --git a/features/wrapp-config/src/resolver.rs b/features/wrapp-config/src/resolver.rs index 845df2f..90f5af7 100644 --- a/features/wrapp-config/src/resolver.rs +++ b/features/wrapp-config/src/resolver.rs @@ -45,7 +45,6 @@ use crate::provider::ConfigProvider; /// config.enabled /// } /// } -/// /// ``` pub struct Config { inner: Arc, @@ -58,12 +57,12 @@ impl Deref for Config { } } impl Config { - #[must_use] + #[must_use] pub fn inner(&self) -> Arc { self.inner.clone() } - #[must_use] + #[must_use] pub fn into_inner(self) -> Arc { self.inner } diff --git a/features/wrapp-di/Cargo.toml b/features/wrapp-di/Cargo.toml index ec3faf9..9c49b93 100755 --- a/features/wrapp-di/Cargo.toml +++ b/features/wrapp-di/Cargo.toml @@ -2,6 +2,7 @@ name = "wrapp-di" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/features/wrapp-di/examples/manual-di-impl.rs b/features/wrapp-di/examples/manual-di-impl.rs index b591e02..fd282e1 100644 --- a/features/wrapp-di/examples/manual-di-impl.rs +++ b/features/wrapp-di/examples/manual-di-impl.rs @@ -1,5 +1,5 @@ -//! An example of how a manual DI implementation can be done using the Wrapp DI framework. -//! +//! An example of how a manual DI implementation can be done using the Wrapp DI framework. +//! //! Usually you should use the wrapp-di procmacros to generate the factory for you, //! but this example shows how to do it manually to help understanding what happens under the hood. #![allow(dead_code)] @@ -11,8 +11,8 @@ use wrapp_di::{ factories::InstanceFactory, initiator::DiHandle, resolver::{ - lazy::{Lazy, LazyOption}, Resolver, + lazy::{Lazy, LazyOption}, }, types::DependencyInfo, }; @@ -59,7 +59,8 @@ impl InstanceFactory for TestFactory { ] } - /// Constructs a new instance of the Test struct with dependencies injected from the DI container. + /// Constructs a new instance of the Test struct with dependencies injected from the DI + /// container. #[allow(refining_impl_trait)] async fn construct( &mut self, diff --git a/features/wrapp-di/src/builder.rs b/features/wrapp-di/src/builder.rs index 543aa4c..a477b9b 100644 --- a/features/wrapp-di/src/builder.rs +++ b/features/wrapp-di/src/builder.rs @@ -8,8 +8,6 @@ use crate::{ types::{Injectable, Instance, TypeInfo}, }; - - pub struct DiBuilder { /// Registered factories which can provide instances pub(crate) registered_factories: Vec>, @@ -23,7 +21,7 @@ impl Default for DiBuilder { } impl DiBuilder { - #[must_use] + #[must_use] pub fn new() -> Self { Self { registered_factories: Vec::new(), @@ -33,10 +31,10 @@ impl DiBuilder { } impl DiBuilder { /// Adds a instance of a type to the DI container. - /// - /// An instance is a concrete object that has already been created and is ready to be used. - /// These instances are stored in the DI container and can be retrieved by their type during container initialization. /// + /// An instance is a concrete object that has already been created and is ready to be used. + /// These instances are stored in the DI container and can be retrieved by their type during + /// container initialization. #[must_use] pub fn add_instance(mut self, instance: T) -> Self { self.registered_instances @@ -45,9 +43,10 @@ impl DiBuilder { } /// Adds a factory to the DI container. - /// - /// [Factories](InstanceFactory) are responsible for creating instances of a specific type during initialization of the DI container. - /// They can have dependencies on other types, which will be resolved and injected by the DI container during [building](DiBuilder::build). + /// + /// [Factories](InstanceFactory) are responsible for creating instances of a specific type + /// during initialization of the DI container. They can have dependencies on other types, + /// which will be resolved and injected by the DI container during [building](DiBuilder::build). #[must_use] pub fn add_factory(mut self, factory: Factory) -> Self { self.registered_factories.push(Box::new(factory)); @@ -55,21 +54,25 @@ impl DiBuilder { } /// Builds the DI container, resolving all dependencies and creating instances as needed. - /// + /// /// ### Errors /// - Fails if any factory fails to create an instance. - /// - Fails if there are conflicts in the dependency graph, e.g. circular dependencies or missing required types. - /// - Fails if two factories produce the same type, which would create ambiguity in the dependency graph. + /// - Fails if there are conflicts in the dependency graph, e.g. circular dependencies or + /// missing required types. + /// - Fails if two factories produce the same type, which would create ambiguity in the + /// dependency graph. pub async fn build(self) -> Result { DiInitiator::new().initiate(self, None).await } /// Builds the DI container, resolving all dependencies and creating instances as needed. - /// + /// /// ### Errors /// - Fails if any factory fails to create an instance. - /// - Fails if there are conflicts in the dependency graph, e.g. circular dependencies or missing required types. - /// - Fails if two factories produce the same type, which would create ambiguity in the dependency graph. + /// - Fails if there are conflicts in the dependency graph, e.g. circular dependencies or + /// missing required types. + /// - Fails if two factories produce the same type, which would create ambiguity in the + /// dependency graph. /// - Fails if the initialization takes longer than the specified timeout duration. pub async fn build_timeout(self, timeout: Duration) -> Result { DiInitiator::new().initiate(self, Some(timeout)).await diff --git a/features/wrapp-di/src/container.rs b/features/wrapp-di/src/container.rs index 6bfbc2f..9b757cd 100644 --- a/features/wrapp-di/src/container.rs +++ b/features/wrapp-di/src/container.rs @@ -1,5 +1,5 @@ use std::{ - any::{type_name, TypeId}, + any::{TypeId, type_name}, collections::HashMap, fmt::Debug, sync::Arc, @@ -42,25 +42,25 @@ impl DiContainer { } /// Attempts to get an instance of type `T` from the DI container. - /// + /// /// # Errors /// See [`RequireError`] for possible errors during resolution. pub fn require(&self) -> Result, RequireError> { match self.0.instances.get(&TypeId::of::()) { Some((_, Some(instance))) => { - instance - .downcast() - .map_err(|actual_type| RequireError::DowncastFailed { + instance.downcast().map_err(|actual_type| { + RequireError::DowncastFailed { required_type: type_name::(), actual_type, - }) + } + }) } Some((_, None)) => Err(RequireError::TypeDisabled(type_name::())), None => Err(RequireError::TypeMissing(type_name::())), } } - #[must_use] + #[must_use] pub fn graph(&self) -> &DependencyGraph { &self.0.graph } diff --git a/features/wrapp-di/src/dependency_graph.rs b/features/wrapp-di/src/dependency_graph.rs index faeba70..d410cc5 100644 --- a/features/wrapp-di/src/dependency_graph.rs +++ b/features/wrapp-di/src/dependency_graph.rs @@ -1,5 +1,6 @@ use std::{ - any::TypeId, collections::{BTreeMap, HashSet}, + any::TypeId, + collections::{BTreeMap, HashSet}, }; use thiserror::Error; @@ -16,7 +17,7 @@ pub struct DependencyGraph { } impl DependencyGraph { /// Creates a new `DependencyGraph` from the given `DiBuilder` - /// + /// /// # Errors /// See [`DependencyGraphError`] for possible errors during graph creation. pub fn new(builder: &DiBuilder) -> Result { @@ -36,7 +37,7 @@ impl DependencyGraph { } /// Adds a new entry to the graph - /// + /// /// # Errors /// See [`DependencyGraphError`] for possible errors during graph creation. pub fn add( @@ -55,7 +56,7 @@ impl DependencyGraph { } /// Validates the dependency graph and returns a list of errors if any are found. - /// + /// /// # Errors /// See [`DependencyGraphErrors`] for possible errors during graph validation. pub fn check(&self) -> Result<(), DependencyGraphErrors> { @@ -145,7 +146,9 @@ pub enum DependencyGraphError { dependency: TypeInfo, required_by: TypeInfo, }, - #[error("A Circular Dependency exists between '{from}' and '{to}' through {chain:?} - Consider using `Lazy`")] + #[error( + "A Circular Dependency exists between '{from}' and '{to}' through {chain:?} - Consider using `Lazy`" + )] CircularDependency { from: TypeInfo, to: TypeInfo, @@ -167,4 +170,4 @@ impl std::fmt::Display for DependencyGraphErrors { } f.write_str(&display.join("\n")) } -} \ No newline at end of file +} diff --git a/features/wrapp-di/src/factories.rs b/features/wrapp-di/src/factories.rs index ca660f1..c485b16 100644 --- a/features/wrapp-di/src/factories.rs +++ b/features/wrapp-di/src/factories.rs @@ -10,7 +10,7 @@ pub trait InstanceFactory: Send { type Provides: Injectable; /// Returns the typeinfo about the factory's provided type - #[must_use] + #[must_use] fn supplies() -> TypeInfo { TypeInfo::of::() } @@ -20,7 +20,8 @@ pub trait InstanceFactory: Send { /// Constructs a new instance of the factory's provided type /// - /// Returns the constructed instance, or an error if either Dependencies are not satisfied or the Instantiation failed + /// Returns the constructed instance, or an error if either Dependencies are not satisfied or + /// the Instantiation failed fn construct( &mut self, di: DiHandle, diff --git a/features/wrapp-di/src/initiator.rs b/features/wrapp-di/src/initiator.rs index f8bac2b..03b8504 100644 --- a/features/wrapp-di/src/initiator.rs +++ b/features/wrapp-di/src/initiator.rs @@ -6,7 +6,7 @@ use std::{ time::Duration, }; -use futures::{stream::FuturesUnordered, StreamExt}; +use futures::{StreamExt, stream::FuturesUnordered}; use futures_channel::{mpsc, oneshot}; use crate::{ @@ -62,8 +62,10 @@ impl DiInitiator { } // Build and check Graph - let graph = DependencyGraph::new(&blueprint).map_err(|error| DependencyGraphErrors { - errors: vec![error], + let graph = DependencyGraph::new(&blueprint).map_err(|error| { + DependencyGraphErrors { + errors: vec![error], + } })?; graph.check()?; @@ -199,7 +201,6 @@ impl DiInitiator { ); return Ok(true); }; - match result { Ok(instance) => { @@ -309,7 +310,7 @@ pub struct DiHandle { } impl DiHandle { /// Resolves a dependency of type `T` using the DI Handle. - /// + /// /// # Errors /// See [`InjectError`] for possible errors during resolution. pub async fn resolve(&mut self) -> Result { diff --git a/features/wrapp-di/src/lib.rs b/features/wrapp-di/src/lib.rs index 80d2ff8..73c4043 100644 --- a/features/wrapp-di/src/lib.rs +++ b/features/wrapp-di/src/lib.rs @@ -1,10 +1,11 @@ -//! Wrapp DI -//! +//! Wrapp DI +//! //! Wrapp DI consists of following components: //! //! 1. `DiBuilder` - for registering all factories and instances. -//! 2. `DiInitiator` - which executes all factories, and handles injection requests during their initialization. -//! 2.1. Injection Handles - which are valid during the `AppInit` Phase, and allow a component to request its dependencies. +//! 2. `DiInitiator` - which executes all factories, and handles injection requests during their +//! initialization. 2.1. Injection Handles - which are valid during the `AppInit` Phase, and +//! allow a component to request its dependencies. //! 3. `DIContainer` - which is the final container that holds all created instances. //! //! General logic: @@ -17,14 +18,13 @@ //! //! `AppInitiator` creates tasks for all factories. //! Factories start asking for their dependencies. -//! Some Factory will not require a dependency (or just requires already existing instances), and as such will complete -//! After Completion, `AppInitiator` gets the result of the completed task. +//! Some Factory will not require a dependency (or just requires already existing instances), and as +//! such will complete After Completion, `AppInitiator` gets the result of the completed task. //! `AppInitiator` then informs all factories which were waiting for this dependency. //! Step by step all factories will complete //! After All factories have completed, `AppInitiator` creates the final `DIContainer`. //! `AppInitiator` publishes the `DIcontainer` through a channel to all dependencies. //! `DiContainer` wil also be given to the caller of `AppBuilder.build()` -//! pub mod builder; pub mod container; diff --git a/features/wrapp-di/src/resolver/arc.rs b/features/wrapp-di/src/resolver/arc.rs index ffc0832..7fa8c5d 100644 --- a/features/wrapp-di/src/resolver/arc.rs +++ b/features/wrapp-di/src/resolver/arc.rs @@ -22,12 +22,12 @@ impl Resolver for Arc { .await?; let resolved = rx.await??; - let downcasted = resolved - .downcast::() - .map_err(|e| RequireError::DowncastFailed { + let downcasted = resolved.downcast::().map_err(|e| { + RequireError::DowncastFailed { required_type: type_name::(), actual_type: e, - })?; + } + })?; Ok(downcasted) } @@ -48,12 +48,15 @@ impl Resolver for Option { { match Resolvable::resolve(handle).await { Ok(resolved) => Ok(Some(resolved)), - Err(e) => match e { - // If the required type is disabled, or not registered Option does not fail - InjectError::RequireError(RequireError::TypeDisabled(_) | -RequireError::TypeMissing(_)) => Ok(None), - _ => Err(e), - }, + Err(e) => { + match e { + // If the required type is disabled, or not registered Option does not fail + InjectError::RequireError( + RequireError::TypeDisabled(_) | RequireError::TypeMissing(_), + ) => Ok(None), + _ => Err(e), + } + } } } diff --git a/features/wrapp-di/src/resolver/lazy.rs b/features/wrapp-di/src/resolver/lazy.rs index d89f7d0..137bd73 100644 --- a/features/wrapp-di/src/resolver/lazy.rs +++ b/features/wrapp-di/src/resolver/lazy.rs @@ -28,10 +28,10 @@ use crate::{ /// /// Note: /// -/// This Type by itself has many panic conditions - However if used in the DI context, no panics should happen unless: +/// This Type by itself has many panic conditions - However if used in the DI context, no panics +/// should happen unless: /// - It is accessed during the Injection Phase /// - It is accessed after DI has already Failed -/// pub struct Lazy(Arc>); impl Debug for Lazy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -85,7 +85,7 @@ impl Lazy { /// /// # Panics /// - When accessed before the DI Container Init has completed - #[must_use] + #[must_use] pub fn get(&self) -> &Arc { self.try_get() .expect("Lazy inject accessed before initialized") @@ -94,14 +94,14 @@ impl Lazy { } /// Try to access the lazy dependency - /// + /// /// ### Panics /// - When accessed before the DI Container Init has completed pub fn try_get(&self) -> Option, &InjectError>> { if let Some(result) = self.0.once.get() { return Some(result.as_ref()); } - + // Lock receiver, so result is not taken out while we check let mut recv = self.0.rx.lock().expect("we don't handle poisoning"); @@ -120,22 +120,18 @@ impl Lazy { .map_err(|_| ()) .expect("holding lock on rx - this can't be set twice"); - self.0.once.get().map(Result::as_ref) - } - Ok(None) => { - None - } - Err(_) => { - Some(Err(&InjectError::HandleClosed)) + self.0.once.get().map(Result::as_ref) } + Ok(None) => None, + Err(_) => Some(Err(&InjectError::HandleClosed)), } } /// Resolves as soon as the lazy is available - /// - /// Must not be waited on during module construction + /// + /// Must not be waited on during module construction // Note: Maybe Add a second DI stage (Injection, Pre Start) - where this is allowed - #[must_use] + #[must_use] pub fn wait_result(&self) -> LazyFuture<'_, T> { LazyFuture { lazy: &self.0 } } @@ -143,13 +139,15 @@ impl Lazy { impl Lazy { fn downcast_recv(recv: Result) -> Result, InjectError> { match recv { - Ok(instance) => instance.downcast().map_err(|e| { - RequireError::DowncastFailed { - required_type: type_name::(), - actual_type: e, - } - .into() - }), + Ok(instance) => { + instance.downcast().map_err(|e| { + RequireError::DowncastFailed { + required_type: type_name::(), + actual_type: e, + } + .into() + }) + } Err(e) => Err(e.into()), } } @@ -166,7 +164,7 @@ impl<'a, T: Injectable> Future for LazyFuture<'a, T> { fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll { // Lock receiver, so result is not taken out while we check - let mut rx = self.lazy.rx.lock().unwrap(); + let mut rx = self.lazy.rx.lock().expect("we don't handle poisoning"); // Check if result is ready if let Some(result) = self.lazy.once.get() { @@ -226,7 +224,7 @@ impl Resolver for LazyOption { } impl LazyOption { /// Accesses the Lazy Dependency - returning an error on access - #[must_use] + #[must_use] pub fn try_get(&self) -> Option, &InjectError>> { self.lazy.try_get() } @@ -235,20 +233,21 @@ impl LazyOption { /// /// # Panics /// - If accessed after DI has failed - #[must_use] + #[must_use] pub fn get(&self) -> Option<&Arc> { match self.lazy.try_get() { None => None, Some(Ok(result)) => Some(result), - Some(Err(err)) => match err { - InjectError::RequireError(RequireError::TypeDisabled(_) | -RequireError::TypeMissing(_)) => { - None + Some(Err(err)) => { + match err { + InjectError::RequireError( + RequireError::TypeDisabled(_) | RequireError::TypeMissing(_), + ) => None, + err => { + panic!("Accessed LazyOption after DI failure: {err:?}"); + } } - err => { - panic!("Accessed LazyOption after DI failure: {err:?}"); - } - }, + } } } } diff --git a/features/wrapp-di/src/types.rs b/features/wrapp-di/src/types.rs index 41b9563..b10cdbc 100644 --- a/features/wrapp-di/src/types.rs +++ b/features/wrapp-di/src/types.rs @@ -27,7 +27,7 @@ impl Instance { } /// Attempts to downcast the instance to the requested type - /// + /// /// ### Errors /// - Returns an error if the instance cannot be downcasted to the requested type pub fn downcast(&self) -> Result, &'static str> { @@ -60,7 +60,7 @@ impl std::fmt::Display for TypeInfo { } } impl TypeInfo { - #[must_use] + #[must_use] pub fn of() -> Self { Self { type_name: std::any::type_name::(), diff --git a/features/wrapp-router/Cargo.toml b/features/wrapp-router/Cargo.toml index b3e12e0..dc76f03 100644 --- a/features/wrapp-router/Cargo.toml +++ b/features/wrapp-router/Cargo.toml @@ -2,6 +2,7 @@ name = "wrapp-router" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/features/wrapp-router/src/lib.rs b/features/wrapp-router/src/lib.rs index 21739bf..56df860 100644 --- a/features/wrapp-router/src/lib.rs +++ b/features/wrapp-router/src/lib.rs @@ -1,4 +1,4 @@ -#[must_use] +#[must_use] pub const fn add(left: usize, right: usize) -> usize { left + right } diff --git a/features/wrapp-strategies/Cargo.toml b/features/wrapp-strategies/Cargo.toml index 8884af2..6980ff5 100644 --- a/features/wrapp-strategies/Cargo.toml +++ b/features/wrapp-strategies/Cargo.toml @@ -2,6 +2,7 @@ name = "wrapp-strategies" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/features/wrapp-strategies/src/lib.rs b/features/wrapp-strategies/src/lib.rs index 21739bf..56df860 100644 --- a/features/wrapp-strategies/src/lib.rs +++ b/features/wrapp-strategies/src/lib.rs @@ -1,4 +1,4 @@ -#[must_use] +#[must_use] pub const fn add(left: usize, right: usize) -> usize { left + right } diff --git a/ideas.md b/ideas.md deleted file mode 100644 index 55cc5e7..0000000 --- a/ideas.md +++ /dev/null @@ -1,6 +0,0 @@ -# Strategies - -Instead of making modules mark themselves as X Module -Skip the magic, make a Strategy injector, which then has all relevant functions for adding endpoints. - -More flexibility on both the strategy side and on the user side. Strategy handle can have any kind of function. diff --git a/readme.md b/readme.md index e2aa14c..b68fb14 100644 --- a/readme.md +++ b/readme.md @@ -1,105 +1,103 @@ -# Why? -Building systems which are more than one thing at once (e.g. A WebAPI + Kafka Consumer) often forces -developers to either build more bootstrapping glue or to accept their applications to look chaotic. +# Wrapp -E.g., a typical multi-functional application might require: +**Create composeable Rust Applications, cleanly seperating business logic from infrastructure concerns.** -1. Setting up a web server for API endpoints -2. Configuring Kafka consumers for message processing -3. Initializing database connections -4. Setting up background job schedulers -5. Implementing logging and monitoring systems +> ⚠️ **Work in progress / experimental.** Wrapp is in early development (`v0.0.0`). The API is incomplete, unstable, and changes frequently. It is **not** ready for production use. Expect things to break. -Each of these components often requires its own initialization logic, configuration management, and integration with the rest of the application. This can lead to: +--- -- Complex and hard-to-maintain bootstrapping code -- Tight coupling between different parts of the application -- Difficulty in adding or removing features -- Challenges in testing individual components in isolation -- Inconsistent patterns across different parts of the system +## Why? -These issues can significantly increase the cognitive load on developers and make the codebase harder to evolve over time. -The wrapp ecosystem aims to address these challenges by providing a modular and extensible framework for building multi-functional applications. +Once an application exposes more than one interface, e.g. a web API, Kafka consumer, CLI, you usually end up writing bootstrapping code to wire everything together. -# How? +Each part needs its own setup, config, and wiring, often this leads to chaotic code and thus hard to maintain codebases. -1. Modular Architecture: - Wrapp encourages breaking down the application into smaller, self-contained modules. Each module encapsulates a specific Domain (e.g. User, Todos, Metrics). - Each domain on its own can then use one or multiple Strategies to define how it has to interact with the outside world. - E.g. User could be an Actix and a Kafka module, thus getting input from these sources. - - This approach offers several advantages: - - Improved maintainability: Each module can be developed, tested, and updated independently. - - Flexibility: Domains can easily adopt new strategies or remove existing ones without affecting other parts of the system. - - Scalability: As the application grows, new domains can be added as separate modules without disrupting existing functionality. - - Reusability: Well-defined modules can be reused across different projects or shared within a larger ecosystem. +Wrapp moves that wiring into reusable infrastructure so modules can focus on business logic. - The separation of domains and strategies allows for a clean architecture where business logic (domains) is decoupled from the technical implementation details (strategies), promoting better code organization and easier long-term maintenance. +Wrapp aims to make multi-functional applications **modular** by separating *what* your application does from *how* it talks to the outside world. +## Core idea -# Goal of wrapp: -Create an ecosystem which allows applications to compose themselves of different logical modules. -All modules share a DI framework, and strategies which determine how the outside world can interact with them. - -The DI Framework will allow a Module to declare other Modules it depends on - being optional, required or lazily. -The DI Framework will orchestrate the construction of all Modules, injecting dependencies as needed. - -The strategy system will allow modules to bootstrap themselves to e.g. the Webserver started by the application, or the Kafka Cluster connected to the app. -With the strategy system a module can declare itself as a WebModule (or probably more specific as an ActixModule, even though a general WebModule would be really cool). -This WebModule could then declare it's own routes, handlers, middlewares etc. which if the Module is registerd will attach themselves to the Server. -Then the Module could also declare itself as a KafkaModule, or a PostgresNotifyModule or a GRPC module, or a GraphQLModule etc. - -This way reusability components like a MetricsModule, a TaskQueueModule, and many more could be created which then -can simply be registered allowing their functionality work. - -To simplify the creation of Strategies Wrapp should also provide some useful tools. E.g. a simple Router which can be used to quickly build a new strategy. -In general it is a goal to make Strategies and Modules: - 1. As easy to implement as possible - 2. Backwards compatible for as long as possible +Wrapp takes care of the bootstrapping and wiring, so you can focus on your solutions. +It does this by providing three core concepts: +- **Modules** contain the logic of a domain (e.g. `User`, `Metrics`, `Health`). +- **Strategies** define how the outside world interacts with a module (e.g. an Actix web server or a Kafka consumer). A single module can opt into several strategies. +- A shared **DI (dependency injection)** framework wires everything together and constructs modules in the right order. +- Additional building blocks to simplify creating strategies ```mermaid graph LR; + wrapp --> wrapp-di + wrapp --> wrapp-strategies + wrapp --> wrapp-router + wrapp-strategies --> wrapp-actix + wrapp-di --> Module --> UserApp + wrapp-actix --> Module +``` -wrapp --> wrapp-di -wrapp --> wrapp-strategy -wrapp --> wrapp-router +For the full design rationale see [`docs/idea.md`](docs/idea.md). +For a work in progess API sketch see [`examples/prototype`](examples/prototype). + +### Non Goals + +- Wrapp is **not** a framework for a specific domain (e.g. web) +- Wrapp is **not** a replacement for existing frameworks or libraries (e.g. Actix, Tokio, Kafka clients) +- Wrapp is **not** trying to be compile-time validated. + +## Repository layout + +```text +wrapp/ # umbrella crate +features/ + wrapp-di/ # dependency injection + wrapp-config/ # config injection + wrapp-router/ # routing building block + wrapp-strategies/ # strategy trait +strategies/ # contains strategies for different frameworks + wrapp-actix/ +examples/ + prototype/ # design sketch of the target API +docs/ # scratchpad for ideas and roadmap +``` -wrapp-strategy --> wrapp-kafka -wrapp-strategy --> wrapp-actix -wrapp-strategy --> wrapp-sql-notify +## Development +This is a Cargo workspace, so the usual commands work: +```sh +cargo build +cargo test --all +``` -wrapp-di --> Module1 -wrapp-actix --> ActixWebModule --> Module1 --> UserApp -wrapp-kafka --> KafkaModule --> Module1 --> UserApp -wrapp-tauri --> TauriModule --> Module1 --> UserApp +A helper script bundles the common checks (format, lint, doc, dependency audit, tests): +```sh +./check.sh # run all checks +./check.sh fix # auto-fix formatting/lints where possible +./check.sh help # list available commands ``` -wrapp - overarching framework -wrapp-di - DI framework -wrapp-strategy - Trait for implementing new Strategies - e.g. a WebServer would be a strategy, or Listening to Kafka is a strategy -wrapp-router - Optional component to use for strategies - could e.g. allow routing through proc macros, and extracting app state +The toolchain is pinned via [`rust-toolchain.toml`](rust-toolchain.toml). + +## Roadmap + +See [`docs/todo`](docs/todo). + +## Contributing -E.g. +Wrapp is in an exploratory phase, so designs and APIs are still in flux. +Issues and ideas are welcome - but expect significant churn before anything stabilizes. -wrapp-actixs implements wrapp-strategy -wrapp-actixs thus has hooks to: collect all the routes and config it needs and start the actix server -wrapp-actixs exports a trait wrapp-actix-module, which if implemented by a module, has the functions needed to collect all routes and config -since actix has it's own router, it will not use wrapp-router, but the wrapp-axtic strategy will add the finished DI container to actix's app state, and will provide an etractor like WrappState, to access data from the Wrapp-DI +## License -or +Licensed under either of -wrapp-kafka implements wrapp-strategy -wrapp-kafka has a hook to collect all routes for messages, and has an implementation to start clients to listen to kafka -it exports a trait wrapp-kafka-module, which if implemented... -since this does not have it's own router, it can opt in to use the wrapp router, automatically allowing it to use e.g. middlewares and injection on it's endpoints. +- Apache License, Version 2.0 (LICENSE-APACHE or ) +- MIT license (LICENSE-MIT or ) -Possible Issues: -1. Version conflicts -If module has serverely different versions of e.g. wrapp-actixs, this could lead to annoyances +#### Contribution +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/scripts/ci-local.sh b/scripts/ci-local.sh deleted file mode 100755 index f6ce76f..0000000 --- a/scripts/ci-local.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env sh -# Run the same checks as .github/workflows/ci.yml locally, using the *real* -# aderinom/rust-all-action bundle (no source changes to the action itself). -# -# The action is vendored into a git-ignored, repo-relative folder (.tools/), -# so this never depends on a path outside the repository. -# -# Usage: -# scripts/ci-local.sh # fmt, clippy, shear, deny, test, doc -# RUN=clippy scripts/ci-local.sh # run a single workflow -# RUN=fmt,clippy scripts/ci-local.sh # run a subset (no spaces!) -# ACTION_REF=main scripts/ci-local.sh # pin a different action ref -# VERBOSE=1 scripts/ci-local.sh # also show ::debug:: lines -# NO_COLOR=1 scripts/ci-local.sh # disable colored output -set -eu - -# --- pretty output -------------------------------------------------------- -# The action speaks GitHub's workflow-command dialect (::group::, ::warning::, -# ...). Locally we translate those into readable, colored terminal output. -if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then - COLOR=1 - C_RESET=$(printf "\033[0m"); C_BOLD=$(printf "\033[1m") - C_CYAN=$(printf "\033[36m"); C_RED=$(printf "\033[31m") - C_GRN=$(printf "\033[32m"); C_YEL=$(printf "\033[33m") -else - COLOR=0 - C_RESET=; C_BOLD=; C_CYAN=; C_RED=; C_GRN=; C_YEL= -fi -VERBOSE="${VERBOSE:-0}" - -say() { printf "%s>>%s %s\n" "$C_BOLD$C_CYAN" "$C_RESET" "$*"; } -die() { printf "%s!!%s %s\n" "$C_BOLD$C_RED" "$C_RESET" "$*" >&2; exit 1; } - -# --- config --------------------------------------------------------------- -ACTION_REPO="https://github.com/aderinom/rust-all-action.git" -ACTION_REF="${ACTION_REF:-v1}" # matches `uses: aderinom/rust-all-action@v1` -RUN="${RUN:-all-default,deny}" # all-default = fmt,clippy,shear,test,doc -TOOLCHAIN="${TOOLCHAIN:-stable}" # rust-toolchain.toml channel still wins - -# --- paths ---------------------------------------------------------------- -REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd) -VENDOR_DIR="$REPO_ROOT/.tools/rust-all-action" -ENTRY="$VENDOR_DIR/dist/index.js" - -# --- fetch / update the vendored action bundle ---------------------------- -if [ ! -d "$VENDOR_DIR/.git" ]; then - say "Cloning rust-all-action@$ACTION_REF into .tools/ ..." - git clone --quiet --depth 1 --branch "$ACTION_REF" "$ACTION_REPO" "$VENDOR_DIR" -else - say "Updating rust-all-action to $ACTION_REF ..." - git -C "$VENDOR_DIR" fetch --quiet --depth 1 origin "$ACTION_REF" - git -C "$VENDOR_DIR" checkout --quiet --force FETCH_HEAD -fi - -[ -f "$ENTRY" ] || die "Bundled entry point not found: $ENTRY" - -# --- run ------------------------------------------------------------------ -# GitHub Actions passes inputs as INPUT_ env vars; the action reads them -# via @actions/core.getInput. cacheKey=no-cache disables the GitHub-only cache -# service calls, leaving tool install + workflow execution intact. -say "Running workflows: $RUN" -cd "$REPO_ROOT" - -# awk filter: translate GitHub workflow commands into readable terminal output -# and indent everything emitted inside a ::group::. -FILTER=' -function decode(s) { - gsub(/%0D/, "\r", s) - gsub(/%0A/, "\n", s) - gsub(/%3A/, ":", s) - gsub(/%2C/, ",", s) - gsub(/%25/, "%", s) - return s -} -BEGIN { - esc = sprintf("%c", 27) - if (color == "1") { - reset = esc "[0m"; bold = esc "[1m"; dim = esc "[2m" - red = esc "[31m"; grn = esc "[32m"; yel = esc "[33m"; cyn = esc "[36m" - } -} -{ - if (substr($0, 1, 2) == "::") { - rest = substr($0, 3) - idx = index(rest, "::") - if (idx > 0) { - header = substr(rest, 1, idx - 1) - msg = decode(substr(rest, idx + 2)) - split(header, parts, " ") - cmd = parts[1] - if (cmd == "group") { printf "\n%s%s▶ %s%s\n", bold, cyn, msg, reset; ingroup = 1; next } - if (cmd == "endgroup") { ingroup = 0; next } - if (cmd == "debug") { if (verbose == "1") printf "%s · %s%s\n", dim, msg, reset; next } - if (cmd == "warning") { if (index(msg, "SCCACHE_PATH is not set") == 0) printf "%s⚠ %s%s\n", yel, msg, reset; next } - if (cmd == "error") { printf "%s✖ %s%s\n", red, msg, reset; next } - if (cmd == "notice") { printf "%s● %s%s\n", grn, msg, reset; next } - next - } - } - if (ingroup == 1) print " " $0; else print $0 -} -' - -# Run the action, but keep ITS exit code (not awk's) so failures still fail. -# POSIX sh has no pipefail, so stash the status in a temp file. -status_file=$(mktemp) -trap 'rm -f "$status_file"' EXIT INT TERM - -set +e -{ - INPUT_PROJECT="." \ - INPUT_RUN="$RUN" \ - INPUT_TOOLCHAIN="$TOOLCHAIN" \ - INPUT_CACHEKEY="no-cache" \ - node "$ENTRY" 2>&1 - echo $? > "$status_file" -} | awk -v color="$COLOR" -v verbose="$VERBOSE" "$FILTER" -set -e - -code=$(cat "$status_file" 2>/dev/null || echo 1) -case "$code" in (""|*[!0-9]*) code=1 ;; esac - -echo -if [ "$code" -eq 0 ]; then - printf "%s✔ All workflows passed%s (%s)\n" "$C_BOLD$C_GRN" "$C_RESET" "$RUN" -elif [ "$code" -eq 130 ]; then - printf "%s■ Interrupted%s\n" "$C_BOLD$C_YEL" "$C_RESET" -else - printf "%s✖ Workflow(s) failed%s (exit %s)\n" "$C_BOLD$C_RED" "$C_RESET" "$code" -fi -exit "$code" diff --git a/strategies/wrapp-actix/Cargo.toml b/strategies/wrapp-actix/Cargo.toml index a05c071..0769bae 100644 --- a/strategies/wrapp-actix/Cargo.toml +++ b/strategies/wrapp-actix/Cargo.toml @@ -2,6 +2,7 @@ name = "wrapp-actix" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/strategies/wrapp-actix/src/lib.rs b/strategies/wrapp-actix/src/lib.rs index 21739bf..56df860 100644 --- a/strategies/wrapp-actix/src/lib.rs +++ b/strategies/wrapp-actix/src/lib.rs @@ -1,4 +1,4 @@ -#[must_use] +#[must_use] pub const fn add(left: usize, right: usize) -> usize { left + right } diff --git a/wrapp/Cargo.toml b/wrapp/Cargo.toml index 702bc9f..a2501fa 100644 --- a/wrapp/Cargo.toml +++ b/wrapp/Cargo.toml @@ -2,6 +2,7 @@ name = "wrapp" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/wrapp/src/lib.rs b/wrapp/src/lib.rs index 21739bf..56df860 100644 --- a/wrapp/src/lib.rs +++ b/wrapp/src/lib.rs @@ -1,4 +1,4 @@ -#[must_use] +#[must_use] pub const fn add(left: usize, right: usize) -> usize { left + right } From e0f75cbd65fc8c98febff29c11bd74311d121cc0 Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 17:40:03 +0200 Subject: [PATCH 06/12] fix: ci yml --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b062589..41355b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ permissions: contents: read jobs: - rust-fmt: rust-all: name: Rust (fmt, clippy, test, doc, shear) runs-on: ubuntu-latest From 7a72fd1ea4d6765d461f0c627d61b98da414a156 Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 17:41:18 +0200 Subject: [PATCH 07/12] fix: ci fmt nightly --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41355b5..bbd4690 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,4 @@ jobs: with: # 'all-default' = test, clippy, fmt, doc, shear. run: fmt, clippy, test, doc, shear, deny + flow-fmt-toolchain: nightly From 43f3969c3bfb7502c86fae82c542466f55a4a5e5 Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 17:44:15 +0200 Subject: [PATCH 08/12] fix: ci the third --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbd4690..c09b2d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,5 @@ jobs: - name: Run Rust workflows uses: aderinom/rust-all-action@v1 with: - # 'all-default' = test, clippy, fmt, doc, shear. - run: fmt, clippy, test, doc, shear, deny + run: fmt,clippy,test,doc,shear,deny flow-fmt-toolchain: nightly From 36f0c76bd26b54838079730da6592b9ad88d5212 Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 21:10:08 +0200 Subject: [PATCH 09/12] cleanup --- .github/workflows/ci.yml | 2 +- .vscode/settings.json | 2 +- Cargo.lock | 99 ++++++++++++++++++++ Cargo.toml | 1 + deny.toml | 3 +- features/wrapp-di/Cargo.toml | 3 + features/wrapp-di/examples/manual-di-impl.rs | 30 +++++- features/wrapp-di/src/builder.rs | 2 +- features/wrapp-di/src/initiator.rs | 67 +++++++++++-- features/wrapp-di/src/lib.rs | 56 ++++++----- 10 files changed, 226 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c09b2d1..0120f29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: + branches: - main - dev pull_request: diff --git a/.vscode/settings.json b/.vscode/settings.json index b9c087f..efe6630 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,5 @@ ], "rust-analyzer.rustfmt.extraArgs": [ "+nightly" - ], + ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a98fd16..066d345 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "futures" version = "0.3.32" @@ -90,12 +96,33 @@ dependencies = [ "slab", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + [[package]] name = "memchr" version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -126,12 +153,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + [[package]] name = "syn" version = "2.0.118" @@ -163,6 +205,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tracing" version = "0.1.44" @@ -192,6 +243,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -200,6 +277,27 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "wrapp" version = "0.0.0" @@ -226,6 +324,7 @@ dependencies = [ "pin-project-lite", "thiserror", "tracing", + "tracing-subscriber", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 64792f7..a3f6541 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ wrapp-di = { version = "0.0.0", path = "./features/wrapp-di" } futures = "0.3" futures-channel = "0.3" tracing = "0.1" +tracing-subscriber = "0.3.23" pin-project-lite = "0.2" thiserror = "2.0" diff --git a/deny.toml b/deny.toml index 50430e2..40a51dc 100644 --- a/deny.toml +++ b/deny.toml @@ -1,7 +1,6 @@ # cargo-deny configuration — https://embarkstudios.github.io/cargo-deny/ -# Licensing policy for this MIT-licensed project: only permissive, -# MIT-compatible licenses are allowed. Copyleft licenses are rejected. +# We are licensed under MIT/Apache-2.0. Explicitly deny any copyleft licenses [graph] all-features = true diff --git a/features/wrapp-di/Cargo.toml b/features/wrapp-di/Cargo.toml index 9c49b93..a9882c0 100755 --- a/features/wrapp-di/Cargo.toml +++ b/features/wrapp-di/Cargo.toml @@ -12,7 +12,10 @@ futures-channel.workspace = true tracing.workspace = true pin-project-lite.workspace = true thiserror.workspace = true +tracing-subscriber.workspace = true +[dev-dependencies] +tracing-subscriber.workspace = true [lints] workspace = true diff --git a/features/wrapp-di/examples/manual-di-impl.rs b/features/wrapp-di/examples/manual-di-impl.rs index fd282e1..72d6198 100644 --- a/features/wrapp-di/examples/manual-di-impl.rs +++ b/features/wrapp-di/examples/manual-di-impl.rs @@ -8,6 +8,7 @@ use std::{error::Error, fmt::Debug, sync::Arc}; use wrapp_di::{ builder::DiBuilder, + errors::InjectError, factories::InstanceFactory, initiator::DiHandle, resolver::{ @@ -17,9 +18,18 @@ use wrapp_di::{ types::DependencyInfo, }; +struct TestConfig { + enable: bool, +} + fn main() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .init(); + // Create a new DI builder and register instances and factories let app = DiBuilder::new() + .add_instance(TestConfig { enable: true }) .add_instance(124_i128) .add_instance("test".to_string()) .add_factory(TestFactory); @@ -59,20 +69,34 @@ impl InstanceFactory for TestFactory { ] } + /// Determines if the `TestFactory` is enabled based on the `TestConfig` instance in the DI + /// container. + /// + /// This is run before the `construct` method and allows for conditional instantiation of the + /// `Test` struct. + async fn is_enabled( + &mut self, + mut di: DiHandle, + ) -> Result> { + let enabled = Option::>::resolve(&mut di) + .await? + .map_or(false, |config| config.enable); + Ok::<_, InjectError>(enabled) + } + /// Constructs a new instance of the Test struct with dependencies injected from the DI /// container. - #[allow(refining_impl_trait)] async fn construct( &mut self, mut di: DiHandle, - ) -> Result> { + ) -> Result> { // Resolve the dependencies from the DI container let str = Arc::::resolve(&mut di).await?; let str2 = Lazy::::resolve(&mut di).await?; let str3 = LazyOption::::resolve(&mut di).await?; // Return a new instance of Test with the resolved dependencies - Ok(Test { + Ok::<_, InjectError>(Test { a: str, b: str2, c: str3, diff --git a/features/wrapp-di/src/builder.rs b/features/wrapp-di/src/builder.rs index a477b9b..d888fff 100644 --- a/features/wrapp-di/src/builder.rs +++ b/features/wrapp-di/src/builder.rs @@ -30,7 +30,7 @@ impl DiBuilder { } } impl DiBuilder { - /// Adds a instance of a type to the DI container. + /// Adds an instance of a type to the DI container. /// /// An instance is a concrete object that has already been created and is ready to be used. /// These instances are stored in the DI container and can be retrieved by their type during diff --git a/features/wrapp-di/src/initiator.rs b/features/wrapp-di/src/initiator.rs index 03b8504..e1d9937 100644 --- a/features/wrapp-di/src/initiator.rs +++ b/features/wrapp-di/src/initiator.rs @@ -1,6 +1,7 @@ use std::{ any::TypeId, collections::{HashMap, HashSet}, + fmt::Display, sync::Arc, thread::{self, sleep}, time::Duration, @@ -88,7 +89,30 @@ impl DiInitiator { return Err(e); } - tracing::debug!("All"); + tracing::debug!( + "All factories finished, all injection requests handled, final instance count: {}", + self.instances.len() + ); + #[cfg(debug_assertions)] + { + for (type_id, (info, instance)) in &self.instances { + let status = if instance.is_some() { + "" + } else { + "[disabled] " + }; + tracing::debug!(" - {}{}", status, info.type_name,); + } + } + + debug_assert!( + self.instance_waiters.is_empty(), + "Not all instance waiters were satisfied" + ); + debug_assert!( + self.container_waiters.is_empty(), + "Not all container waiters were satisfied" + ); let container = DiContainer::new(self.instances, graph); for waiter in self.container_waiters { @@ -146,7 +170,6 @@ impl DiInitiator { // Construct factory let instance = Box::into_pin(factory.construct(handle.clone())).await?; - tracing::debug!("Constructed instance of {}", instance.info.type_name); Ok::<_, DynError>(Some(instance)) } .await; @@ -158,13 +181,14 @@ impl DiInitiator { } // Start handling injection requests and wait for all factories to finish - // let pin_set = pin!(set); let factory_count = factory_futures.len(); loop { let factories_left = factory_futures.len(); - tracing::debug!( - "Waiting for factories to finish [{factories_left} of {factory_count} complete]" + let waiters = self.instance_waiters.len(); + let container_waiters = self.container_waiters.len(); + tracing::trace!( + "Polling initiation [{factories_left} of {factory_count} in progress, waiters: {waiters}, container waiters: {container_waiters}]" ); futures::select! { @@ -202,6 +226,8 @@ impl DiInitiator { return Ok(true); }; + tracing::debug!("Factory for {} finished", info.type_name); + match result { Ok(instance) => { handle_created_instance(self, info, instance); @@ -239,6 +265,7 @@ impl DiInitiator { .into_iter() .flat_map(Vec::into_iter) { + tracing::trace!("Informing waiter for {} with result", type_name); let _ = waiter.send(message.clone()); } } @@ -256,6 +283,8 @@ impl DiInitiator { // Injection Request handlers impl DiInitiator { fn handle_injection_request(&mut self, request: DiRequest) { + tracing::trace!("Got injection request: {}", request); + match request { DiRequest::Require { type_info, @@ -281,13 +310,21 @@ impl DiInitiator { // Check if we already have a result if let Some((_, result)) = self.instances.get(&info.type_id) { - let _ = match result { - Some(instance) => response_channel.send(Ok(instance.clone())), - None => response_channel.send(Err(RequireError::TypeDisabled(info.type_name))), + tracing::trace!("Found result for {} - sending to waiter", info.type_name); + let response = match result { + Some(instance) => Ok(instance.clone()), + None => Err(RequireError::TypeDisabled(info.type_name)), }; + + // Ignore error, receiver is just dropped + let _ = response_channel.send(response); return; } + tracing::trace!( + "No result found for {} - adding to waiters list", + info.type_name + ); // Otherwise add the request to the waiters list self.instance_waiters .entry(info.type_id) @@ -328,8 +365,20 @@ pub enum DiRequest { type_info: TypeInfo, response_channel: DiResponseSender, }, - /// Requires a reference to the [`DiContainer`] once it has been build + /// Requires a reference to the [`DiContainer`] once it has been built RequireApp { response_channel: DiResponseSender, }, } +impl Display for DiRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DiRequest::Require { type_info, .. } => { + write!(f, "Require({})", type_info.type_name) + } + DiRequest::RequireApp { .. } => { + write!(f, "RequireApp") + } + } + } +} diff --git a/features/wrapp-di/src/lib.rs b/features/wrapp-di/src/lib.rs index 73c4043..7659420 100644 --- a/features/wrapp-di/src/lib.rs +++ b/features/wrapp-di/src/lib.rs @@ -1,30 +1,42 @@ -//! Wrapp DI +//! # Wrapp DI //! -//! Wrapp DI consists of following components: +//! Opinionated dependency injection framework for Wrapp. //! -//! 1. `DiBuilder` - for registering all factories and instances. -//! 2. `DiInitiator` - which executes all factories, and handles injection requests during their -//! initialization. 2.1. Injection Handles - which are valid during the `AppInit` Phase, and -//! allow a component to request its dependencies. -//! 3. `DIContainer` - which is the final container that holds all created instances. +//! ### Overview +//! - [`DiBuilder`]: Main registration and building interface for the DI container. +//! - [`DiContainer`]: The DI container that holds instances and resolves dependencies. +//! - [`InstanceFactory`]: Trait for factories that can create instances of specific types. Usually +//! generated by the `todo!()` proc macro. +//! - [`Resolver`]: Trait allowing implementation of custom resolution strategies. //! -//! General logic: -//! `AppBuilder` calls `BUild` +//! ### Example //! -//! `AppGraph` queries all factories for their dependencies and creates a graph. -//! `AppGraph` checks the graph for circular dependencies, and aborts DI if: -//! - Any required Dependencies are missing -//! - Any Circular Dependencies were found +//! ``` +#![doc = include_str!("../examples/manual-di-impl.rs")] +//! ``` +//! +//! ### Internal Flow //! -//! `AppInitiator` creates tasks for all factories. -//! Factories start asking for their dependencies. -//! Some Factory will not require a dependency (or just requires already existing instances), and as -//! such will complete After Completion, `AppInitiator` gets the result of the completed task. -//! `AppInitiator` then informs all factories which were waiting for this dependency. -//! Step by step all factories will complete -//! After All factories have completed, `AppInitiator` creates the final `DIContainer`. -//! `AppInitiator` publishes the `DIcontainer` through a channel to all dependencies. -//! `DiContainer` wil also be given to the caller of `AppBuilder.build()` +//! - All dependencies are registered in the [`DiBuilder`] either as instances or factories. +//! - When [`DiBuilder::build`] is called, the [`DiInitiator`] is created. +//! - The [`DiInitiator`] creates a [`DependencyGraph`] from the registered dependencies, checking +//! for circular dependencies and other issues. +//! - Afterwards all [`InstanceFactory`] are called and executed in a [`FuturesUnordered`], first to +//! check if they are enabled, and then to create their instances. +//! - Results from the [`InstanceFactory`] are stored and become available through a handle to the +//! [`DiInitiator`]. +//! - After all factories have been called, all results are collected and stored in the +//! [`DiContainer`], which is then returned to the application. +//! +//! +//! [`InstanceFactory`]: factories::InstanceFactory +//! [`DiBuilder`]: builder::DiBuilder +//! [`DiBuilder::build`]: builder::DiBuilder::build +//! [`DiInitiator`]: initiator::DiInitiator +//! [`DiContainer`]: container::DiContainer +//! [`DependencyGraph`]: dependency_graph::DependencyGraph +//! [`Resolver`]: resolver::Resolver +//! [`FuturesUnordered``]: futures::stream::FuturesUnordered pub mod builder; pub mod container; From 3e7e842985f48d49019bda2d437945e3df3cd84d Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 22:23:37 +0200 Subject: [PATCH 10/12] fix: bug with lazy injection --- Cargo.lock | 1 - Cargo.toml | 1 - check.sh | 5 +- docs/todo | 3 + features/wrapp-di/Cargo.toml | 2 - features/wrapp-di/examples/manual-di-impl.rs | 4 +- features/wrapp-di/src/initiator.rs | 38 +++- features/wrapp-di/src/resolver/lazy.rs | 183 +++++++------------ 8 files changed, 106 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 066d345..a6111cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,7 +321,6 @@ version = "0.0.0" dependencies = [ "futures", "futures-channel", - "pin-project-lite", "thiserror", "tracing", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index a3f6541..3b2c9e1 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ futures = "0.3" futures-channel = "0.3" tracing = "0.1" tracing-subscriber = "0.3.23" -pin-project-lite = "0.2" thiserror = "2.0" diff --git a/check.sh b/check.sh index 8ab5f3c..6349dd1 100755 --- a/check.sh +++ b/check.sh @@ -43,7 +43,7 @@ cargo_alias() { # doc gets the full documentation argument set doc) REPLY=(doc --workspace --all-features --no-deps --document-private-items "${@:2}") ;; # lint is clippy with the workspace-wide settings - lint) REPLY=(clippy --all-targets "${@:2}") ;; + lint) REPLY=(clippy --all-targets "${@:2}" -- -D warnings) ;; # test is a workspace-wide test run test) REPLY=(test --all "${@:2}") ;; *) REPLY=("$@") ;; @@ -144,8 +144,7 @@ fi # All Fixes if [ "$1" = "fix" ]; then - # Kept sequential on purpose: lint --fix and fmt both rewrite the same .rs - # files, so running them concurrently would race and corrupt the sources. + # Sequentially run all fixable steps, recording pass/fail for the summary. run_step "lint --fix" lint --fix --allow-dirty run_step "format" fmt --all run_step "shear --fix" shear --fix diff --git a/docs/todo b/docs/todo index 0d4645c..c5807ed 100644 --- a/docs/todo +++ b/docs/todo @@ -1,6 +1,9 @@ [~] Wrapp [~] DI [x] Implementation + [ ] Rework: Error handling and Lazy + Possibly change it so that instead of returning the error to the requestor, DI just fails, this simplifies error handling + Furthermore Lazy should be looked at again, it's akward to use. [] Proc Macro [x] Config diff --git a/features/wrapp-di/Cargo.toml b/features/wrapp-di/Cargo.toml index a9882c0..40cfd0a 100755 --- a/features/wrapp-di/Cargo.toml +++ b/features/wrapp-di/Cargo.toml @@ -10,9 +10,7 @@ license = { workspace = true } futures.workspace = true futures-channel.workspace = true tracing.workspace = true -pin-project-lite.workspace = true thiserror.workspace = true -tracing-subscriber.workspace = true [dev-dependencies] tracing-subscriber.workspace = true diff --git a/features/wrapp-di/examples/manual-di-impl.rs b/features/wrapp-di/examples/manual-di-impl.rs index 72d6198..fbae515 100644 --- a/features/wrapp-di/examples/manual-di-impl.rs +++ b/features/wrapp-di/examples/manual-di-impl.rs @@ -4,7 +4,7 @@ //! but this example shows how to do it manually to help understanding what happens under the hood. #![allow(dead_code)] -use std::{error::Error, fmt::Debug, sync::Arc}; +use std::{fmt::Debug, sync::Arc}; use wrapp_di::{ builder::DiBuilder, @@ -80,7 +80,7 @@ impl InstanceFactory for TestFactory { ) -> Result> { let enabled = Option::>::resolve(&mut di) .await? - .map_or(false, |config| config.enable); + .is_some_and(|config| config.enable); Ok::<_, InjectError>(enabled) } diff --git a/features/wrapp-di/src/initiator.rs b/features/wrapp-di/src/initiator.rs index e1d9937..c995049 100644 --- a/features/wrapp-di/src/initiator.rs +++ b/features/wrapp-di/src/initiator.rs @@ -95,7 +95,7 @@ impl DiInitiator { ); #[cfg(debug_assertions)] { - for (type_id, (info, instance)) in &self.instances { + for (info, instance) in self.instances.values() { let status = if instance.is_some() { "" } else { @@ -206,6 +206,32 @@ impl DiInitiator { } } + // Close the request channel and handle any remaining requests + self.request_rx.close(); + loop { + tracing::trace!( + "Polling remaining injection requests [{} waiters, {} container waiters]", + self.instance_waiters.len(), + self.container_waiters.len(), + ); + futures::select! { + request = self.request_rx.next() => { + let Some(request) = request else { + break; + }; + self.handle_injection_request(request); + + assert!(self.instance_waiters.is_empty(), + "Not all instance waiters were satisfied after all factories finished: {} waiters remain - this is a bug in the DI system", + self.instance_waiters.len() + ); + } + _ = timeout => { + return Err(InitError::Timeout) + } + } + } + Ok(()) } @@ -219,10 +245,6 @@ impl DiInitiator { let Some((info, result)) = result else { // A none result means all tasks are complete, we exit the loop // all injection requests must now also be handled as nothing is left to be build - debug_assert!( - self.instance_waiters.is_empty(), - "Not all waiters were satisfied" - ); return Ok(true); }; @@ -316,7 +338,7 @@ impl DiInitiator { None => Err(RequireError::TypeDisabled(info.type_name)), }; - // Ignore error, receiver is just dropped + // Ignore error, as the receiver may have been dropped let _ = response_channel.send(response); return; } @@ -373,10 +395,10 @@ pub enum DiRequest { impl Display for DiRequest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - DiRequest::Require { type_info, .. } => { + Self::Require { type_info, .. } => { write!(f, "Require({})", type_info.type_name) } - DiRequest::RequireApp { .. } => { + Self::RequireApp { .. } => { write!(f, "RequireApp") } } diff --git a/features/wrapp-di/src/resolver/lazy.rs b/features/wrapp-di/src/resolver/lazy.rs index 137bd73..090933e 100644 --- a/features/wrapp-di/src/resolver/lazy.rs +++ b/features/wrapp-di/src/resolver/lazy.rs @@ -1,15 +1,12 @@ use std::{ any::type_name, fmt::Debug, - future::Future, ops::Deref, - sync::{Arc, Mutex, OnceLock}, - task::{Context, Poll}, + sync::{Arc, OnceLock}, }; -use futures::{FutureExt, SinkExt}; +use futures::{FutureExt, SinkExt, future::Shared}; use futures_channel::oneshot; -use pin_project_lite::pin_project; use crate::{ errors::{InjectError, RequireError}, @@ -18,6 +15,12 @@ use crate::{ types::{DependencyInfo, Injectable, Instance, TypeInfo}, }; +/// Shared receiver for a lazily resolved instance. +/// +/// [`Shared`] lets the single backing oneshot be observed from every clone, handling +/// the "resolve once, read many" synchronization so we don't have to. +type SharedInstance = Shared>; + /// Lazily resolved dependency /// /// Should only be accessed after the DI process has completed. @@ -32,16 +35,18 @@ use crate::{ /// should happen unless: /// - It is accessed during the Injection Phase /// - It is accessed after DI has already Failed -pub struct Lazy(Arc>); +pub struct Lazy { + /// Shared source future, resolved once the instance becomes available. + source: SharedInstance, + /// Caches the downcast result so [`get`](Self::get) can hand out a stable reference. + resolved: OnceLock, InjectError>>, +} impl Debug for Lazy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Lazy").field(&self.get()).finish() + // Use `try_get` so debugging an unresolved `Lazy` does not panic. + f.debug_tuple("Lazy").field(&self.try_get()).finish() } } -struct LazyInner { - once: OnceLock, InjectError>>, - rx: Mutex>, -} impl Deref for Lazy { type Target = Arc; @@ -55,6 +60,12 @@ impl Resolver for Lazy { Self: Sized, { let (tx, rx) = oneshot::channel(); + + tracing::trace!( + "Lazy dependency {} requested - will be resolved when available", + type_name::() + ); + handle .request_sender .send(DiRequest::Require { @@ -64,12 +75,11 @@ impl Resolver for Lazy { .await .map_err(|_| InjectError::HandleClosed)?; - // Check if we got an immediate error - - Ok(Self(Arc::new(LazyInner { - once: OnceLock::new(), - rx: Mutex::new(rx), - }))) + // We queue the request, but don't wait for the result here. + Ok(Self { + source: rx.shared(), + resolved: OnceLock::new(), + }) } fn dependency_info() -> DependencyInfo { @@ -80,116 +90,67 @@ impl Resolver for Lazy { } } } + impl Lazy { - /// Accesses the Lazy Dependency + /// Accesses the lazy dependency. /// /// # Panics - /// - When accessed before the DI Container Init has completed + /// - When accessed before the DI container init has completed. + /// - When accessed after DI has already failed. #[must_use] pub fn get(&self) -> &Arc { self.try_get() .expect("Lazy inject accessed before initialized") - .as_ref() - .expect("Lazy inject accessed after DI failed") + .expect("Lazy inject result contained an error - this should not happen if accessed after DI has completed") } - /// Try to access the lazy dependency + /// Tries to access the lazy dependency without blocking. /// - /// ### Panics - /// - When accessed before the DI Container Init has completed + /// Returns `None` while the dependency has not been resolved yet. pub fn try_get(&self) -> Option, &InjectError>> { - if let Some(result) = self.0.once.get() { - return Some(result.as_ref()); + if self.resolved.get().is_none() { + // Drive the shared future once with a no-op waker. This yields `None` + // while the backing oneshot has not produced a value yet. + let output = self.source.clone().now_or_never()?; + // First writer wins; concurrent callers just observe the same result. + let _ = self.resolved.set(Self::downcast(output)); } - // Lock receiver, so result is not taken out while we check - let mut recv = self.0.rx.lock().expect("we don't handle poisoning"); - - // Double check once - it might have been set while we waited for the lock - if let Some(result) = self.0.once.get() { - return Some(result.as_ref()); - } - - // Otherwise, try to receive result, and set once - match recv.try_recv() { - Ok(Some(rx)) => { - let res = Self::downcast_recv(rx); - self.0 - .once - .set(res) - .map_err(|_| ()) - .expect("holding lock on rx - this can't be set twice"); - - self.0.once.get().map(Result::as_ref) - } - Ok(None) => None, - Err(_) => Some(Err(&InjectError::HandleClosed)), - } + self.resolved.get().map(Result::as_ref) } - /// Resolves as soon as the lazy is available + /// Resolves as soon as the lazy dependency is available. /// - /// Must not be waited on during module construction - // Note: Maybe Add a second DI stage (Injection, Pre Start) - where this is allowed - #[must_use] - pub fn wait_result(&self) -> LazyFuture<'_, T> { - LazyFuture { lazy: &self.0 } - } -} -impl Lazy { - fn downcast_recv(recv: Result) -> Result, InjectError> { - match recv { - Ok(instance) => { - instance.downcast().map_err(|e| { - RequireError::DowncastFailed { - required_type: type_name::(), - actual_type: e, - } - .into() - }) - } - Err(e) => Err(e.into()), - } - } -} - -pin_project! { - pub struct LazyFuture<'a, T:Injectable> { - #[pin] - lazy: &'a LazyInner, + /// Must not be awaited during module construction - the dependency may not be + /// constructed yet at that point. + /// + /// # Errors + /// + /// See [`InjectError`] for possible errors during resolution. + /// + /// # Panics + /// - When accessed after DI has already failed. + /// - When accessed before the DI container init has completed. + // Note: Maybe add a second DI stage (Injection, Pre-Start) - where this is allowed. + pub async fn wait(&self) -> Result<&Arc, &InjectError> { + // Await the shared future so a real waker is registered. + let _ = self.source.clone().await; + self.try_get() + .expect("source resolved - try_get must return a value") } -} -impl<'a, T: Injectable> Future for LazyFuture<'a, T> { - type Output = &'a Result, InjectError>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll { - // Lock receiver, so result is not taken out while we check - let mut rx = self.lazy.rx.lock().expect("we don't handle poisoning"); - // Check if result is ready - if let Some(result) = self.lazy.once.get() { - return Poll::Ready(result); - } - - // Poll receiver for result - match rx.poll_unpin(cx) { - Poll::Ready(recv) => { - // We have a result, handle it and set once lock - let res = match recv { - Ok(instance) => Lazy::::downcast_recv(instance), - Err(e) => Err(e.into()), - }; - - self.lazy - .once - .set(res) - .map_err(|_| ()) - .expect("holding lock on rx - this can't be set"); - - Poll::Ready(self.lazy.once.get().expect("just set once - must be set")) + /// Downcasts the resolved instance into the requested type. + fn downcast( + output: Result, oneshot::Canceled>, + ) -> Result, InjectError> { + let instance = output.map_err(|_| InjectError::HandleClosed)??; + instance.downcast::().map_err(|actual_type| { + RequireError::DowncastFailed { + required_type: type_name::(), + actual_type, } - Poll::Pending => Poll::Pending, - } + .into() + }) } } @@ -223,12 +184,6 @@ impl Resolver for LazyOption { } } impl LazyOption { - /// Accesses the Lazy Dependency - returning an error on access - #[must_use] - pub fn try_get(&self) -> Option, &InjectError>> { - self.lazy.try_get() - } - /// Accesses the Lazy Dependency /// /// # Panics From f80d5d44dcd94cba627ab6b9baa37adc3213c263 Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 22:46:45 +0200 Subject: [PATCH 11/12] refactor: cr --- docs/todo | 2 +- features/wrapp-di/src/lib.rs | 2 +- readme.md | 4 ++-- rust-toolchain.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/todo b/docs/todo index c5807ed..af1e179 100644 --- a/docs/todo +++ b/docs/todo @@ -3,7 +3,7 @@ [x] Implementation [ ] Rework: Error handling and Lazy Possibly change it so that instead of returning the error to the requestor, DI just fails, this simplifies error handling - Furthermore Lazy should be looked at again, it's akward to use. + Furthermore Lazy should be looked at again, it's awkward to use. [] Proc Macro [x] Config diff --git a/features/wrapp-di/src/lib.rs b/features/wrapp-di/src/lib.rs index 7659420..1b879fb 100644 --- a/features/wrapp-di/src/lib.rs +++ b/features/wrapp-di/src/lib.rs @@ -36,7 +36,7 @@ //! [`DiContainer`]: container::DiContainer //! [`DependencyGraph`]: dependency_graph::DependencyGraph //! [`Resolver`]: resolver::Resolver -//! [`FuturesUnordered``]: futures::stream::FuturesUnordered +//! [`FuturesUnordered`]: futures::stream::FuturesUnordered pub mod builder; pub mod container; diff --git a/readme.md b/readme.md index b68fb14..eaee5af 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ # Wrapp -**Create composeable Rust Applications, cleanly seperating business logic from infrastructure concerns.** +**Create composable Rust Applications, cleanly separating business logic from infrastructure concerns.** > ⚠️ **Work in progress / experimental.** Wrapp is in early development (`v0.0.0`). The API is incomplete, unstable, and changes frequently. It is **not** ready for production use. Expect things to break. @@ -39,7 +39,7 @@ graph LR; ``` For the full design rationale see [`docs/idea.md`](docs/idea.md). -For a work in progess API sketch see [`examples/prototype`](examples/prototype). +For a work in progress API sketch see [`examples/prototype`](examples/prototype). ### Non Goals diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 94794ba..d33ead3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "1.96.0" profile = "default" -components = ["rustc", "rust-analyzer", "rustfmt", "clippy"] +components = ["rust-analyzer", "rustfmt", "clippy"] From 6b5fd4601f4c7ba88b80dbb07e1fa42c7c60a42c Mon Sep 17 00:00:00 2001 From: aaron kelbsch Date: Sat, 27 Jun 2026 22:51:03 +0200 Subject: [PATCH 12/12] fix: lint --- check.sh | 3 ++- features/wrapp-di/src/initiator.rs | 2 +- features/wrapp-di/src/lib.rs | 7 +++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/check.sh b/check.sh index 6349dd1..19b0ec7 100755 --- a/check.sh +++ b/check.sh @@ -86,7 +86,8 @@ launch_step() { local cmd=("${REPLY[@]}") ( s=$(date +%s) - cargo "${cmd[@]}" > "${LOGS[idx]}" 2>&1 + + RUSTDOCFLAGS="-D warnings" cargo "${cmd[@]}" > "${LOGS[idx]}" 2>&1 rc=$? printf '%s' "$(( $(date +%s) - s ))" > "${DURS[idx]}" exit "$rc" diff --git a/features/wrapp-di/src/initiator.rs b/features/wrapp-di/src/initiator.rs index c995049..7d899fc 100644 --- a/features/wrapp-di/src/initiator.rs +++ b/features/wrapp-di/src/initiator.rs @@ -380,7 +380,7 @@ impl DiHandle { pub type DiResponseSender = oneshot::Sender>; pub type DiResponseReceiver = oneshot::Receiver>; -/// Requests between [`DiHandle`] and [`DiInitiator`] +/// Requests between [`DiHandle`] and `DiInitiator` pub enum DiRequest { /// Requires an instance of a specific type Require { diff --git a/features/wrapp-di/src/lib.rs b/features/wrapp-di/src/lib.rs index 1b879fb..2c05c96 100644 --- a/features/wrapp-di/src/lib.rs +++ b/features/wrapp-di/src/lib.rs @@ -18,13 +18,13 @@ //! ### Internal Flow //! //! - All dependencies are registered in the [`DiBuilder`] either as instances or factories. -//! - When [`DiBuilder::build`] is called, the [`DiInitiator`] is created. -//! - The [`DiInitiator`] creates a [`DependencyGraph`] from the registered dependencies, checking +//! - When [`DiBuilder::build`] is called, the `DiInitiator` is created. +//! - The `DiInitiator` creates a [`DependencyGraph`] from the registered dependencies, checking //! for circular dependencies and other issues. //! - Afterwards all [`InstanceFactory`] are called and executed in a [`FuturesUnordered`], first to //! check if they are enabled, and then to create their instances. //! - Results from the [`InstanceFactory`] are stored and become available through a handle to the -//! [`DiInitiator`]. +//! `DiInitiator`. //! - After all factories have been called, all results are collected and stored in the //! [`DiContainer`], which is then returned to the application. //! @@ -32,7 +32,6 @@ //! [`InstanceFactory`]: factories::InstanceFactory //! [`DiBuilder`]: builder::DiBuilder //! [`DiBuilder::build`]: builder::DiBuilder::build -//! [`DiInitiator`]: initiator::DiInitiator //! [`DiContainer`]: container::DiContainer //! [`DependencyGraph`]: dependency_graph::DependencyGraph //! [`Resolver`]: resolver::Resolver