aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikolay Govorov <me@govorov.online>2026-02-05 09:41:21 +0000
committerNikolay Govorov <me@govorov.online>2026-02-05 12:07:13 +0000
commit875f4ed7fdd00bbf4e287d0ba9d5124149a7df8e (patch)
tree7c8f51bd3c0a6d4421ac936950da76beb00c112e
parentfb11f922a50e9189f709e31e767199b8ac4f4ca5 (diff)
downloadtar
tar.gz
tar.bz2
tar.lz
tar.xz
tar.zst
zip
Adds code coverage checks and cleans up the config a bit
Diffstat
-rw-r--r--.github/workflows/build.yml14+14 −0
-rw-r--r--Cargo.lock438+303 −135
-rw-r--r--Cargo.toml1+1 −0
-rw-r--r--Makefile6+6 −0
-rw-r--r--src/backends/go.rs4+2 −2
-rw-r--r--src/backends/zig.rs4+2 −2
-rw-r--r--src/config.rs327+303 −24
-rw-r--r--src/main.rs24+6 −18
-rw-r--r--src/utils.rs110+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"));
+ }
+}