diff options
Diffstat
| -rw-r--r-- | .github/workflows/build.yml | 14 | +14 −0 |
| -rw-r--r-- | Cargo.lock | 438 | +303 −135 |
| -rw-r--r-- | Cargo.toml | 1 | +1 −0 |
| -rw-r--r-- | Makefile | 6 | +6 −0 |
| -rw-r--r-- | src/backends/go.rs | 4 | +2 −2 |
| -rw-r--r-- | src/backends/zig.rs | 4 | +2 −2 |
| -rw-r--r-- | src/config.rs | 327 | +303 −24 |
| -rw-r--r-- | src/main.rs | 24 | +6 −18 |
| -rw-r--r-- | src/utils.rs | 110 | +109 −1 |
9 files changed, 746 insertions, 182 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e57e505..e8020ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,6 +52,8 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable + with: + components: llvm-tools-preview - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - name: Import GPG key @@ -87,6 +89,18 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Generate coverage (lcov) + run: cargo llvm-cov --all-features --workspace --lcov --output-path coverage.lcov + + - name: Upload coverage artifact + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: coverage-lcov-${{ matrix.arch }} + path: coverage.lcov + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: packages-${{ matrix.arch }} diff --git a/Cargo.lock b/Cargo.lock index 9dda733..ee311d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,9 +105,9 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" dependencies = [ "rustversion", ] @@ -376,9 +376,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bytesize" @@ -399,6 +399,18 @@ dependencies = [ ] [[package]] +name = "cargo-config2" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3795d3a48839a46854805f56c8fe9c558f10804bcf57df53925ca843d87c788f" +dependencies = [ + "serde", + "serde_derive", + "toml", + "windows-sys 0.61.2", +] + +[[package]] name = "cargo-deny" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -443,10 +455,37 @@ dependencies = [ ] [[package]] +name = "cargo-llvm-cov" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8132c5e1b6489a345e12bbc3deb7154cb8d8b51bd9d9b168505eff5e5ce88ee" +dependencies = [ + "anyhow", + "camino", + "cargo-config2", + "duct", + "fs-err", + "glob", + "lcov2cobertura", + "lexopt", + "opener", + "regex", + "rustc-demangle", + "ruzstd", + "serde", + "serde_derive", + "serde_json", + "shell-escape", + "tar", + "termcolor", + "walkdir", +] + +[[package]] name = "cargo-lock" -version = "11.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf53e0ebbbc6e45357b199f3b213f3eb330792c8b370e548499f5685470ecb11" +checksum = "63585cdf8572aa7adf0e30a253f988f2b77233bfac1973d52efb6dd53a75920e" dependencies = [ "semver", "serde", @@ -456,9 +495,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.53" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -474,9 +513,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-expr" -version = "0.20.5" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720" +checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" dependencies = [ "smallvec", "target-lexicon", @@ -510,9 +549,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -520,9 +559,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -532,9 +571,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -824,6 +863,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] +name = "duct" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e66e9c0c03d094e1a0ba1be130b849034aa80c3a2ab8ee94316bc809f3fa684" +dependencies = [ + "libc", + "os_pipe", + "shared_child", + "shared_thread", +] + +[[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -912,21 +963,20 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -936,9 +986,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1215,7 +1265,7 @@ dependencies = [ "parking_lot", "signal-hook 0.3.18", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1228,7 +1278,7 @@ dependencies = [ "gix-date", "gix-utils", "itoa", - "thiserror 2.0.17", + "thiserror 2.0.18", "winnow", ] @@ -1245,7 +1295,7 @@ dependencies = [ "gix-trace", "kstring", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-bom", ] @@ -1255,7 +1305,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e150161b8a75b5860521cb876b506879a3376d3adc857ec7a9d35e7c6a5e531" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1264,7 +1314,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c356b3825677cb6ff579551bb8311a81821e184453cbd105e2fc5311b288eeb" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1290,7 +1340,7 @@ dependencies = [ "gix-chunk", "gix-hash", "memmap2", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1308,7 +1358,7 @@ dependencies = [ "gix-sec", "memchr", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-bom", "winnow", ] @@ -1323,7 +1373,7 @@ dependencies = [ "bstr", "gix-path", "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1341,7 +1391,7 @@ dependencies = [ "gix-sec", "gix-trace", "gix-url", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1354,7 +1404,7 @@ dependencies = [ "itoa", "jiff", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1366,7 +1416,7 @@ dependencies = [ "bstr", "gix-hash", "gix-object", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1382,7 +1432,7 @@ dependencies = [ "gix-path", "gix-ref", "gix-sec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1399,7 +1449,7 @@ dependencies = [ "libc", "once_cell", "prodash", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", "zlib-rs", ] @@ -1422,7 +1472,7 @@ dependencies = [ "gix-trace", "gix-utils", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1436,7 +1486,7 @@ dependencies = [ "gix-features", "gix-path", "gix-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1460,7 +1510,7 @@ dependencies = [ "faster-hex", "gix-features", "sha1-checked", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1512,7 +1562,7 @@ dependencies = [ "memmap2", "rustix", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1523,7 +1573,7 @@ checksum = "115268ae5e3b3b7bc7fc77260eecee05acca458e45318ca45d35467fa81a3ac5" dependencies = [ "gix-tempfile", "gix-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1539,7 +1589,7 @@ dependencies = [ "gix-object", "gix-revwalk", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1559,7 +1609,7 @@ dependencies = [ "gix-validate", "itoa", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "winnow", ] @@ -1581,7 +1631,7 @@ dependencies = [ "gix-quote", "parking_lot", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1601,7 +1651,7 @@ dependencies = [ "memmap2", "parking_lot", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1613,7 +1663,7 @@ dependencies = [ "bstr", "faster-hex", "gix-trace", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1625,7 +1675,7 @@ dependencies = [ "bstr", "gix-trace", "gix-validate", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1640,7 +1690,7 @@ dependencies = [ "gix-config-value", "gix-glob", "gix-path", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1653,7 +1703,7 @@ dependencies = [ "gix-config-value", "parking_lot", "rustix", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1678,7 +1728,7 @@ dependencies = [ "gix-transport", "gix-utils", "maybe-async", - "thiserror 2.0.17", + "thiserror 2.0.18", "winnow", ] @@ -1690,7 +1740,7 @@ checksum = "e912ec04b7b1566a85ad486db0cab6b9955e3e32bcd3c3a734542ab3af084c5b" dependencies = [ "bstr", "gix-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1710,7 +1760,7 @@ dependencies = [ "gix-utils", "gix-validate", "memmap2", - "thiserror 2.0.17", + "thiserror 2.0.18", "winnow", ] @@ -1726,7 +1776,7 @@ dependencies = [ "gix-revision", "gix-validate", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1741,7 +1791,7 @@ dependencies = [ "gix-hash", "gix-object", "gix-revwalk", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1756,7 +1806,7 @@ dependencies = [ "gix-hashtable", "gix-object", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1780,7 +1830,7 @@ dependencies = [ "bstr", "gix-hash", "gix-lock", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1795,7 +1845,7 @@ dependencies = [ "gix-pathspec", "gix-refspec", "gix-url", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1807,7 +1857,7 @@ dependencies = [ "gix-fs", "libc", "parking_lot", - "signal-hook 0.4.1", + "signal-hook 0.4.3", "signal-hook-registry", "tempfile", ] @@ -1834,7 +1884,7 @@ dependencies = [ "gix-sec", "gix-url", "reqwest 0.13.1", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1851,7 +1901,7 @@ dependencies = [ "gix-object", "gix-revwalk", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1864,7 +1914,7 @@ dependencies = [ "gix-features", "gix-path", "percent-encoding", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1884,7 +1934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" dependencies = [ "bstr", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1921,10 +1971,16 @@ dependencies = [ "gix-path", "gix-worktree", "io-close", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] name = "globset" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2216,14 +2272,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -2243,9 +2298,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2540,6 +2595,24 @@ dependencies = [ ] [[package]] +name = "lcov2cobertura" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa0cf456e88a45378a5737f228c0800175d94be6856908dc4718b3a91c7c9f8" +dependencies = [ + "anyhow", + "quick-xml", + "regex", + "rustc-demangle", +] + +[[package]] +name = "lexopt" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" + +[[package]] name = "libc" version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2547,9 +2620,9 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -2759,6 +2832,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] +name = "normpath" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2785,9 +2867,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -2832,6 +2914,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] +name = "opener" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fa337e0cf13357c13ef1dc108df1333eb192f75fc170bea03fcf1fd404c2ee" +dependencies = [ + "bstr", + "normpath", + "windows-sys 0.61.2", +] + +[[package]] name = "openssl" version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2865,9 +2958,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" @@ -2891,7 +2984,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -2933,7 +3026,7 @@ dependencies = [ "opentelemetry_sdk", "prost", "reqwest 0.12.28", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tonic", "tracing", @@ -2970,12 +3063,22 @@ dependencies = [ "opentelemetry", "percent-encoding", "rand 0.9.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", ] [[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3097,24 +3200,24 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "platforms" -version = "3.7.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f21de1852251c849a53467e0ce8b97cca9d11fd4efa3930145c5d5f02f24447" +checksum = "a546fc83c436ffbef8e7e639df8498bbc5122e0bd19cf8db208720c2cc85290e" dependencies = [ "serde", ] [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -3145,9 +3248,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3175,9 +3278,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", @@ -3237,10 +3340,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -3374,9 +3486,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -3386,9 +3498,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3397,9 +3509,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" @@ -3543,6 +3655,12 @@ dependencies = [ ] [[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] name = "rustc-stable-hash" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3582,7 +3700,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.0", + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -3647,7 +3765,7 @@ dependencies = [ "platforms", "semver", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml", "url", ] @@ -3671,6 +3789,12 @@ dependencies = [ ] [[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" + +[[package]] name = "ryu" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3898,6 +4022,28 @@ dependencies = [ ] [[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "shared_thread" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] name = "shell-words" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3921,9 +4067,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" +checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e" dependencies = [ "libc", "signal-hook-registry", @@ -3957,9 +4103,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -3982,9 +4128,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -4072,7 +4218,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -4155,7 +4301,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -4193,7 +4339,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -4218,7 +4364,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", ] @@ -4312,9 +4458,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", "core-foundation 0.9.4", @@ -4352,7 +4498,7 @@ dependencies = [ "serde_json", "sha2", "smol_str", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "toml-span", "twox-hash", @@ -4365,6 +4511,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] name = "target-lexicon" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4403,11 +4560,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4423,9 +4580,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -4443,9 +4600,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -4458,15 +4615,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -4620,9 +4777,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" dependencies = [ "async-trait", "axum", @@ -4734,7 +4891,7 @@ dependencies = [ "governor", "http", "pin-project", - "thiserror 2.0.17", + "thiserror 2.0.18", "tonic", "tower", "tracing", @@ -4933,9 +5090,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -5091,9 +5248,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] @@ -5528,6 +5685,16 @@ dependencies = [ ] [[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] name = "yoke" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5552,18 +5719,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" dependencies = [ "proc-macro2", "quote", @@ -5638,9 +5805,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zorian" @@ -5653,6 +5820,7 @@ dependencies = [ "bytes", "bytesize", "cargo-deny", + "cargo-llvm-cov", "chrono", "crc32fast", "futures", @@ -5677,7 +5845,7 @@ dependencies = [ "serde_json", "sqlx", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-util", "toml", diff --git a/Cargo.toml b/Cargo.toml index 17571ff..da0ac9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,5 +81,6 @@ sd-notify = { version = "0.4" } [dev-dependencies] cargo-deny = "0.19" +cargo-llvm-cov = "0.8" proptest = "1.6" tempfile = "3.24" diff --git a/Makefile b/Makefile index 5c335cf..a7ca28f 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,9 @@ clippy: test: cargo build --release cargo test --all-features --release --locked + +.PHONY: coverage +coverage: + mkdir -p target/coverage/ + cargo llvm-cov --all-features --workspace --lcov --output-path target/coverage/lcov.info + cargo llvm-cov --all-features --workspace --html --output-dir target/coverage/html diff --git a/src/backends/go.rs b/src/backends/go.rs index a941387..f11a057 100644 --- a/src/backends/go.rs +++ b/src/backends/go.rs @@ -10,14 +10,14 @@ use thiserror::Error; use url::Url; use super::{Archive, Backend, BackendDelegate, FileKind, IndexError, ResolveError, ResolvedFile}; -use crate::utils::deserialize_duration_secs; +use crate::utils::deserialize_duration; #[derive(Debug, Clone, Deserialize)] #[serde(default)] pub struct GoConfig { pub enabled: bool, pub upstream: Url, - #[serde(deserialize_with = "deserialize_duration_secs")] + #[serde(deserialize_with = "deserialize_duration")] pub refresh_interval: Duration, } impl Default for GoConfig { diff --git a/src/backends/zig.rs b/src/backends/zig.rs index 903f4b1..9f95c1a 100644 --- a/src/backends/zig.rs +++ b/src/backends/zig.rs @@ -15,14 +15,14 @@ use super::{ Archive, Backend, BackendDelegate, FileKind, IndexError, ResolveError, ResolvedFile, VersionType, stable_version, }; -use crate::utils::{deserialize_duration_secs, deserialize_size}; +use crate::utils::{deserialize_duration, deserialize_size}; #[derive(Debug, Clone, Deserialize)] #[serde(default)] pub struct ZigConfig { pub enabled: bool, pub upstream: Url, - #[serde(deserialize_with = "deserialize_duration_secs")] + #[serde(deserialize_with = "deserialize_duration")] pub refresh_interval: Duration, } impl Default for ZigConfig { diff --git a/src/config.rs b/src/config.rs index c7437aa..beee6f8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,9 +10,10 @@ use std::time::Duration; use bytesize::ByteSize; use serde::Deserialize; use thiserror::Error; +use tracing::info; use crate::backends::{GoConfig, ZigConfig}; -use crate::utils::{deserialize_duration_secs, deserialize_listener_addr}; +use crate::utils::{deserialize_duration, deserialize_listener_addr}; #[derive(Debug, Error)] pub enum ConfigError { @@ -50,11 +51,11 @@ pub enum ConfigError { #[derive(Debug, Clone, Deserialize)] pub struct ServerConfig { /// When receiving a SIGINT/SIGTERM signal, we will wait for the proposed timeout before terminating workers - #[serde(deserialize_with = "deserialize_duration_secs")] + #[serde(deserialize_with = "deserialize_duration")] pub shutdown_timeout: Duration, /// Request timeout - maximum time to process a request (protects against Slowloris) - #[serde(deserialize_with = "deserialize_duration_secs")] + #[serde(deserialize_with = "deserialize_duration")] pub request_timeout: Duration, /// Maximum request body size @@ -64,12 +65,13 @@ pub struct ServerConfig { pub max_concurrent_requests: usize, /// Rate limit: requests per second per client IP - #[serde(deserialize_with = "deserialize_duration_secs")] + #[serde(deserialize_with = "deserialize_duration")] pub rate_limit_period: Duration, /// Rate limit: burst size (max requests allowed in a burst) per client IP pub rate_limit_burst_size: u32, } + impl Default for ServerConfig { fn default() -> Self { Self { @@ -97,6 +99,7 @@ pub struct ListenerConfig { /// Path to TLS private key file (PEM format). If set, tls_crt must also be set. pub tls_key: Option<PathBuf>, } + impl Default for ListenerConfig { fn default() -> Self { Self { @@ -142,6 +145,7 @@ pub struct StdoutConfig { /// Controls the format of logs in stdout pub log_format: StdoutFormat, } + impl Default for StdoutConfig { fn default() -> Self { Self { @@ -171,7 +175,7 @@ pub struct OtelcolConfig { pub endpoint: String, /// Export timeout in seconds - #[serde(deserialize_with = "deserialize_duration_secs")] + #[serde(deserialize_with = "deserialize_duration")] pub timeout: Duration, /// Controls which logs will be sent to otlp @@ -189,6 +193,7 @@ pub struct OtelcolConfig { /// HTTP headers for authentication pub headers: HashMap<String, String>, } + impl Default for OtelcolConfig { fn default() -> Self { Self { @@ -222,7 +227,6 @@ pub struct BackendsConfig { } #[derive(Debug, Deserialize)] -#[serde(default)] pub struct ConfigService { appname: String, dirname: PathBuf, @@ -231,26 +235,34 @@ pub struct ConfigService { telemetry: TelemetryConfig, backends: BackendsConfig, } -impl Default for ConfigService { - fn default() -> Self { - Self { - appname: "zorian".to_string(), - dirname: PathBuf::from("./.zorian-state"), - server: ServerConfig::default(), - listen: vec![ListenerConfig::default()], - telemetry: TelemetryConfig::default(), - backends: BackendsConfig::default(), - } - } -} + impl ConfigService { - pub fn from_file(path: &Path) -> Result<Self, ConfigError> { - let content = fs::read_to_string(path)?; - let config: Self = toml::from_str(&content)?; - Ok(config) + pub fn load(config_path: Option<PathBuf>) -> Result<Self, ConfigError> { + let config = match config_path { + Some(path) => { + info!("use config file from {}", path.to_str().unwrap()); + + let content = fs::read_to_string(path)?; + let config: Self = toml::from_str(&content)?; + config + } + None => { + info!("configuration file path not provided"); + Self { + appname: "zorian".to_string(), + dirname: PathBuf::from("./.zorian-state"), + server: ServerConfig::default(), + listen: vec![ListenerConfig::default()], + telemetry: TelemetryConfig::default(), + backends: BackendsConfig::default(), + } + } + }; + + config.validate() } - pub fn validate(&self) -> Result<(), ConfigError> { + fn validate(self) -> Result<Self, ConfigError> { let mut chars = self.appname.chars(); if !chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') { return Err(ConfigError::InvalidAppname(self.appname.clone())); @@ -294,7 +306,7 @@ impl ConfigService { } } - Ok(()) + Ok(self) } pub fn appname(&self) -> &str { @@ -340,3 +352,270 @@ impl ConfigService { } } } + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + fn base_config(dir: PathBuf) -> ConfigService { + ConfigService { + appname: "valid-name".to_string(), + dirname: dir, + server: ServerConfig::default(), + listen: vec![ListenerConfig { + addr: "127.0.0.1:0".parse().unwrap(), + hostnames: Vec::new(), + tls_crt: None, + tls_key: None, + }], + telemetry: TelemetryConfig::default(), + backends: BackendsConfig::default(), + } + } + + fn write_temp_file(dir: &Path, name: &str) -> PathBuf { + let path = dir.join(name); + std::fs::write(&path, "x").unwrap(); + path + } + + mod defaults_tests { + use super::*; + + #[test] + fn test_for_test_config() { + let dir = TempDir::new().unwrap(); + let cfg = ConfigService::for_test(dir.path().to_path_buf()); + assert_eq!(cfg.appname(), "test"); + assert_eq!(cfg.dirname(), dir.path()); + } + + #[test] + fn test_listener_default() { + let cfg = ListenerConfig::default(); + assert!(cfg.hostnames.contains(&"localhost".to_string())); + } + } + + mod load_tests { + use super::*; + + #[test] + fn test_load_none_defaults() { + let temp = TempDir::new().unwrap(); + let state = temp.path().join(".zorian-state"); + std::fs::create_dir_all(&state).unwrap(); + let cwd = std::env::current_dir().unwrap(); + std::env::set_current_dir(temp.path()).unwrap(); + + let cfg = ConfigService::load(None).unwrap(); + assert_eq!(cfg.appname(), "zorian"); + + std::env::set_current_dir(cwd).unwrap(); + } + + #[test] + fn test_load_some_and_parse_error() { + let dir = TempDir::new().unwrap(); + let good = dir.path().join("good.toml"); + let bad = dir.path().join("bad.toml"); + let missing = dir.path().join("missing.toml"); + let state = dir.path().join("state"); + std::fs::create_dir_all(&state).unwrap(); + + std::fs::write( + &good, + format!( + "appname = \"ok\"\n\ + dirname = \"{}\"\n\ + listen = []\n\ + [server]\n\ + shutdown_timeout = 1\n\ + request_timeout = 1\n\ + max_body_size = \"1 MB\"\n\ + max_concurrent_requests = 1\n\ + rate_limit_period = 1\n\ + rate_limit_burst_size = 1\n\ + [telemetry]\n\ + [telemetry.stdout]\n\ + enabled = true\n\ + log_level = \"info\"\n\ + log_format = \"pretty\"\n\ + [backends]\n\ + [backends.go]\n\ + [backends.zig]\n", + state.display() + ), + ) + .unwrap(); + + std::fs::write(&bad, "not = [valid").unwrap(); + + let cfg = ConfigService::load(Some(good)).unwrap(); + assert_eq!(cfg.appname(), "ok"); + + let err = ConfigService::load(Some(missing)).unwrap_err(); + assert!(err.to_string().contains("failed to read")); + + let err = ConfigService::load(Some(bad)).unwrap_err(); + assert!(err.to_string().contains("failed to parse")); + } + } + + mod validation_tests { + use super::*; + + #[test] + fn test_validate_invalid_appname() { + let dir = TempDir::new().unwrap(); + let mut cfg = base_config(dir.path().to_path_buf()); + cfg.appname = "bad name!".to_string(); + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("appname 'bad name!'")); + } + + #[test] + fn test_validate_dir_not_found() { + let temp = TempDir::new().unwrap(); + let missing = temp.path().join("missing-dir"); + let cfg = base_config(missing); + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("does not exist")); + } + + #[test] + fn test_validate_not_a_directory() { + let dir = TempDir::new().unwrap(); + let file_path = dir.path().join("file"); + std::fs::write(&file_path, "x").unwrap(); + let cfg = base_config(file_path); + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("is not a directory")); + } + + #[cfg(unix)] + #[test] + fn test_validate_metadata_io_error() { + use std::os::unix::fs::PermissionsExt; + + let dir = TempDir::new().unwrap(); + let child = dir.path().join("child"); + std::fs::create_dir_all(&child).unwrap(); + + let mut perms = std::fs::metadata(dir.path()).unwrap().permissions(); + perms.set_mode(0o000); + std::fs::set_permissions(dir.path(), perms).unwrap(); + + let cfg = base_config(child.clone()); + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("failed to read")); + + let mut perms = std::fs::metadata(dir.path()).unwrap().permissions(); + perms.set_mode(0o700); + std::fs::set_permissions(dir.path(), perms).unwrap(); + } + + #[cfg(unix)] + #[test] + fn test_validate_not_writable() { + use std::os::unix::fs::PermissionsExt; + + let dir = TempDir::new().unwrap(); + let mut perms = std::fs::metadata(dir.path()).unwrap().permissions(); + perms.set_mode(0o400); + std::fs::set_permissions(dir.path(), perms).unwrap(); + + let cfg = base_config(dir.path().to_path_buf()); + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("not writable")); + } + + #[test] + fn test_validate_tls_key_missing() { + let dir = TempDir::new().unwrap(); + let mut cfg = base_config(dir.path().to_path_buf()); + cfg.listen = vec![ListenerConfig { + addr: "127.0.0.1:0".parse().unwrap(), + hostnames: Vec::new(), + tls_crt: Some(PathBuf::from("/tmp/does-not-matter.crt")), + tls_key: None, + }]; + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("tls_key is missing")); + } + + #[test] + fn test_validate_tls_crt_missing() { + let dir = TempDir::new().unwrap(); + let mut cfg = base_config(dir.path().to_path_buf()); + cfg.listen = vec![ListenerConfig { + addr: "127.0.0.1:0".parse().unwrap(), + hostnames: Vec::new(), + tls_crt: None, + tls_key: Some(PathBuf::from("/tmp/does-not-matter.key")), + }]; + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("tls_crt is missing")); + } + + #[test] + fn test_validate_tls_files_not_found() { + let dir = TempDir::new().unwrap(); + let mut cfg = base_config(dir.path().to_path_buf()); + cfg.listen = vec![ListenerConfig { + addr: "127.0.0.1:0".parse().unwrap(), + hostnames: Vec::new(), + tls_crt: Some(PathBuf::from("/tmp/missing.crt")), + tls_key: Some(PathBuf::from("/tmp/missing.key")), + }]; + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("TLS crtificate file not found")); + } + + #[test] + fn test_validate_tls_key_not_found() { + let dir = TempDir::new().unwrap(); + let mut cfg = base_config(dir.path().to_path_buf()); + let crt = write_temp_file(dir.path(), "cert.pem"); + cfg.listen = vec![ListenerConfig { + addr: "127.0.0.1:0".parse().unwrap(), + hostnames: Vec::new(), + tls_crt: Some(crt), + tls_key: Some(dir.path().join("missing.key")), + }]; + let err = cfg.validate().unwrap_err(); + assert!(err.to_string().contains("TLS key file not found")); + } + + #[test] + fn test_validate_tls_files_exist() { + let dir = TempDir::new().unwrap(); + let mut cfg = base_config(dir.path().to_path_buf()); + let crt = write_temp_file(dir.path(), "cert.pem"); + let key = write_temp_file(dir.path(), "key.pem"); + cfg.listen = vec![ListenerConfig { + addr: "127.0.0.1:0".parse().unwrap(), + hostnames: Vec::new(), + tls_crt: Some(crt), + tls_key: Some(key), + }]; + cfg.validate().unwrap(); + } + + #[test] + fn test_validate_ok_and_getters() { + let dir = TempDir::new().unwrap(); + let mut cfg = base_config(dir.path().to_path_buf()); + cfg.listen = vec![ListenerConfig::default()]; + + let cfg = cfg.validate().unwrap(); + assert_eq!(cfg.appname(), "valid-name"); + assert_eq!(cfg.dirname(), dir.path()); + let _ = cfg.server(); + let _ = cfg.listeners(); + let _ = cfg.telemetry(); + let _ = cfg.backends(); + } + } +} diff --git a/src/main.rs b/src/main.rs index bc364db..d708124 100644 --- a/src/main.rs +++ b/src/main.rs @@ -169,24 +169,12 @@ async fn main() { } } - let config = Arc::new(match config_path { - Some(path) => { - info!("use config file from {}", path.to_str().unwrap()); - - config::ConfigService::from_file(&path).unwrap_or_else(|e| { - error!("{e}"); - std::process::exit(1); - }) - } - None => { - info!("configuration file path not provided"); - config::ConfigService::default() - } - }); - config.validate().unwrap_or_else(|e| { - eprintln!("invalid config: {e}"); - std::process::exit(1); - }); + let config = Arc::new( + config::ConfigService::load(config_path).unwrap_or_else(|e| { + eprintln!("invalid config: {e}"); + std::process::exit(1); + }), + ); let mut telemetry = telemetry::TelemetryService::init(config.telemetry(), config.appname(), VERSION); diff --git a/src/utils.rs b/src/utils.rs index 955a492..a5ec8a4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,7 +7,7 @@ use std::time::Duration; use serde::de::{self, Deserialize, Visitor}; /// Deserializes a duration from seconds (u64). -pub fn deserialize_duration_secs<'de, D>(deserializer: D) -> Result<Duration, D::Error> +pub fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error> where D: serde::Deserializer<'de>, { @@ -15,6 +15,30 @@ where Ok(Duration::from_secs(secs)) } +#[cfg(test)] +mod deserialize_duration_tests { + use super::*; + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + struct DurationWrapper { + #[serde(deserialize_with = "deserialize_duration")] + value: Duration, + } + + #[test] + fn test_deserialize_duration() { + let value: DurationWrapper = serde_json::from_str(r#"{"value": 5}"#).unwrap(); + assert_eq!(value.value, Duration::from_secs(5)); + } + + #[test] + fn test_deserialize_duration_invalid_type() { + let err = serde_json::from_str::<DurationWrapper>(r#"{"value": "5"}"#).unwrap_err(); + assert!(err.to_string().contains("invalid type")); + } +} + /// Deserializes a u64 from either a number or a string. pub fn deserialize_size<'de, D>(deserializer: D) -> Result<u64, D::Error> where @@ -40,6 +64,42 @@ where deserializer.deserialize_any(SizeVisitor) } +#[cfg(test)] +mod deserialize_size_tests { + use super::*; + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + struct SizeWrapper { + #[serde(deserialize_with = "deserialize_size")] + value: u64, + } + + #[test] + fn test_deserialize_size_number() { + let value: SizeWrapper = serde_json::from_str(r#"{"value": 42}"#).unwrap(); + assert_eq!(value.value, 42); + } + + #[test] + fn test_deserialize_size_string() { + let value: SizeWrapper = serde_json::from_str(r#"{"value": "2048"}"#).unwrap(); + assert_eq!(value.value, 2048); + } + + #[test] + fn test_deserialize_size_invalid_string() { + let err = serde_json::from_str::<SizeWrapper>(r#"{"value": "nope"}"#).unwrap_err(); + assert!(err.to_string().contains("invalid digit")); + } + + #[test] + fn test_deserialize_size_invalid_type_uses_expectation() { + let err = serde_json::from_str::<SizeWrapper>(r#"{"value": {"k": 1}}"#).unwrap_err(); + assert!(err.to_string().contains("a number or string")); + } +} + /// Deserializes a SocketAddr from a string. pub fn deserialize_listener_addr<'de, D>(deserializer: D) -> Result<SocketAddr, D::Error> where @@ -59,3 +119,51 @@ where raw.parse::<SocketAddr>() .map_err(|err| serde::de::Error::custom(format!("invalid address '{raw}': {err}",))) } + +#[cfg(test)] +mod deserialize_listener_addr_tests { + use super::*; + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + struct AddrWrapper { + #[serde(deserialize_with = "deserialize_listener_addr")] + value: SocketAddr, + } + + #[test] + fn test_deserialize_listener_addr_localhost() { + let value: AddrWrapper = serde_json::from_str(r#"{"value": "localhost:1234"}"#).unwrap(); + assert_eq!(value.value, "127.0.0.1:1234".parse().unwrap()); + } + + #[test] + fn test_deserialize_listener_addr_trim() { + let value: AddrWrapper = serde_json::from_str(r#"{"value": " localhost:4321 "}"#).unwrap(); + assert_eq!(value.value, "127.0.0.1:4321".parse().unwrap()); + } + + #[test] + fn test_deserialize_listener_addr_ip() { + let value: AddrWrapper = serde_json::from_str(r#"{"value": "127.0.0.1:80"}"#).unwrap(); + assert_eq!(value.value, "127.0.0.1:80".parse().unwrap()); + } + + #[test] + fn test_deserialize_listener_addr_invalid() { + let err = serde_json::from_str::<AddrWrapper>(r#"{"value": "bad"}"#).unwrap_err(); + assert!(err.to_string().contains("invalid address")); + } + + #[test] + fn test_deserialize_listener_addr_localhost_invalid_port() { + let err = serde_json::from_str::<AddrWrapper>(r#"{"value": "localhost:bad"}"#).unwrap_err(); + assert!(err.to_string().contains("invalid port")); + } + + #[test] + fn test_deserialize_listener_addr_invalid_type() { + let err = serde_json::from_str::<AddrWrapper>(r#"{"value": 1234}"#).unwrap_err(); + assert!(err.to_string().contains("invalid type")); + } +} |
