├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── config ├── default.toml └── dev.toml ├── crates └── waiter_codegen │ ├── Cargo.toml │ └── src │ ├── attr_parser.rs │ ├── component │ ├── injector.rs │ ├── mod.rs │ └── type_to_inject.rs │ ├── lib.rs │ └── provider.rs ├── examples ├── 1_get_started.rs ├── 2_modules.rs └── 3_inject_options_list.rs ├── src ├── container.rs ├── deferred.rs ├── inject.rs └── lib.rs └── waiter.iml /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "async-trait" 16 | version = "0.1.77" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" 19 | dependencies = [ 20 | "proc-macro2", 21 | "quote", 22 | "syn 2.0.48", 23 | ] 24 | 25 | [[package]] 26 | name = "base64" 27 | version = "0.21.7" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "2.4.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 36 | dependencies = [ 37 | "serde", 38 | ] 39 | 40 | [[package]] 41 | name = "block-buffer" 42 | version = "0.10.4" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 45 | dependencies = [ 46 | "generic-array", 47 | ] 48 | 49 | [[package]] 50 | name = "cfg-if" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 54 | 55 | [[package]] 56 | name = "config" 57 | version = "0.14.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" 60 | dependencies = [ 61 | "async-trait", 62 | "convert_case", 63 | "json5", 64 | "lazy_static", 65 | "nom", 66 | "pathdiff", 67 | "ron", 68 | "rust-ini", 69 | "serde", 70 | "serde_json", 71 | "toml", 72 | "yaml-rust", 73 | ] 74 | 75 | [[package]] 76 | name = "const-random" 77 | version = "0.1.17" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" 80 | dependencies = [ 81 | "const-random-macro", 82 | ] 83 | 84 | [[package]] 85 | name = "const-random-macro" 86 | version = "0.1.16" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" 89 | dependencies = [ 90 | "getrandom", 91 | "once_cell", 92 | "tiny-keccak", 93 | ] 94 | 95 | [[package]] 96 | name = "convert_case" 97 | version = "0.6.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 100 | dependencies = [ 101 | "unicode-segmentation", 102 | ] 103 | 104 | [[package]] 105 | name = "cpufeatures" 106 | version = "0.2.12" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 109 | dependencies = [ 110 | "libc", 111 | ] 112 | 113 | [[package]] 114 | name = "crunchy" 115 | version = "0.2.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 118 | 119 | [[package]] 120 | name = "crypto-common" 121 | version = "0.1.6" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 124 | dependencies = [ 125 | "generic-array", 126 | "typenum", 127 | ] 128 | 129 | [[package]] 130 | name = "digest" 131 | version = "0.10.7" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 134 | dependencies = [ 135 | "block-buffer", 136 | "crypto-common", 137 | ] 138 | 139 | [[package]] 140 | name = "dlv-list" 141 | version = "0.5.2" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" 144 | dependencies = [ 145 | "const-random", 146 | ] 147 | 148 | [[package]] 149 | name = "equivalent" 150 | version = "1.0.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 153 | 154 | [[package]] 155 | name = "generic-array" 156 | version = "0.14.7" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 159 | dependencies = [ 160 | "typenum", 161 | "version_check", 162 | ] 163 | 164 | [[package]] 165 | name = "getrandom" 166 | version = "0.2.12" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 169 | dependencies = [ 170 | "cfg-if", 171 | "libc", 172 | "wasi", 173 | ] 174 | 175 | [[package]] 176 | name = "hashbrown" 177 | version = "0.13.2" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 180 | 181 | [[package]] 182 | name = "hashbrown" 183 | version = "0.14.3" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 186 | 187 | [[package]] 188 | name = "indexmap" 189 | version = "2.2.2" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" 192 | dependencies = [ 193 | "equivalent", 194 | "hashbrown 0.14.3", 195 | ] 196 | 197 | [[package]] 198 | name = "itoa" 199 | version = "0.4.6" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 202 | 203 | [[package]] 204 | name = "json5" 205 | version = "0.4.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" 208 | dependencies = [ 209 | "pest", 210 | "pest_derive", 211 | "serde", 212 | ] 213 | 214 | [[package]] 215 | name = "lazy_static" 216 | version = "1.4.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 219 | 220 | [[package]] 221 | name = "libc" 222 | version = "0.2.153" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 225 | 226 | [[package]] 227 | name = "linked-hash-map" 228 | version = "0.5.3" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" 231 | 232 | [[package]] 233 | name = "log" 234 | version = "0.4.20" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 237 | 238 | [[package]] 239 | name = "memchr" 240 | version = "2.7.1" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 243 | 244 | [[package]] 245 | name = "minimal-lexical" 246 | version = "0.2.1" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 249 | 250 | [[package]] 251 | name = "nom" 252 | version = "7.1.3" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 255 | dependencies = [ 256 | "memchr", 257 | "minimal-lexical", 258 | ] 259 | 260 | [[package]] 261 | name = "once_cell" 262 | version = "1.19.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 265 | 266 | [[package]] 267 | name = "ordered-multimap" 268 | version = "0.6.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" 271 | dependencies = [ 272 | "dlv-list", 273 | "hashbrown 0.13.2", 274 | ] 275 | 276 | [[package]] 277 | name = "pathdiff" 278 | version = "0.2.1" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" 281 | 282 | [[package]] 283 | name = "pest" 284 | version = "2.7.7" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" 287 | dependencies = [ 288 | "memchr", 289 | "thiserror", 290 | "ucd-trie", 291 | ] 292 | 293 | [[package]] 294 | name = "pest_derive" 295 | version = "2.7.7" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" 298 | dependencies = [ 299 | "pest", 300 | "pest_generator", 301 | ] 302 | 303 | [[package]] 304 | name = "pest_generator" 305 | version = "2.7.7" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" 308 | dependencies = [ 309 | "pest", 310 | "pest_meta", 311 | "proc-macro2", 312 | "quote", 313 | "syn 2.0.48", 314 | ] 315 | 316 | [[package]] 317 | name = "pest_meta" 318 | version = "2.7.7" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" 321 | dependencies = [ 322 | "once_cell", 323 | "pest", 324 | "sha2", 325 | ] 326 | 327 | [[package]] 328 | name = "proc-macro2" 329 | version = "1.0.78" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 332 | dependencies = [ 333 | "unicode-ident", 334 | ] 335 | 336 | [[package]] 337 | name = "quote" 338 | version = "1.0.35" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 341 | dependencies = [ 342 | "proc-macro2", 343 | ] 344 | 345 | [[package]] 346 | name = "regex" 347 | version = "1.10.3" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 350 | dependencies = [ 351 | "aho-corasick", 352 | "memchr", 353 | "regex-automata", 354 | "regex-syntax", 355 | ] 356 | 357 | [[package]] 358 | name = "regex-automata" 359 | version = "0.4.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" 362 | dependencies = [ 363 | "aho-corasick", 364 | "memchr", 365 | "regex-syntax", 366 | ] 367 | 368 | [[package]] 369 | name = "regex-syntax" 370 | version = "0.8.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 373 | 374 | [[package]] 375 | name = "ron" 376 | version = "0.8.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" 379 | dependencies = [ 380 | "base64", 381 | "bitflags", 382 | "serde", 383 | "serde_derive", 384 | ] 385 | 386 | [[package]] 387 | name = "rust-ini" 388 | version = "0.19.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" 391 | dependencies = [ 392 | "cfg-if", 393 | "ordered-multimap", 394 | ] 395 | 396 | [[package]] 397 | name = "ryu" 398 | version = "1.0.5" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 401 | 402 | [[package]] 403 | name = "serde" 404 | version = "1.0.196" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" 407 | dependencies = [ 408 | "serde_derive", 409 | ] 410 | 411 | [[package]] 412 | name = "serde_derive" 413 | version = "1.0.196" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" 416 | dependencies = [ 417 | "proc-macro2", 418 | "quote", 419 | "syn 2.0.48", 420 | ] 421 | 422 | [[package]] 423 | name = "serde_json" 424 | version = "1.0.57" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" 427 | dependencies = [ 428 | "itoa", 429 | "ryu", 430 | "serde", 431 | ] 432 | 433 | [[package]] 434 | name = "serde_spanned" 435 | version = "0.6.5" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 438 | dependencies = [ 439 | "serde", 440 | ] 441 | 442 | [[package]] 443 | name = "sha2" 444 | version = "0.10.8" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 447 | dependencies = [ 448 | "cfg-if", 449 | "cpufeatures", 450 | "digest", 451 | ] 452 | 453 | [[package]] 454 | name = "syn" 455 | version = "1.0.48" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" 458 | dependencies = [ 459 | "proc-macro2", 460 | "quote", 461 | "unicode-xid", 462 | ] 463 | 464 | [[package]] 465 | name = "syn" 466 | version = "2.0.48" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 469 | dependencies = [ 470 | "proc-macro2", 471 | "quote", 472 | "unicode-ident", 473 | ] 474 | 475 | [[package]] 476 | name = "thiserror" 477 | version = "1.0.56" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" 480 | dependencies = [ 481 | "thiserror-impl", 482 | ] 483 | 484 | [[package]] 485 | name = "thiserror-impl" 486 | version = "1.0.56" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" 489 | dependencies = [ 490 | "proc-macro2", 491 | "quote", 492 | "syn 2.0.48", 493 | ] 494 | 495 | [[package]] 496 | name = "tiny-keccak" 497 | version = "2.0.2" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 500 | dependencies = [ 501 | "crunchy", 502 | ] 503 | 504 | [[package]] 505 | name = "toml" 506 | version = "0.8.9" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325" 509 | dependencies = [ 510 | "serde", 511 | "serde_spanned", 512 | "toml_datetime", 513 | "toml_edit", 514 | ] 515 | 516 | [[package]] 517 | name = "toml_datetime" 518 | version = "0.6.5" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 521 | dependencies = [ 522 | "serde", 523 | ] 524 | 525 | [[package]] 526 | name = "toml_edit" 527 | version = "0.21.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" 530 | dependencies = [ 531 | "indexmap", 532 | "serde", 533 | "serde_spanned", 534 | "toml_datetime", 535 | "winnow", 536 | ] 537 | 538 | [[package]] 539 | name = "typenum" 540 | version = "1.17.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 543 | 544 | [[package]] 545 | name = "ucd-trie" 546 | version = "0.1.6" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" 549 | 550 | [[package]] 551 | name = "unicode-ident" 552 | version = "1.0.12" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 555 | 556 | [[package]] 557 | name = "unicode-segmentation" 558 | version = "1.10.1" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 561 | 562 | [[package]] 563 | name = "unicode-xid" 564 | version = "0.2.1" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 567 | 568 | [[package]] 569 | name = "version_check" 570 | version = "0.9.4" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 573 | 574 | [[package]] 575 | name = "waiter_codegen" 576 | version = "1.6.5" 577 | dependencies = [ 578 | "proc-macro2", 579 | "quote", 580 | "regex", 581 | "syn 1.0.48", 582 | ] 583 | 584 | [[package]] 585 | name = "waiter_di" 586 | version = "1.6.6" 587 | dependencies = [ 588 | "config", 589 | "lazy_static", 590 | "log", 591 | "regex", 592 | "serde", 593 | "waiter_codegen", 594 | ] 595 | 596 | [[package]] 597 | name = "wasi" 598 | version = "0.11.0+wasi-snapshot-preview1" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 601 | 602 | [[package]] 603 | name = "winnow" 604 | version = "0.5.37" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" 607 | dependencies = [ 608 | "memchr", 609 | ] 610 | 611 | [[package]] 612 | name = "yaml-rust" 613 | version = "0.4.4" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" 616 | dependencies = [ 617 | "linked-hash-map", 618 | ] 619 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "waiter_di" 3 | version = "1.6.6" 4 | edition = "2018" 5 | description = "Dependency injection" 6 | keywords = [ "dependency-injection", "inversion-of-control", "di", "ioc"] 7 | repository = "https://github.com/dmitryb-dev/waiter" 8 | license = "MIT" 9 | readme = "README.md" 10 | authors = [ "dmitryb.dev@gmail.com" ] 11 | 12 | [workspace] 13 | members = [ "crates/*" ] 14 | 15 | [dependencies] 16 | waiter_codegen = { path = "crates/waiter_codegen", version = "1.6.6" } 17 | config = "0.14.0" 18 | serde = { version = "1.0.196", features = [ "derive" ] } 19 | regex = "1.10.3" 20 | lazy_static = "1.4.0" 21 | log = "0.4.20" 22 | 23 | [features] 24 | async = [ "waiter_codegen/async" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dependency injection for Rust 2 | 3 | How to use: 4 | 5 | `Cargo.toml`: 6 | ```toml 7 | waiter_di = "1.6.5" 8 | ``` 9 | `lib.rs` or any other file, that uses library: 10 | ```rust 11 | use waiter_di::*; 12 | ``` 13 | 14 | See 15 | [examples/1_get_started.rs](https://github.com/dmitryb-dev/waiter/blob/master/examples/1_get_started.rs) 16 | for minimal example of usage. 17 | 18 | See 19 | [examples/2_modules.rs](https://github.com/dmitryb-dev/waiter/blob/master/examples/2_modules.rs) 20 | for example with modules and constructors. 21 | 22 | See 23 | [examples/3_inject_options_list.rs](https://github.com/dmitryb-dev/waiter/blob/master/examples/3_inject_options_list.rs) 24 | for the demo of all available injection options. 25 | 26 | ## How to use 27 | 28 | Annotate structure with `#[component]` 29 | 30 | ```rust 31 | #[component] 32 | struct Comp {} 33 | ``` 34 | 35 | Annotate impl blocks with `#[provides]` 36 | 37 | ```rust 38 | #[provides] 39 | impl Interface for Comp {} 40 | ``` 41 | 42 | Create a container: 43 | 44 | ```rust 45 | fn main() { 46 | let mut container = Container::::new(); 47 | } 48 | ``` 49 | 50 | Get dependency ref: 51 | 52 | ```rust 53 | fn main() { 54 | let comp = Provider::::get(&mut container); 55 | } 56 | ``` 57 | 58 | ## Inject references 59 | 60 | For Rc: 61 | 62 | ```rust 63 | #[component] 64 | struct Dependency; 65 | 66 | #[component] 67 | struct Comp { 68 | dependency_rc: Rc 69 | } 70 | 71 | fn main() { 72 | let mut container = Container::::new(); 73 | Provider::::get(&mut container); 74 | } 75 | ``` 76 | 77 | 78 | to use `Arc` instead of `Rc` you need to add `async` feature in cargo: 79 | ```toml 80 | waiter_di = { version = "...", features = [ "async" ] } 81 | ``` 82 | 83 | Also, you can use `waiter_di::Wrc` type that will be compiled to `Rc` or `Arc` depending on `async` feature. 84 | 85 | To create new struct instead of getting reference: 86 | 87 | ```rust 88 | #[component] 89 | struct Comp { 90 | dependency: Dependency, 91 | dependency_box: Box 92 | } 93 | 94 | fn main() { 95 | let mut container = Container::::new(); 96 | Provider::::create(&mut container); 97 | Provider::::create_boxed(&mut container); 98 | } 99 | ``` 100 | 101 | ## Properties 102 | 103 | It uses `config` crate under the hood, for example it tries to find `float_prop` 104 | in args as `--float_prop `, if not found it tries to find it in environment variables, 105 | after that tries `config/{profile}.toml`, after that `config/default.toml` 106 | 107 | ```rust 108 | #[derive(Debug, Deserialize)] 109 | struct ConfigObject { 110 | i32_prop: i32 111 | } 112 | 113 | #[component] 114 | struct Comp { 115 | config: Config, 116 | #[prop("int")] int_prop: usize, 117 | #[prop("int")] int_prop_opt: Option, 118 | #[prop("int" = 42)] int_prop_with_default_value: usize, 119 | float_prop: f32, 120 | #[prop] config_object: ConfigObject 121 | } 122 | ``` 123 | 124 | ## Dependency cycle 125 | 126 | Use Deferred type: 127 | 128 | ```rust 129 | #[component] 130 | struct Comp { 131 | dependency_def: Deferred, 132 | dependency_def_rc: Deferred>, 133 | dependency_def_box: Deferred> 134 | } 135 | ``` 136 | 137 | ## Profiles 138 | 139 | You can use predefined profiles from `waiter_di::profile" or create custom: 140 | 141 | ```rust 142 | struct CustomProfile; 143 | 144 | #[provides(profiles::Dev, CustomProfile)] 145 | impl Interface for Comp {} 146 | 147 | fn main() { 148 | let mut container = Container::::new(); 149 | let mut container = Container::::new(); 150 | let mut container = Container::::new(); 151 | } 152 | ``` 153 | 154 | ## Get profile from args, environment or `config/default.toml` 155 | 156 | Just define property named `profile` as `--profile ` arg, `profile` env variable or 157 | `profile` property in `config/default.toml` and use `inject!` macro: 158 | 159 | ```rust 160 | fn main() { 161 | let comp = inject!(Comp: profiles::Default, profiles::Dev); 162 | } 163 | ``` 164 | 165 | `inject!` macro can't be used for several components, so it's recommended to use it with modules: 166 | 167 | ```rust 168 | #[module] 169 | struct SomeModule { 170 | component: Component 171 | } 172 | #[module] 173 | struct RootModule { 174 | some_module: SomeModule 175 | } 176 | fn main() { 177 | let root_module = inject!(RootModule: profiles::Default, profiles::Dev); 178 | } 179 | ``` 180 | 181 | In this case `#[module]` is just a synonym for `#[component]` 182 | 183 | ## Factory functions: 184 | 185 | If you can't use `#[component]` annotation, use factory function instead: 186 | 187 | ```rust 188 | #[provides] 189 | fn create_dependency(bool_prop: bool) -> Dependency { 190 | Dependency { prop: bool_prop } 191 | } 192 | ``` 193 | 194 | To use it like a constructor, use it with `#[component]` on impl block: 195 | 196 | ```rust 197 | struct Comp(); 198 | 199 | #[component] 200 | impl Comp { 201 | #[provides] 202 | fn new() -> Self { 203 | Self() 204 | } 205 | } 206 | ``` 207 | 208 | `Deferred` args in factory functions is unsupported. In the rest it can accept 209 | the same arg types as `#[component]`. 210 | 211 | External types isn't supported for factory functions: 212 | 213 | ```rust 214 | #[provides] // won't compile 215 | fn create_external_type_dependency() -> HashMap { 216 | HashMap::new() 217 | } 218 | ``` 219 | 220 | So you need to create crate-local wrapper: 221 | 222 | ```rust 223 | struct Wrapper(HashMap); 224 | 225 | #[provides] 226 | fn create_external_type_dependency() -> Wrapper { 227 | Wrapper(HashMap::new()) 228 | } 229 | ``` 230 | 231 | For convenience, you can use `#[wrapper]` attribute to implement Deref automatically: 232 | 233 | ```rust 234 | #[wrapper] 235 | struct HashMap(std::collections::HashMap); 236 | 237 | #[provides] 238 | fn create_external_type_dependency() -> HashMap { 239 | return HashMap(std::collections::HashMap::::new()); 240 | } 241 | ``` -------------------------------------------------------------------------------- /config/default.toml: -------------------------------------------------------------------------------- 1 | profile = "dev" 2 | 3 | int_v = 3 4 | str_prop = "str" 5 | prop = "prop_value" 6 | i32_prop = 7 -------------------------------------------------------------------------------- /config/dev.toml: -------------------------------------------------------------------------------- 1 | int_v = 74 2 | str_prop = "dev str" -------------------------------------------------------------------------------- /crates/waiter_codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "waiter_codegen" 3 | version = "1.6.6" 4 | edition = "2018" 5 | description = "Macro for Waiter DI" 6 | repository = "https://github.com/dmitryb-dev/waiter" 7 | license = "MIT" 8 | readme = "../../README.md" 9 | authors = ["dmitryb.dev@gmail.com"] 10 | 11 | [dependencies] 12 | quote = "1.0.7" 13 | regex = "1.3.9" 14 | proc-macro2 = "1.0.24" 15 | 16 | [dependencies.syn] 17 | version = "1.0.48" 18 | features = ["full"] 19 | 20 | [lib] 21 | proc-macro = true 22 | 23 | [features] 24 | async = [] -------------------------------------------------------------------------------- /crates/waiter_codegen/src/attr_parser.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | use proc_macro2::TokenStream as TokenStream2; 4 | use quote::ToTokens; 5 | use syn::{Attribute, Error, Path}; 6 | use syn::{ExprAssign, LitStr}; 7 | use syn::parse::Parser; 8 | use syn::parse_macro_input::parse; 9 | use syn::punctuated::Punctuated; 10 | use syn::token::Comma; 11 | 12 | pub(crate) struct ProvidesAttr { 13 | pub profiles: Vec, 14 | } 15 | 16 | pub(crate) fn parse_provides_attr(attr: TokenStream) -> Result { 17 | let profiles_syn = >::parse_terminated.parse(attr)?; 18 | 19 | let profiles: Vec = profiles_syn 20 | .iter().cloned() 21 | .collect(); 22 | 23 | Ok(ProvidesAttr { profiles }) 24 | } 25 | 26 | 27 | #[derive(Clone)] 28 | pub(crate) struct PropAttr { 29 | pub(crate) name: Option, 30 | pub(crate) default_value: Option, 31 | } 32 | 33 | pub(crate) fn parse_prop_attr(attr: &Attribute) -> Result { 34 | if attr.tokens.is_empty() { 35 | Ok(PropAttr { name: None, default_value: None }) 36 | } else { 37 | attr.parse_args::() 38 | .and_then(|with_default| { 39 | let name = parse::(with_default.left.to_token_stream().into())?; 40 | 41 | Ok(PropAttr { 42 | name: Some(name.value()), 43 | default_value: Some(with_default.right.to_token_stream()), 44 | }) 45 | }) 46 | .or_else(|_| { 47 | Ok(PropAttr { 48 | name: Some(attr.parse_args::()?.value()), 49 | default_value: None, 50 | }) 51 | }) 52 | } 53 | } -------------------------------------------------------------------------------- /crates/waiter_codegen/src/component/injector.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | use quote::ToTokens; 3 | use syn::{Error, Ident, PathArguments}; 4 | use syn::spanned::Spanned; 5 | 6 | use crate::component::type_to_inject::TypeToInject; 7 | 8 | pub(crate) trait Injector { 9 | fn generate_inject_code( 10 | &self, 11 | to_inject: &TypeToInject, 12 | container: &Ident, 13 | ) -> Option; 14 | } 15 | 16 | pub(crate) struct WrcInjector; 17 | 18 | impl Injector for WrcInjector { 19 | fn generate_inject_code( 20 | &self, 21 | to_inject: &TypeToInject, 22 | container: &Ident, 23 | ) -> Option { 24 | #[cfg(feature = "async")] 25 | const RC_FULL_TYPE: &str = "std :: sync :: Arc <"; 26 | #[cfg(not(feature = "async"))] 27 | const RC_FULL_TYPE: &str = "std :: rc :: Rc <"; 28 | 29 | #[cfg(feature = "async")] 30 | const RC_SHORT_TYPE: &str = "Arc <"; 31 | #[cfg(not(feature = "async"))] 32 | const RC_SHORT_TYPE: &str = "Rc <"; 33 | 34 | let referenced_type_opt = if to_inject.type_name.starts_with("waiter_di :: Wrc <") 35 | || to_inject.type_name.starts_with(RC_FULL_TYPE) { 36 | Some(get_type_arg(&to_inject.type_path.segments[2].arguments)) 37 | } else if to_inject.type_name.starts_with("Wrc <") || 38 | to_inject.type_name.starts_with(RC_SHORT_TYPE) { 39 | Some(get_type_arg(&to_inject.type_path.segments[0].arguments)) 40 | } else { 41 | None 42 | }; 43 | 44 | referenced_type_opt.map(|ref_type| quote::quote! { 45 | waiter_di::Provider::<#ref_type>::get(#container) 46 | }) 47 | } 48 | } 49 | 50 | 51 | pub(crate) struct BoxInjector; 52 | 53 | impl Injector for BoxInjector { 54 | fn generate_inject_code( 55 | &self, 56 | to_inject: &TypeToInject, 57 | container: &Ident, 58 | ) -> Option { 59 | if to_inject.type_name.starts_with("Box <") { 60 | let referenced_type = get_type_arg(&to_inject.type_path.segments[0].arguments); 61 | return Some(quote::quote! { 62 | Box::new(waiter_di::Provider::<#referenced_type>::create(#container)) 63 | }); 64 | } 65 | 66 | None 67 | } 68 | } 69 | 70 | 71 | pub(crate) struct DeferredInjector; 72 | 73 | impl Injector for DeferredInjector { 74 | fn generate_inject_code( 75 | &self, 76 | to_inject: &TypeToInject, 77 | _container: &Ident, 78 | ) -> Option { 79 | let referenced_type_opt = if to_inject.type_name.starts_with("waiter_di :: Deferred <") { 80 | Some(get_type_arg(&to_inject.type_path.segments[1].arguments)) 81 | } else if to_inject.type_name.starts_with("Deferred <") { 82 | Some(get_type_arg(&to_inject.type_path.segments[0].arguments)) 83 | } else { 84 | None 85 | }; 86 | 87 | referenced_type_opt.map(|ref_type| quote::quote! { 88 | waiter_di::Deferred::<#ref_type>::new() 89 | }) 90 | } 91 | } 92 | 93 | 94 | pub(crate) struct ConfigInjector; 95 | 96 | impl Injector for ConfigInjector { 97 | fn generate_inject_code( 98 | &self, 99 | to_inject: &TypeToInject, 100 | container: &Ident, 101 | ) -> Option { 102 | if to_inject.type_name == *"Config" 103 | || to_inject.type_name == *"config :: Config" { 104 | return Some(quote::quote! { #container.config.clone() }); 105 | } 106 | 107 | None 108 | } 109 | } 110 | 111 | 112 | pub(crate) struct PropInjector; 113 | 114 | impl Injector for PropInjector { 115 | fn generate_inject_code( 116 | &self, 117 | to_inject: &TypeToInject, 118 | container: &Ident, 119 | ) -> Option { 120 | let (prop_name_opt, default_value_code) = if to_inject.prop_attr.is_some() { 121 | let prop_attr = to_inject.prop_attr.clone().unwrap(); 122 | let prop_name_opt = prop_attr.name.clone() 123 | .or(to_inject.arg_name.clone() 124 | .map(|name_ts| name_ts.to_string()) 125 | ); 126 | 127 | let default_value_code = prop_attr.default_value.clone() 128 | .map(|default_value| quote::quote! { .or_else(|| Some(#default_value)) }) 129 | .unwrap_or_default(); 130 | 131 | (prop_name_opt, default_value_code) 132 | } else { 133 | (to_inject.arg_name.clone() 134 | .map(|name_ts| name_ts.to_string()), 135 | quote::quote! {} 136 | ) 137 | }; 138 | 139 | let base_types_extracted = prop_name_opt.and_then(|prop_name_tokens| { 140 | let prop_name = prop_name_tokens.to_string(); 141 | 142 | let (type_path, opt_extractor) = if to_inject.type_name.starts_with("Option <") { 143 | (get_type_arg(&to_inject.type_path.segments[0].arguments), 144 | quote::quote! { } 145 | ) 146 | } else { 147 | (to_inject.type_path.to_token_stream(), 148 | quote::quote! { .expect(format!("Property \"{}\" not found", #prop_name).as_str()) } 149 | ) 150 | }; 151 | let type_name = type_path.to_string(); 152 | 153 | let mut extractors: Vec> = Vec::new(); 154 | extractors.push(Box::new(SafeCastPropExtractor)); 155 | extractors.push(Box::new(UnsafeCastPropExtractor)); 156 | extractors.push(Box::new(AsCastPropExtractor)); 157 | 158 | extractors.iter() 159 | .find_map(|extractor| extractor 160 | .generate_extract_method(type_name.clone()) 161 | .map(|extract_method| { 162 | let convert_code = extractor.generate_convert_code( 163 | type_path.clone(), 164 | type_name.clone(), 165 | prop_name.to_string(), 166 | quote::quote! { value }, 167 | ); 168 | 169 | quote::quote! { 170 | #container.config.#extract_method(#prop_name) 171 | .map(|value| #convert_code) 172 | .ok() 173 | #default_value_code 174 | #opt_extractor 175 | } 176 | }) 177 | ) 178 | }); 179 | 180 | base_types_extracted 181 | .or_else(|| { 182 | if to_inject.prop_attr.is_some() { 183 | let type_name = to_inject.type_name.clone(); 184 | let type_path = to_inject.type_path.clone(); 185 | Some(quote::quote! { 186 | #container.config.clone().try_deserialize::<#type_path>() 187 | .expect(format!("Can't parse config as '{}'", #type_name).as_str()) 188 | }) 189 | } else { 190 | None 191 | } 192 | }) 193 | } 194 | } 195 | 196 | trait PropExtractor { 197 | fn generate_extract_method(&self, type_name: String) -> Option; 198 | fn generate_convert_code( 199 | &self, 200 | _type_path: TokenStream2, 201 | _type_name: String, 202 | _prop_name: String, 203 | extract_code: TokenStream2, 204 | ) -> TokenStream2 { 205 | extract_code 206 | } 207 | } 208 | 209 | struct SafeCastPropExtractor; 210 | 211 | impl PropExtractor for SafeCastPropExtractor { 212 | fn generate_extract_method(&self, type_name: String) -> Option { 213 | match type_name.as_str() { 214 | "i128" | "u128" => Some(quote::quote! { get_int }), 215 | _ => None 216 | } 217 | } 218 | 219 | fn generate_convert_code( 220 | &self, 221 | type_path: TokenStream2, 222 | _type_name: String, 223 | _prop_name: String, 224 | value: TokenStream2, 225 | ) -> TokenStream2 { 226 | quote::quote! { #type_path::from(#value) } 227 | } 228 | } 229 | 230 | struct UnsafeCastPropExtractor; 231 | 232 | impl PropExtractor for UnsafeCastPropExtractor { 233 | fn generate_extract_method(&self, type_name: String) -> Option { 234 | match type_name.as_str() { 235 | "i8" | "i16" | "i32" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" => 236 | Some(quote::quote! { get_int }), 237 | _ => None 238 | } 239 | } 240 | 241 | fn generate_convert_code( 242 | &self, 243 | type_path: TokenStream2, 244 | type_name: String, 245 | prop_name: String, 246 | value: TokenStream2, 247 | ) -> TokenStream2 { 248 | quote::quote! { 249 | <#type_path as std::convert::TryFrom>::try_from(#value) 250 | .expect(format!("Can't parse prop '{}' as '{}'", #prop_name, #type_name).as_str()) 251 | } 252 | } 253 | } 254 | 255 | struct AsCastPropExtractor; 256 | 257 | impl PropExtractor for AsCastPropExtractor { 258 | fn generate_extract_method(&self, type_name: String) -> Option { 259 | match type_name.as_str() { 260 | "i64" => Some(quote::quote! { get_int }), 261 | "f64" | "f32" => Some(quote::quote! { get_float }), 262 | "String" => Some(quote::quote! { get_string }), 263 | "bool" => Some(quote::quote! { get_bool }), 264 | _ => None 265 | } 266 | } 267 | 268 | fn generate_convert_code( 269 | &self, 270 | type_path: TokenStream2, 271 | _type_name: String, 272 | _prop_name: String, 273 | value: TokenStream2, 274 | ) -> TokenStream2 { 275 | quote::quote! { 276 | #value as #type_path 277 | } 278 | } 279 | } 280 | 281 | fn get_type_arg(arguments: &PathArguments) -> TokenStream2 { 282 | if let PathArguments::AngleBracketed(ab) = arguments { 283 | ab.args.to_token_stream() 284 | } else { 285 | Error::new(arguments.span(), "Unsupported type argument").to_compile_error() 286 | } 287 | } -------------------------------------------------------------------------------- /crates/waiter_codegen/src/component/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | use proc_macro2::{Span, TokenStream as TokenStream2}; 4 | use quote::ToTokens; 5 | use syn::{Error, Expr, Field, Fields, GenericArgument, Ident, ImplItem, ItemFn, ItemImpl, ItemStruct, PathArguments, Type}; 6 | use syn::spanned::Spanned; 7 | 8 | use crate::attr_parser::parse_provides_attr; 9 | use crate::component::injector::{BoxInjector, ConfigInjector, DeferredInjector, Injector, 10 | PropInjector, WrcInjector}; 11 | use crate::component::type_to_inject::TypeToInject; 12 | use crate::provider::generate_component_provider_impl_fn; 13 | 14 | pub(crate) mod injector; 15 | pub(crate) mod type_to_inject; 16 | 17 | pub(crate) fn generate_component_for_impl(comp_impl: ItemImpl) -> Result { 18 | for item in &comp_impl.items { 19 | if let ImplItem::Method(method) = item { 20 | let provides_attr = method.attrs.iter() 21 | .find(|attr| attr.path.to_token_stream().to_string() == *"provides"); 22 | 23 | if provides_attr.is_some() { 24 | let provides_attr = provides_attr.unwrap(); 25 | let provides = if provides_attr.tokens.is_empty() { 26 | parse_provides_attr(TokenStream::new())? 27 | } else { 28 | parse_provides_attr( 29 | provides_attr.parse_args::()? 30 | .to_token_stream() 31 | .into() 32 | )? 33 | }; 34 | 35 | let mut fn_tokens = method.sig.to_token_stream(); 36 | fn_tokens.extend(method.block.to_token_stream()); 37 | let item_fn = syn::parse::(fn_tokens.into())?; 38 | 39 | return generate_component_provider_impl_fn( 40 | provides, 41 | item_fn, 42 | comp_impl.self_ty.to_token_stream(), 43 | ); 44 | } 45 | } 46 | } 47 | Err(Error::new(comp_impl.span(), "Constructor with #[provides] attribute is not found")) 48 | } 49 | 50 | pub(crate) fn generate_component_for_struct(component: ItemStruct) -> Result { 51 | let comp_name = &component.ident; 52 | let comp_generics = &component.generics; 53 | 54 | let dependencies_code = generate_dependencies_create_code( 55 | component.fields.iter() 56 | .map(TypeToInject::from_field) 57 | .collect::, _>>()? 58 | ); 59 | let deferred_dependencies_code = generate_deferred_dependencies_code( 60 | component.fields.iter().collect() 61 | )?; 62 | 63 | let (factory_code, deferred_inject_code) = match component.fields { 64 | Fields::Named(fields) => ( 65 | generate_inject_dependencies_named(fields.named.iter().collect()), 66 | generate_inject_deferred(fields.named.iter().collect(), false) 67 | ), 68 | Fields::Unnamed(fields) => ( 69 | generate_inject_dependencies_tuple(fields.unnamed.len()), 70 | generate_inject_deferred(fields.unnamed.iter().collect(), true) 71 | ), 72 | Fields::Unit => ( 73 | generate_inject_dependencies_tuple(0), 74 | generate_inject_deferred(vec!(), true) 75 | ), 76 | }; 77 | 78 | 79 | let result = quote::quote! { 80 | impl #comp_generics waiter_di::Component for #comp_name #comp_generics { 81 | fn __waiter_create

(container: &mut waiter_di::Container

) -> Self { 82 | #dependencies_code 83 | return #comp_name #factory_code; 84 | } 85 | fn __waiter_inject_deferred

(container: &mut waiter_di::Container

, component: &Self) { 86 | #deferred_dependencies_code 87 | #deferred_inject_code 88 | } 89 | } 90 | }; 91 | 92 | Ok(result.into()) 93 | } 94 | 95 | pub(crate) fn generate_inject_dependencies_tuple(dep_number: usize) -> TokenStream2 { 96 | let dependencies: Vec = (0..dep_number) 97 | .map(|i| Ident::new(format!("dep_{}", i).as_str(), Span::call_site())) 98 | .collect(); 99 | 100 | quote::quote! { 101 | (#(#dependencies),*) 102 | } 103 | } 104 | 105 | fn generate_inject_dependencies_named(fields: Vec<&Field>) -> TokenStream2 { 106 | let dependencies: Vec = (0..fields.len()) 107 | .map(|i| Ident::new(format!("dep_{}", i).as_str(), Span::call_site())) 108 | .collect(); 109 | 110 | let field_names: Vec<&Ident> = fields.iter() 111 | .map(|f| f.ident.as_ref().unwrap()) 112 | .collect(); 113 | 114 | quote::quote! { 115 | {#(#field_names: #dependencies),*} 116 | } 117 | } 118 | 119 | fn generate_inject_deferred(fields: Vec<&Field>, is_tuple: bool) -> TokenStream2 { 120 | let dependencies_inject: Vec = fields.iter() 121 | .enumerate() 122 | .filter(|(_, f)| { 123 | if let Type::Path(path_type) = &f.ty { 124 | let ptr_type = path_type.path.to_token_stream().to_string(); 125 | 126 | return ptr_type.starts_with("waiter :: Deferred <") || ptr_type.starts_with("Deferred <"); 127 | } 128 | false 129 | }) 130 | .map(|(i, f)| if is_tuple { 131 | (i, Ident::new(format!("{}", i).as_str(), Span::call_site())) 132 | } else { 133 | (i, f.ident.clone().unwrap()) 134 | }) 135 | .map(|(i, field_name)| { 136 | let dependency = Ident::new(format!("dep_{}", i).as_str(), Span::call_site()); 137 | quote::quote! { #field_name.init(#dependency); } 138 | }) 139 | .collect(); 140 | 141 | quote::quote! { 142 | #(component.#dependencies_inject)* 143 | } 144 | } 145 | 146 | pub(crate) fn generate_dependencies_create_code(args: Vec) -> TokenStream2 { 147 | let dep_code_list: Vec = args.into_iter() 148 | .enumerate() 149 | .map(|(i, arg)| generate_dependency_create_code(arg, i)).collect(); 150 | 151 | quote::quote! { 152 | #(#dep_code_list)* 153 | } 154 | } 155 | 156 | fn generate_dependency_create_code(to_inject: TypeToInject, pos: usize) -> TokenStream2 { 157 | let dep_var_name = quote::format_ident!("dep_{}", pos); 158 | let type_path = to_inject.type_path.clone(); 159 | 160 | let injectors: Vec> = vec![ 161 | Box::new(DeferredInjector), 162 | Box::new(WrcInjector), 163 | Box::new(BoxInjector), 164 | Box::new(ConfigInjector), 165 | Box::new(PropInjector), 166 | ]; 167 | 168 | let inject_code = injectors.iter() 169 | .find_map(|injector| injector.generate_inject_code( 170 | &to_inject, 171 | &Ident::new("container", Span::call_site()), 172 | )) 173 | .unwrap_or_else(|| quote::quote! { waiter_di::Provider::<#type_path>::create(container) }); 174 | 175 | quote::quote! { 176 | let #dep_var_name = #inject_code; 177 | } 178 | } 179 | 180 | fn generate_deferred_dependencies_code(fields: Vec<&Field>) -> Result { 181 | let dep_code_list: Vec = fields.iter() 182 | .enumerate() 183 | .map(|(i, f)| { 184 | if let Type::Path(path_type) = &f.ty { 185 | let ptr_type = path_type.path.to_token_stream().to_string(); 186 | 187 | let mut generic_args = None; 188 | if ptr_type.starts_with("waiter :: Deferred <") { 189 | if let PathArguments::AngleBracketed(typ) = &path_type.path.segments[1].arguments { 190 | generic_args = Some(&typ.args); 191 | } 192 | } else if ptr_type.starts_with("Deferred <") { 193 | if let PathArguments::AngleBracketed(typ) = &path_type.path.segments[0].arguments { 194 | generic_args = Some(&typ.args); 195 | } 196 | } 197 | if generic_args.is_some() { 198 | if let GenericArgument::Type(typ) = generic_args.unwrap() 199 | .first() 200 | .expect("Expected arg for Deferred type") 201 | { 202 | return (i, Some(typ)); 203 | } 204 | } 205 | } 206 | (i, None) 207 | }) 208 | .filter(|(_, opt_arg)| opt_arg.is_some()) 209 | .map(|(i, opt_arg)| (i, opt_arg.unwrap())) 210 | .map(|(i, t)| TypeToInject::from_type(t) 211 | .map(|to_inject| generate_dependency_create_code(to_inject, i)) 212 | ) 213 | .collect::, Error>>()?; 214 | 215 | Ok(quote::quote! { 216 | #(#dep_code_list)* 217 | }) 218 | } -------------------------------------------------------------------------------- /crates/waiter_codegen/src/component/type_to_inject.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | use quote::ToTokens; 3 | use syn::{Attribute, Error, Field, FnArg, Pat, Path, Type}; 4 | use syn::spanned::Spanned; 5 | 6 | use crate::attr_parser::{parse_prop_attr, PropAttr}; 7 | 8 | #[derive(Clone)] 9 | pub(crate) struct TypeToInject { 10 | pub(crate) type_name: String, 11 | pub(crate) type_path: Path, 12 | pub(crate) arg_name: Option, 13 | pub(crate) prop_attr: Option, 14 | } 15 | 16 | 17 | impl TypeToInject { 18 | pub(crate) fn from_type(type_: &Type) -> Result { 19 | Ok(Self { 20 | type_name: type_.to_token_stream().to_string(), 21 | type_path: Self::parse_path(type_)?, 22 | arg_name: None, 23 | prop_attr: None, 24 | }) 25 | } 26 | pub(crate) fn from_field(field: &Field) -> Result { 27 | Ok(Self { 28 | type_name: field.ty.to_token_stream().to_string(), 29 | type_path: Self::parse_path(&field.ty)?, 30 | arg_name: field.ident.clone().map(|name| name.to_token_stream()), 31 | prop_attr: Self::parse_attr(&field.attrs)?, 32 | }) 33 | } 34 | pub(crate) fn from_fn_arg(arg: FnArg) -> Result { 35 | let typed = if let FnArg::Typed(typed) = &arg { 36 | typed 37 | } else { 38 | return Err(Error::new(arg.span(), "Unsupported argument type")); 39 | }; 40 | 41 | let arg_name = if let Pat::Ident(pat_ident) = *typed.pat.clone() { 42 | Some(pat_ident.ident.to_token_stream()) 43 | } else { 44 | return Err(Error::new(arg.span(), "Unsupported argument name")); 45 | }; 46 | 47 | Ok(Self { 48 | type_name: typed.ty.to_token_stream().to_string(), 49 | type_path: Self::parse_path(&typed.ty)?, 50 | arg_name, 51 | prop_attr: Self::parse_attr(&typed.attrs)?, 52 | }) 53 | } 54 | 55 | fn parse_attr(attrs: &[Attribute]) -> Result, Error> { 56 | let prop_attr = attrs.iter() 57 | .find(|attr| attr.path.to_token_stream().to_string() == *"prop"); 58 | 59 | if let Some(p) = prop_attr { 60 | return parse_prop_attr(p) 61 | .map(Some); 62 | 63 | } 64 | Ok(None) 65 | } 66 | 67 | fn parse_path(type_: &Type) -> Result { 68 | if let Type::Path(path_type) = type_ { 69 | Ok(path_type.path.clone()) 70 | } else { 71 | Err(Error::new(type_.span(), "Unsupported type")) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/waiter_codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | use proc_macro2::TokenStream as TokenStream2; 4 | use quote::ToTokens; 5 | use syn::*; 6 | use syn::punctuated::Punctuated; 7 | use syn::spanned::Spanned; 8 | use syn::token::Comma; 9 | 10 | use attr_parser::parse_provides_attr; 11 | use component::{generate_component_for_impl, generate_component_for_struct}; 12 | use provider::*; 13 | 14 | 15 | mod component; 16 | mod provider; 17 | mod attr_parser; 18 | 19 | #[proc_macro_attribute] 20 | pub fn module(_attr: TokenStream, item: TokenStream) -> TokenStream { 21 | component(_attr, item) 22 | } 23 | 24 | #[proc_macro_attribute] 25 | pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream { 26 | let mut res: TokenStream = remove_attrs(item.clone()); 27 | 28 | let comp = syn::parse::(item.clone()); 29 | if comp.is_ok() { 30 | let comp = comp.unwrap(); 31 | res.extend(unwrap(generate_component_for_struct(comp.clone()))); 32 | res.extend(generate_component_provider_impl_struct(comp.clone())); 33 | return res; 34 | } 35 | 36 | let impl_block = syn::parse::(item.clone()) 37 | .expect("#[component]/#[module] cant be used only on struct or impls"); 38 | res.extend(unwrap(generate_component_for_impl(impl_block.clone()))); 39 | res 40 | } 41 | 42 | #[proc_macro_attribute] 43 | pub fn provides(attr: TokenStream, item: TokenStream) -> TokenStream { 44 | let provides_attr = match parse_provides_attr(attr) { 45 | Ok(attr) => attr, 46 | Err(error) => return error.to_compile_error().into() 47 | }; 48 | 49 | let mut res = remove_attrs(item.clone()); 50 | 51 | let impl_block = syn::parse::(item.clone()); 52 | if let Ok(i) = impl_block { 53 | res.extend(generate_interface_provider_impl(provides_attr, i.clone())); 54 | return res; 55 | } 56 | 57 | let fn_block = syn::parse::(item.clone()) 58 | .expect("#[provides] must be used only on impl blocks and factory functions"); 59 | res.extend(unwrap(generate_component_provider_impl_fn( 60 | provides_attr, 61 | fn_block.clone(), 62 | TokenStream2::new(), 63 | ))); 64 | res 65 | } 66 | 67 | #[proc_macro_attribute] 68 | pub fn wrapper(_attr: TokenStream, item: TokenStream) -> TokenStream { 69 | let wrapper = parse_macro_input!(item as ItemStruct); 70 | 71 | let type_to_wrap = if let Fields::Unnamed(fields) = &wrapper.fields { 72 | let field = fields.unnamed.first(); 73 | if field.is_none() { 74 | return TokenStream::from( 75 | Error::new(wrapper.span(), "Struct annotated #[wrapper] must have exactly one field") 76 | .to_compile_error() 77 | ); 78 | } 79 | 80 | field.unwrap().ty.clone() 81 | } else { 82 | return TokenStream::from( 83 | Error::new(wrapper.span(), "Only tuple like struct supported for #[wrapper]") 84 | .to_compile_error() 85 | ); 86 | }; 87 | 88 | let wrapper_name = &wrapper.ident; 89 | 90 | TokenStream::from(quote::quote! { 91 | #wrapper 92 | impl std::ops::Deref for #wrapper_name { 93 | type Target = #type_to_wrap; 94 | 95 | fn deref(&self) -> &Self::Target { 96 | return &self.0; 97 | } 98 | } 99 | }) 100 | } 101 | 102 | fn remove_attrs(item: TokenStream) -> TokenStream { 103 | fn attr_filter(attr: &Attribute) -> bool { 104 | let attr_name = attr.path.to_token_stream().to_string(); 105 | attr_name.as_str() != "prop" && attr_name.as_str() != "provides" 106 | } 107 | 108 | let item = syn::parse::(item).unwrap(); 109 | 110 | let item = match item { 111 | Item::Fn(mut fn_) => { 112 | fn_.attrs.retain(attr_filter); 113 | fn_.sig.inputs.iter_mut() 114 | .for_each(|arg| if let FnArg::Typed(path) = arg { 115 | path.attrs.retain(attr_filter); 116 | }); 117 | Item::Fn(fn_) 118 | } 119 | Item::Impl(impl_) => { 120 | let mut impl_filtered = impl_.clone(); 121 | impl_filtered.items.clear(); 122 | 123 | for impl_item in impl_.items { 124 | let impl_item = match impl_item { 125 | ImplItem::Method(method) => { 126 | let mut method_filtered = method.clone(); 127 | method_filtered.attrs.retain(attr_filter); 128 | method_filtered.sig.inputs.clear(); 129 | 130 | for fn_arg in method.sig.inputs { 131 | let filtered = match fn_arg { 132 | FnArg::Typed(mut typed) => { 133 | typed.attrs.retain(attr_filter); 134 | FnArg::Typed(typed) 135 | } 136 | other => other 137 | }; 138 | method_filtered.sig.inputs.push(filtered); 139 | } 140 | 141 | ImplItem::Method(method_filtered) 142 | } 143 | other => other 144 | }; 145 | impl_filtered.items.push(impl_item) 146 | }; 147 | 148 | Item::Impl(impl_filtered) 149 | } 150 | Item::Struct(struct_) => { 151 | let mut struct_filtered = struct_.clone(); 152 | 153 | fn filter_fields(fields: Punctuated) -> Punctuated { 154 | let mut fields_filtered = fields.clone(); 155 | fields_filtered.clear(); 156 | 157 | for mut field in fields { 158 | field.attrs.retain(attr_filter); 159 | fields_filtered.push(field); 160 | } 161 | 162 | fields_filtered 163 | } 164 | 165 | struct_filtered.fields = match struct_.fields { 166 | Fields::Named(mut fields) => { 167 | fields.named = filter_fields(fields.named.clone()); 168 | Fields::Named(fields) 169 | } 170 | Fields::Unnamed(mut fields) => { 171 | fields.unnamed = filter_fields(fields.unnamed.clone()); 172 | Fields::Unnamed(fields) 173 | } 174 | other => other 175 | }; 176 | 177 | Item::Struct(struct_filtered) 178 | } 179 | other => other 180 | }; 181 | 182 | item.to_token_stream().into() 183 | } 184 | 185 | fn unwrap(result: Result) -> TokenStream { 186 | match result { 187 | Ok(result) => result, 188 | Err(err) => err.to_compile_error().into() 189 | } 190 | } -------------------------------------------------------------------------------- /crates/waiter_codegen/src/provider.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use std::ops::Deref; 3 | 4 | use proc_macro2::TokenStream as TokenStream2; 5 | use quote::ToTokens; 6 | use syn::{Error, GenericParam, ItemFn, ItemImpl, ItemStruct, Path, ReturnType, Type}; 7 | use syn::spanned::Spanned; 8 | 9 | use crate::attr_parser::ProvidesAttr; 10 | use crate::component::{generate_dependencies_create_code, generate_inject_dependencies_tuple}; 11 | use crate::component::type_to_inject::TypeToInject; 12 | 13 | pub(crate) fn generate_component_provider_impl_struct(component: ItemStruct) -> TokenStream { 14 | let comp_name = component.ident; 15 | let comp_generics = component.generics.clone(); 16 | 17 | let create_component_code = quote::quote! { 18 | #comp_name::__waiter_create(self) 19 | }; 20 | let inject_deferred_code = quote::quote! { 21 | #comp_name::__waiter_inject_deferred(self, &component); 22 | }; 23 | 24 | generate_component_provider_impl( 25 | quote::quote! { #comp_name #comp_generics }, 26 | component.generics.params.iter().collect(), 27 | vec!(), 28 | create_component_code, 29 | inject_deferred_code, 30 | ) 31 | } 32 | 33 | pub(crate) fn generate_component_provider_impl_fn( 34 | provides: ProvidesAttr, 35 | factory: ItemFn, 36 | force_type: TokenStream2, 37 | ) -> Result { 38 | let comp_name = if force_type.is_empty() { 39 | let ret_value = if let ReturnType::Type(_, type_) = &factory.sig.output { 40 | if let Type::Path(type_path) = type_.deref() { 41 | type_path.path.segments.to_token_stream() 42 | } else { 43 | return Err(Error::new( 44 | factory.span(), 45 | "Unsupported return type for factory function", 46 | )); 47 | } 48 | } else { 49 | return Err(Error::new( 50 | factory.span(), 51 | "Return type must be specified for factory function", 52 | )); 53 | }; 54 | ret_value 55 | } else { 56 | force_type.clone() 57 | }; 58 | 59 | let fn_name = factory.sig.ident.to_token_stream(); 60 | let fn_name_prefix = if force_type.is_empty() { 61 | force_type 62 | } else { 63 | quote::quote! { #force_type :: } 64 | }; 65 | 66 | let dependencies_code = generate_dependencies_create_code( 67 | factory.sig.inputs.iter() 68 | .map(|arg| TypeToInject::from_fn_arg(arg.clone())) 69 | .collect::, _>>()? 70 | ); 71 | let factory_code = generate_inject_dependencies_tuple(factory.sig.inputs.len()); 72 | 73 | let create_component_code = quote::quote! { 74 | { 75 | let container = &mut *self; 76 | #dependencies_code 77 | #fn_name_prefix #fn_name #factory_code 78 | } 79 | }; 80 | let inject_deferred_code = quote::quote! {}; 81 | 82 | Ok(generate_component_provider_impl( 83 | comp_name, 84 | factory.sig.generics.params.iter() 85 | .filter(|p| matches!(p, GenericParam::Lifetime(_))) 86 | .collect(), 87 | provides.profiles, 88 | create_component_code, 89 | inject_deferred_code, 90 | )) 91 | } 92 | 93 | pub fn generate_component_provider_impl( 94 | comp_name: TokenStream2, 95 | comp_generics: Vec<&GenericParam>, 96 | profiles: Vec, 97 | create_component_code: TokenStream2, 98 | inject_deferred_code: TokenStream2, 99 | ) -> TokenStream { 100 | let (profiles, provider_generics) = if profiles.is_empty() { 101 | let generic_profile = quote::quote! { PROFILE }; 102 | 103 | let provider_generics = if comp_generics.is_empty() { 104 | quote::quote! { } 105 | } else { 106 | quote::quote! { <#(#comp_generics),*, PROFILE> } 107 | }; 108 | 109 | (vec!(generic_profile), provider_generics) 110 | } else { 111 | let profiles = profiles.iter() 112 | .map(|p| p.to_token_stream()) 113 | .collect(); 114 | (profiles, quote::quote! { <#(#comp_generics),*> }) 115 | }; 116 | 117 | let result = quote::quote! {#( 118 | impl #provider_generics waiter_di::Provider<#comp_name> for waiter_di::Container<#profiles> { 119 | type Impl = #comp_name; 120 | fn get(&mut self) -> waiter_di::Wrc { 121 | let type_id = std::any::TypeId::of::<#comp_name>(); 122 | if !self.components.contains_key(&type_id) { 123 | let component = waiter_di::Wrc::new(#create_component_code); 124 | self.components.insert(type_id, component.clone()); 125 | #inject_deferred_code 126 | } 127 | let any = self.components.get(&type_id) 128 | .unwrap(); 129 | 130 | return any.clone() 131 | .downcast::<#comp_name>() 132 | .unwrap(); 133 | } 134 | fn create(&mut self) -> Self::Impl { 135 | let component = #create_component_code; 136 | #inject_deferred_code 137 | return component; 138 | } 139 | } 140 | )*}; 141 | 142 | TokenStream::from(result) 143 | } 144 | 145 | pub(crate) fn generate_interface_provider_impl(provides: ProvidesAttr, impl_block: ItemImpl) -> TokenStream { 146 | let interface = match impl_block.trait_ { 147 | Some((_, interface, _)) => interface, 148 | None => return TokenStream::from(Error::new( 149 | impl_block.span(), 150 | "#[provides] can be used only on impl blocks for traits", 151 | ).to_compile_error()) 152 | }; 153 | 154 | let comp_name = if let Type::Path(comp_path) = *impl_block.self_ty { 155 | comp_path.path.segments.first().unwrap().ident.clone() 156 | } else { 157 | return TokenStream::from(Error::new(impl_block.self_ty.span(), "Failed to create provider").to_compile_error()); 158 | }; 159 | 160 | let provider_body = quote::quote! {{ 161 | type Impl = #comp_name; 162 | fn get(&mut self) -> waiter_di::Wrc { 163 | waiter_di::Provider::<#comp_name>::get(self) 164 | } 165 | fn create(&mut self) -> Self::Impl { 166 | waiter_di::Provider::<#comp_name>::create(self) 167 | } 168 | }}; 169 | 170 | let profiles = provides.profiles; 171 | let result = if profiles.is_empty() { 172 | quote::quote! { 173 | impl

waiter_di::Provider for waiter_di::Container

#provider_body 174 | } 175 | } else { 176 | quote::quote! { 177 | #(impl waiter_di::Provider for waiter_di::Container<#profiles> #provider_body)* 178 | } 179 | }; 180 | 181 | TokenStream::from(result) 182 | } -------------------------------------------------------------------------------- /examples/1_get_started.rs: -------------------------------------------------------------------------------- 1 | extern crate config; 2 | extern crate serde; 3 | extern crate waiter_di; 4 | 5 | use std::rc::Rc; 6 | 7 | use waiter_di::*; 8 | 9 | trait Interface: Send { 10 | fn demo(&self); 11 | } 12 | 13 | #[component] 14 | struct InterfaceImpl {} 15 | 16 | #[provides] 17 | impl Interface for InterfaceImpl { 18 | fn demo(&self) { 19 | println!("Dependency"); 20 | } 21 | } 22 | 23 | #[component] 24 | struct SomeComp { 25 | #[prop("i32_prop")] prop: i32, 26 | interface: Rc, 27 | } 28 | 29 | fn main() { 30 | let mut container = Container::::new(); 31 | 32 | let component = Provider::::get(&mut container); 33 | 34 | component.interface.demo(); 35 | } -------------------------------------------------------------------------------- /examples/2_modules.rs: -------------------------------------------------------------------------------- 1 | extern crate config; 2 | extern crate serde; 3 | extern crate waiter_di; 4 | 5 | use std::collections::HashMap; 6 | 7 | use waiter_di::*; 8 | 9 | // Simple demo of dependency inversion, constructors and modules 10 | 11 | trait UserRepository { 12 | fn find(&self, id: i64) -> Option<&String>; 13 | fn save(&mut self, id: i64, username: String); 14 | } 15 | 16 | struct HashMapUserRepository { 17 | users: HashMap, 18 | } 19 | 20 | #[component] 21 | impl HashMapUserRepository { 22 | #[provides] 23 | fn new() -> Self { 24 | HashMapUserRepository { users: HashMap::new() } 25 | } 26 | } 27 | 28 | #[provides] 29 | impl UserRepository for HashMapUserRepository { 30 | fn find(&self, id: i64) -> Option<&String> { 31 | self.users.get(&id) 32 | } 33 | 34 | fn save(&mut self, id: i64, username: String) { 35 | self.users.insert(id, username); 36 | } 37 | } 38 | 39 | #[module] 40 | struct UserModule { 41 | repository: Box, 42 | } 43 | 44 | #[module] 45 | struct RootModule { 46 | user_module: UserModule, 47 | } 48 | 49 | fn main() { 50 | let mut container = Container::::new(); 51 | 52 | let mut user_repository = Provider::::create(&mut container); 53 | 54 | user_repository.save(12, "John".to_string()); 55 | 56 | println!("Found user with id = 12: {:?}", user_repository.find(12)); 57 | } -------------------------------------------------------------------------------- /examples/3_inject_options_list.rs: -------------------------------------------------------------------------------- 1 | extern crate config; 2 | extern crate serde; 3 | extern crate waiter_di; 4 | 5 | use std::rc::Rc; 6 | 7 | use config::Config; 8 | use serde::Deserialize; 9 | 10 | use waiter_di::*; 11 | 12 | trait Interface { 13 | fn int(&self); 14 | } 15 | 16 | trait Interface2 { 17 | fn int2(&self); 18 | } 19 | 20 | struct Dependency { 21 | map: HashMap, 22 | } 23 | 24 | impl Dependency { 25 | fn dep(&self) { 26 | println!("Dep {:?}", self.map); 27 | } 28 | } 29 | 30 | #[provides] 31 | fn create_dependency(map: HashMap) -> Dependency { 32 | println!("dep factory"); 33 | Dependency { map } 34 | } 35 | 36 | #[derive(Debug)] 37 | struct HashMap(std::collections::HashMap); 38 | 39 | #[provides] 40 | fn create_external_type_dependency() -> HashMap { 41 | HashMap(std::collections::HashMap::::new()) 42 | } 43 | 44 | #[derive(Debug, Deserialize)] 45 | struct ConfigObject { 46 | i32_prop: i32, 47 | } 48 | 49 | #[component] 50 | struct Comp { 51 | dependency: Dependency, 52 | dependency_rc: Rc, 53 | dependency_box: Box, 54 | dependency_def: Deferred, 55 | dependency_def_rc: Deferred>, 56 | dependency_def_box: Deferred>, 57 | cyclic: Deferred>, 58 | config: Config, 59 | #[prop("int_v")] int_prop: usize, 60 | #[prop("float_v" = 3.14)] float_prop: f32, 61 | str_prop: String, 62 | bool_prop: Option, 63 | #[prop] config_object: ConfigObject, 64 | } 65 | 66 | impl Comp { 67 | fn comp(&self) { 68 | self.dependency.dep(); 69 | self.dependency_rc.dep(); 70 | self.dependency_box.dep(); 71 | self.dependency_def.dep(); 72 | self.dependency_def_rc.dep(); 73 | self.dependency_def_box.dep(); 74 | self.config.get_string("prop").unwrap(); 75 | println!("Comp, {}, {}, {}, {:?}, {}", self.int_prop, self.float_prop, self.str_prop, 76 | self.bool_prop, self.config_object.i32_prop); 77 | } 78 | } 79 | 80 | #[provides] 81 | impl Interface for Comp { 82 | fn int(&self) { 83 | println!("Interface"); 84 | } 85 | } 86 | 87 | #[provides(profiles::Dev)] 88 | impl Interface2 for Comp { 89 | fn int2(&self) { 90 | println!("Interface 2"); 91 | } 92 | } 93 | 94 | 95 | fn main() { 96 | let mut container = Container::::new(); 97 | 98 | let comp = Provider::::get_ref(&mut container); 99 | comp.comp(); 100 | comp.int(); 101 | comp.int2(); 102 | 103 | let comp = Provider::::get_ref(&mut container); 104 | comp.int(); 105 | 106 | 107 | let mut container = Container::::new(); 108 | let comp = Provider::::get_ref(&mut container); 109 | comp.int(); 110 | 111 | let comp = Provider::::get_ref(&mut container); 112 | comp.int2(); 113 | 114 | 115 | println!("Using profile: {}", APP_PROFILE.as_str()); 116 | let comp = inject!(Comp: profiles::Default, profiles::Dev); 117 | comp.comp(); 118 | } -------------------------------------------------------------------------------- /src/container.rs: -------------------------------------------------------------------------------- 1 | use std::any::{type_name, TypeId}; 2 | use std::collections::HashMap; 3 | use std::env; 4 | use std::env::args; 5 | use std::marker::PhantomData; 6 | 7 | use config::{Config, Environment, File}; 8 | use lazy_static::lazy_static; 9 | use regex::Regex; 10 | 11 | use crate::{RcAny, Wrc}; 12 | 13 | pub mod profiles { 14 | pub struct Default; 15 | 16 | pub struct Dev; 17 | 18 | pub struct Test; 19 | } 20 | 21 | pub trait Component { 22 | fn __waiter_create

(container: &mut Container

) -> Self; 23 | fn __waiter_inject_deferred

(container: &mut Container

, component: &Self); 24 | } 25 | 26 | pub trait Provider { 27 | type Impl; 28 | fn get(&mut self) -> Wrc; 29 | fn create(&mut self) -> Self::Impl; 30 | 31 | fn get_ref(&mut self) -> &Self::Impl { 32 | // Value under RC is still stored in container, so it can be safely returned as a reference 33 | // that has the same life as container reference 34 | unsafe { 35 | Wrc::as_ptr(&Self::get(self)) 36 | .as_ref() 37 | .unwrap() 38 | } 39 | } 40 | fn create_boxed(&mut self) -> Box { 41 | Box::new(Self::create(self)) 42 | } 43 | } 44 | 45 | pub struct Container

{ 46 | profile: PhantomData

, 47 | pub config: Config, 48 | pub components: HashMap, 49 | } 50 | 51 | impl

Default for Container

{ 52 | fn default() -> Self { 53 | Self::new() 54 | } 55 | } 56 | 57 | impl

Container

{ 58 | pub fn new() -> Container

{ 59 | let mut config = Config::builder() 60 | .add_source(File::with_name("config/default").required(false)); 61 | 62 | let profile = profile_name::

(); 63 | if profile.ne(&"default".to_string()) { 64 | config = config 65 | .add_source(File::with_name(&format!("config/{}", profile)) 66 | .required(false)); 67 | } 68 | 69 | let config = config.add_source(Environment::default()).add_source(parse_args()); 70 | let config = config.build().expect("Failed to load environment"); 71 | 72 | Container { 73 | config, 74 | profile: PhantomData::

, 75 | components: HashMap::new(), 76 | } 77 | } 78 | } 79 | 80 | 81 | lazy_static! { 82 | pub static ref APP_PROFILE: String = parse_profile(); 83 | } 84 | 85 | fn parse_profile() -> String { 86 | let config_builder = Config::builder() 87 | .add_source(File::with_name("config/default").required(false)); 88 | 89 | 90 | let profile_arg = args().position(|arg| arg.as_str() == "--profile") 91 | .and_then(|arg_pos| args().nth(arg_pos + 1)); 92 | let config = config_builder.build().expect("Failed to load environment"); 93 | let parsed_profile = profile_arg 94 | .or(env::var("PROFILE").ok()) 95 | .or(config.get_string("profile").ok()) 96 | .unwrap_or("default".to_string()); 97 | 98 | log::info!("Using profile: {}", parsed_profile); 99 | 100 | parsed_profile 101 | } 102 | 103 | pub fn parse_args() -> Config { 104 | let mut config = Config::builder(); 105 | 106 | let mut args = args().peekable(); 107 | loop { 108 | let arg = args.next(); 109 | if arg.is_some() { 110 | let arg = arg.unwrap(); 111 | if let Some(stripped) = arg.strip_prefix("--") { 112 | let value = args.peek(); 113 | if value.is_none() || value.unwrap().starts_with("--") { 114 | config = config.set_override(stripped, true).unwrap(); 115 | } else { 116 | config = config.set_override(stripped, args.next().unwrap()).unwrap(); 117 | } 118 | } 119 | } else { 120 | break; 121 | } 122 | } 123 | 124 | config.build().expect("Config should be built") 125 | } 126 | 127 | pub fn profile_name() -> String { 128 | let profile_type_name = type_name::().to_lowercase(); 129 | 130 | Regex::new(r".*::").unwrap() 131 | .replace(profile_type_name.as_str(), "") 132 | .to_string() 133 | } -------------------------------------------------------------------------------- /src/deferred.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::sync::Mutex; 3 | 4 | use crate::deferred::DeferredValue::{Initialized, WaitingForValue}; 5 | 6 | pub struct Deferred { 7 | value: Mutex>, 8 | } 9 | 10 | pub enum DeferredValue { 11 | Initialized(T), 12 | WaitingForValue, 13 | } 14 | 15 | impl Deferred { 16 | pub fn new() -> Self { 17 | Self { value: Mutex::new(WaitingForValue) } 18 | } 19 | pub fn init(&self, value: T) { 20 | *self.value.lock().unwrap() = Initialized(value); 21 | } 22 | } 23 | 24 | impl Default for Deferred { 25 | fn default() -> Self { 26 | Self::new() 27 | } 28 | } 29 | 30 | impl Deref for Deferred { 31 | type Target = T; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | unsafe { 35 | if let Initialized(value) = &*(self.value.lock().unwrap().deref() as *const DeferredValue) { 36 | value 37 | } else { 38 | panic!("Deferred value must be initialized before the first usage") 39 | } 40 | } 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use crate::Deferred; 47 | 48 | #[test] 49 | fn deref_after_init() { 50 | let deferred = Deferred::<&str>::new(); 51 | deferred.init("Initialized"); 52 | assert_eq!("Initialized", *deferred); 53 | } 54 | 55 | #[test] 56 | #[should_panic] 57 | fn deref_before_init() { 58 | let deferred = Deferred::<&str>::new(); 59 | assert_eq!("Initialized", *deferred); 60 | } 61 | } -------------------------------------------------------------------------------- /src/inject.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! inject { 3 | ($comp:path: $($profile:path),*) => { 4 | { 5 | $( 6 | if profile_name::<$profile>().eq(&waiter_di::APP_PROFILE.as_str()) { 7 | waiter_di::Provider::<$comp>::create(&mut waiter_di::Container::<$profile>::new()) 8 | } else 9 | )* 10 | { waiter_di::Provider::<$comp>::create(&mut waiter_di::Container::::new()) } 11 | } 12 | } 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! wrap { 17 | ($wrapped_type:path as $wrapper_name:ident) => { 18 | pub struct $wrapper_name($wrapped_type); 19 | impl Deref for $wrapper_name { 20 | type Target = $wrapped_type; 21 | fn deref(&self) -> &Self::Target { 22 | return &self.0; 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | pub use container::*; 4 | pub use deferred::*; 5 | pub use waiter_codegen::*; 6 | 7 | pub mod container; 8 | pub mod deferred; 9 | 10 | #[macro_use] 11 | pub mod inject; 12 | 13 | #[cfg(feature = "async")] 14 | pub type Wrc = std::sync::Arc; 15 | 16 | #[cfg(not(feature = "async"))] 17 | pub type Wrc = std::rc::Rc; 18 | 19 | 20 | #[cfg(feature = "async")] 21 | pub type RcAny = Wrc; 22 | 23 | #[cfg(not(feature = "async"))] 24 | pub type RcAny = Wrc; -------------------------------------------------------------------------------- /waiter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------