diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 00000000..2bc50cc5 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,4 @@ +[advisories] +ignore = ["RUSTSEC-2024-0320", "RUSTSEC-2024-0436", "RUSTSEC-2025-0057", "RUSTSEC-2025-0046", "RUSTSEC-2025-0118"] +informational_warnings = ["unmaintained"] # warn for categories of informational advisories +severity_threshold = "low" # CVSS severity ("none", "low", "medium", "high", "critical") diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 888bfd79..fc420847 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,40 +2,48 @@ version: 2 updates: - package-ecosystem: "cargo" directory: "/" + target-branch: "develop" schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/crates/app/tests/http_search_filter" + target-branch: "develop" schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/crates/wasm-blueprints/rust/" + target-branch: "develop" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" + target-branch: "develop" schedule: interval: "monthly" - package-ecosystem: "docker" directory: "/" + target-branch: "develop" schedule: interval: "weekly" - package-ecosystem: "gomod" directory: "/crates/wasm-blueprints/golang/" + target-branch: "develop" schedule: interval: "weekly" - package-ecosystem: "rust-toolchain" directory: "/" + target-branch: "develop" schedule: interval: "weekly" - package-ecosystem: "npm" directory: "/crates/wasm-blueprints/js/" + target-branch: "develop" schedule: interval: "weekly" diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000..241c0cb4 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,18 @@ +name: Dependabot auto-merge + +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2e22c764..746e858d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,10 +87,10 @@ jobs: strategy: matrix: platforms: - - os: macos-14 + - os: macos-15 target: aarch64-apple-darwin features: "" - - os: macos-14-large + - os: macos-15-intel target: x86_64-apple-darwin features: "" - os: ubuntu-latest @@ -313,6 +313,8 @@ jobs: contents: read container: image: archlinux:latest + volumes: + - /tmp/yozefu-read-only:/tmp/yozefu-readonly:ro needs: [check] steps: - name: Checkout Actions Repository diff --git a/.github/workflows/developer-experience.yml b/.github/workflows/developer-experience.yml index 607abda8..757880dc 100644 --- a/.github/workflows/developer-experience.yml +++ b/.github/workflows/developer-experience.yml @@ -94,7 +94,7 @@ jobs: with: persist-credentials: false - name: Setup JS ${{ env.JS_VERSION }} - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ env.JS_VERSION }} - name: Setup Go ${{ env.GOLANG_VERSION }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a63dd579..d00d7987 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -68,10 +68,10 @@ jobs: strategy: matrix: platforms: - - os: macos-14 + - os: macos-15 target: aarch64-apple-darwin features: "" - - os: macos-14-large + - os: macos-15-intel target: x86_64-apple-darwin features: "" - os: ubuntu-latest diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index da66e9aa..810ca66d 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -33,6 +33,8 @@ jobs: run: git fetch --tags origin - name: Install Rust uses: dtolnay/rust-toolchain@stable + with: + components: rustc, cargo - uses: Swatinem/rust-cache@v2 - name: Configure git run: | diff --git a/Cargo.lock b/Cargo.lock index 2d3d4617..7dd3e1d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,22 +79,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -105,9 +105,9 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "apache-avro" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a033b4ced7c585199fb78ef50fca7fe2f444369ec48080c5fd072efa1a03cc7" +checksum = "36fa98bc79671c7981272d91a8753a928ff6a1cd8e4f20a44c45bd5d313840bf" dependencies = [ "bigdecimal", "bon", @@ -167,9 +167,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" dependencies = [ "axum-core", "bytes", @@ -346,15 +346,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "bytesize" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" +checksum = "c99fa31e08a43eaa5913ef68d7e01c37a2bdce6ed648168239ad33b7d30a9cd8" [[package]] name = "calloop" @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.44" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "jobserver", @@ -518,7 +518,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -540,9 +540,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" dependencies = [ "clap_builder", "clap_derive", @@ -550,9 +550,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" dependencies = [ "anstream", "anstyle", @@ -993,9 +993,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1003,9 +1003,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.83+curl-8.15.0" +version = "0.4.84+curl-8.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5830daf304027db10c82632a464879d46a3f7c4ba17a31592657ad16c719b483" +checksum = "abc4294dc41b882eaff37973c2ec3ae203d0091341ee68fbadd1d06e0c18a73b" dependencies = [ "cc", "libc", @@ -1016,6 +1016,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "current_platform" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74858bcfe44b22016cb49337d7b6f04618c58e5dbfdef61b06b8c434324a0bc" + [[package]] name = "cursor-icon" version = "1.2.0" @@ -1412,9 +1418,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" @@ -1614,9 +1620,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1629,7 +1635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.2", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1801,9 +1807,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -1869,9 +1875,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", @@ -2045,9 +2051,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade6dfcba0dfb62ad59e59e7241ec8912af34fd29e0e743e3db992bd278e8b65" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ "console 0.16.1", "portable-atomic", @@ -2117,9 +2123,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -2287,7 +2293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2308,9 +2314,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" dependencies = [ "cc", "libc", @@ -2751,9 +2757,9 @@ dependencies = [ [[package]] name = "open" -version = "5.3.2" +version = "5.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" dependencies = [ "is-wsl", "libc", @@ -2762,9 +2768,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags", "cfg-if", @@ -2803,9 +2809,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -2850,7 +2856,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2935,7 +2941,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap", - "quick-xml 0.38.3", + "quick-xml 0.38.4", "serde", "time", ] @@ -3125,9 +3131,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.3" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] @@ -3156,9 +3162,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -3541,9 +3547,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.34" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "log", "once_cell", @@ -3554,15 +3560,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.13.0" @@ -3628,9 +3625,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "chrono", "dyn-clone", @@ -3644,9 +3641,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f760a6150d45dd66ec044983c124595ae76912e77ed0b44124cb3e415cce5d9" +checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" dependencies = [ "proc-macro2", "quote", @@ -4037,9 +4034,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.108" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -4668,9 +4665,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unit-prefix" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "untrusted" @@ -4680,16 +4677,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.1.2" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" dependencies = [ "base64 0.22.1", "flate2", "log", "percent-encoding", "rustls", - "rustls-pemfile", "rustls-pki-types", "ureq-proto", "utf-8", @@ -4897,12 +4893,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" +checksum = "e01164c9dda68301e34fdae536c23ed6fe90ce6d97213ccc171eebbd3d02d6b8" dependencies = [ "leb128fmt", - "wasmparser 0.240.0", + "wasmparser 0.241.2", ] [[package]] @@ -4920,9 +4916,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" +checksum = "46d90019b1afd4b808c263e428de644f3003691f243387d30d673211ee0cb8e8" dependencies = [ "bitflags", "indexmap", @@ -5206,24 +5202,24 @@ dependencies = [ [[package]] name = "wast" -version = "240.0.0" +version = "241.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0efe1c93db4ac562b9733e3dca19ed7fc878dba29aef22245acf84f13da4a19" +checksum = "63f66e07e2ddf531fef6344dbf94d112df7c2f23ed6ffb10962e711500b8d816" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.0", - "wasm-encoder 0.240.0", + "wasm-encoder 0.241.2", ] [[package]] name = "wat" -version = "1.240.0" +version = "1.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec9b6eab7ecd4d639d78515e9ea491c9bacf494aa5eda10823bd35992cf8c1e" +checksum = "45f923705c40830af909c5dec2352ec2821202e4a66008194585e1917458a26d" dependencies = [ - "wast 240.0.0", + "wast 241.0.2", ] [[package]] @@ -5477,9 +5473,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -5504,12 +5500,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -5518,22 +5508,13 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-result" -version = "0.3.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -5542,16 +5523,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -5560,7 +5532,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5596,7 +5568,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5621,7 +5593,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -5862,7 +5834,7 @@ dependencies = [ [[package]] name = "yozefu" -version = "0.0.18" +version = "0.0.19" dependencies = [ "console-subscriber", "tokio", @@ -5871,7 +5843,7 @@ dependencies = [ [[package]] name = "yozefu-app" -version = "0.0.18" +version = "0.0.19" dependencies = [ "chrono", "directories", @@ -5893,11 +5865,12 @@ dependencies = [ [[package]] name = "yozefu-command" -version = "0.0.18" +version = "0.0.19" dependencies = [ "chrono", "clap", "const_format", + "current_platform", "extism", "futures", "futures-batch", @@ -5921,7 +5894,7 @@ dependencies = [ [[package]] name = "yozefu-lib" -version = "0.0.18" +version = "0.0.19" dependencies = [ "apache-avro", "byteorder", @@ -5943,7 +5916,7 @@ dependencies = [ [[package]] name = "yozefu-tui" -version = "0.0.18" +version = "0.0.19" dependencies = [ "bytesize", "chrono", @@ -5981,7 +5954,7 @@ dependencies = [ [[package]] name = "yozefu-wasm-types" -version = "0.0.18" +version = "0.0.19" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index f79eb104..e37307c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ default-members = [ resolver = "3" [workspace.package] -version = "0.0.18" +version = "0.0.19" edition = "2024" authors = ["Yann Prono "] readme = "README.md" @@ -33,12 +33,12 @@ license = "Apache-2.0" rust-version = "1.85.0" [workspace.dependencies] -lib = { package = "yozefu-lib", path = "crates/lib", version = "0.0.18" } -app = { package = "yozefu-app", path = "crates/app", version = "0.0.18" } -command = { package = "yozefu-command", path = "crates/command", version = "0.0.18" } -yozefu = { package = "yozefu", path = "crates/bin", version = "0.0.18" } -tui = { package = "yozefu-tui", path = "crates/tui", version = "0.0.18" } -wasm-types = { package = "wasm-types", path = "crates/wasm-types", version = "0.0.18" } +lib = { package = "yozefu-lib", path = "crates/lib", version = "0.0.19" } +app = { package = "yozefu-app", path = "crates/app", version = "0.0.19" } +command = { package = "yozefu-command", path = "crates/command", version = "0.0.19" } +yozefu = { package = "yozefu", path = "crates/bin", version = "0.0.19" } +tui = { package = "yozefu-tui", path = "crates/tui", version = "0.0.19" } +wasm-types = { package = "wasm-types", path = "crates/wasm-types", version = "0.0.19" } serde_json = { version = "1.0.145", features = ["preserve_order"] } serde = { version = "1.0.228", features = ["derive"] } strum = {version = "0.27.2" } @@ -85,3 +85,10 @@ transmute_int_to_float = "warn" transmute_ptr_to_ref = "warn" transmute_undefined_repr = "warn" missing_errors_doc = {level = "allow", priority = 2 } +deref_nullptr = "deny" +integer_to_ptr_transmutes = "deny" +invalid_value = "deny" +invalid_from_utf8 = "deny" +never_type_fallback_flowing_into_unsafe = "deny" +ptr_to_integer_transmute_in_consts = "deny" +static_mut_refs = "deny" diff --git a/Dockerfile b/Dockerfile index 75780f75..fc00f750 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,3 +46,6 @@ ENTRYPOINT ["/bin/yozf"] # # docker run --rm -it ghcr.io/maif/yozefu:latest -c localhost # configuration is located at '/home/yozefu/.config/yozefu/config.json' +# +# If you need to build the project with an alpine image: +# apk add perl musl-dev build-base clang-dev cmake git openssl-dev pkgconfig diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 045bd688..5150f1ff 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -31,7 +31,7 @@ tracing = { workspace = true } [dev-dependencies] tempfile = "3.23.0" testing_logger = "0.1.1" -schemars = { version = "1.0.5", features = ["indexmap2", "url2"] } +schemars = { version = "1.1.0", features = ["indexmap2", "url2"] } diff --git a/crates/app/src/configuration/global_config.rs b/crates/app/src/configuration/global_config.rs index 2d75e5dc..8693e668 100644 --- a/crates/app/src/configuration/global_config.rs +++ b/crates/app/src/configuration/global_config.rs @@ -1,17 +1,14 @@ //! module defining the configuration structure of the application use std::{ - collections::HashMap, fs, path::{Path, PathBuf}, }; -use directories::ProjectDirs; use indexmap::IndexMap; use itertools::Itertools; use lib::Error; use serde::{Deserialize, Serialize}; -use serde_json::Value; use crate::{ APPLICATION_NAME, @@ -33,12 +30,6 @@ pub struct GlobalConfig { /// Path of this config #[serde(skip)] pub path: PathBuf, - /// Path to the Yozefu directory containing themes, config, filters... - #[serde(skip)] - pub yozefu_directory: PathBuf, - /// The file to write logs to - #[serde(skip)] - pub logs: Option, /// A placeholder url that will be used when you want to open a kafka record in the browser #[serde(default = "default_url_template")] pub default_url_template: String, @@ -63,6 +54,8 @@ pub struct GlobalConfig { pub show_shortcuts: bool, #[serde(default = "default_export_directory")] pub export_directory: PathBuf, + /// The file to write logs to + pub log_file: Option, } fn default_url_template() -> String { @@ -85,14 +78,10 @@ fn default_show_shortcuts() -> bool { true } -impl TryFrom<&PathBuf> for GlobalConfig { - type Error = Error; - - fn try_from(path: &PathBuf) -> Result { - Ok(Self { - path: path.clone(), - yozefu_directory: Self::yozefu_directory()?, - logs: None, +impl GlobalConfig { + pub fn new(path: &Path) -> Self { + Self { + path: path.to_path_buf(), default_url_template: default_url_template(), history: EXAMPLE_PROMPTS .iter() @@ -106,23 +95,8 @@ impl TryFrom<&PathBuf> for GlobalConfig { show_shortcuts: true, export_directory: default_export_directory(), consumer: ConsumerConfig::default(), - }) - } -} - -impl GlobalConfig { - /// The default config file path - pub fn path() -> Result { - Self::yozefu_directory().map(|d| d.join("config.json")) - } - - /// The default yozefu directory containing themes, filters, config... - pub fn yozefu_directory() -> Result { - ProjectDirs::from("io", "maif", APPLICATION_NAME) - .ok_or(Error::Error( - "Failed to find the yozefu configuration directory".to_string(), - )) - .map(|e| e.config_dir().to_path_buf()) + log_file: None, + } } /// Reads a configuration file. @@ -144,39 +118,13 @@ impl GlobalConfig { e )) })?; - config.yozefu_directory = Self::yozefu_directory()?; config.path = file.to_path_buf(); Ok(config) } /// Returns the name of the logs file - pub fn logs_file(&self) -> PathBuf { - self.logs - .clone() - .unwrap_or(self.path.parent().unwrap().join("application.log")) - } - - /// Returns the name of the logs file - pub fn themes_file(&self) -> PathBuf { - self.yozefu_directory.join("themes.json") - } - - /// Returns the list of available theme names. - pub fn themes(&self) -> Vec { - let file = self.themes_file(); - let content = fs::read_to_string(file).unwrap_or("{}".to_string()); - let themes: HashMap = serde_json::from_str(&content).unwrap_or_default(); - themes - .keys() - .map(std::string::ToString::to_string) - .collect_vec() - } - - /// Returns the name of the directory containing wasm filters - pub fn filters_dir(&self) -> PathBuf { - let dir = self.yozefu_directory.join("filters"); - let _ = fs::create_dir_all(&dir); - dir + pub fn log_file(&self) -> Option { + self.log_file.clone() } /// web URL template for a given cluster diff --git a/crates/app/src/configuration/internal_config.rs b/crates/app/src/configuration/internal_config.rs index 9c58768b..8f2714a5 100644 --- a/crates/app/src/configuration/internal_config.rs +++ b/crates/app/src/configuration/internal_config.rs @@ -1,24 +1,26 @@ //! module defining the configuration of the yozefu application -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, fs, path::PathBuf}; use chrono::Local; +use lib::Error; -use crate::configuration::{ConsumerConfig, GlobalConfig, SchemaRegistryConfig}; +use crate::configuration::{ConsumerConfig, SchemaRegistryConfig, Workspace}; use super::{Configuration, yozefu_config::YozefuConfig}; #[derive(Debug, Clone)] pub struct InternalConfig { pub specific: YozefuConfig, - pub global: GlobalConfig, + workspace: Workspace, output_file: PathBuf, } impl Configuration for InternalConfig { fn kafka_config_map(&self) -> HashMap { let mut config_map: HashMap = self - .global + .workspace + .config() .default_kafka_config .clone() .into_iter() @@ -29,10 +31,10 @@ impl Configuration for InternalConfig { } impl InternalConfig { - pub fn new(specific: YozefuConfig, global: GlobalConfig) -> Self { + pub fn new(specific: YozefuConfig, workspace: Workspace) -> Self { let directory = match &specific.export_directory { Some(e) => e, - None => &global.export_directory, + None => &workspace.config().export_directory, } .clone(); @@ -49,7 +51,7 @@ impl InternalConfig { Self { specific, - global, + workspace, output_file, } } @@ -58,7 +60,7 @@ impl InternalConfig { pub fn url_template_of(&self, cluster: &str) -> String { match &self.specific.url_template() { Some(url) => url.to_string(), - None => self.global.url_template_of(cluster), + None => self.workspace.config().url_template_of(cluster), } } @@ -68,14 +70,14 @@ impl InternalConfig { /// Consumer configuration for the given cluster. pub fn consumer_config(&self, cluster: &str) -> ConsumerConfig { - self.global.consumer_config_of(cluster) + self.workspace.config().consumer_config_of(cluster) } /// Returns the schema registry configuration for the given cluster. pub fn schema_registry_config_of(&self, cluster: &str) -> Option { match &self.specific.schema_registry() { Some(schema_registry) => Some(schema_registry.clone()), - None => self.global.schema_registry_config_of(cluster), + None => self.workspace.config().schema_registry_config_of(cluster), } } @@ -83,4 +85,37 @@ impl InternalConfig { pub fn output_file(&self) -> &PathBuf { &self.output_file } + + pub fn workspace(&self) -> &Workspace { + &self.workspace + } + + pub fn history(&self) -> &[String] { + &self.workspace.config().history + } + + pub fn push_history(&mut self, prompt: &str) { + self.workspace.config.history.push(prompt.to_string()); + self.workspace.config.history.dedup(); + } + + pub fn initial_query(&self) -> &str { + &self.workspace.config.initial_query + } + + pub fn theme(&self) -> &str { + &self.workspace.config.theme + } + + pub fn save_config(&mut self) -> Result<(), Error> { + let history = &self.workspace.config.history; + if history.len() > 1000 { + self.workspace.config.history = history.iter().skip(500).cloned().collect(); + } + fs::write( + &self.workspace.config.path, + serde_json::to_string_pretty(&self.workspace.config)?, + )?; + Ok(()) + } } diff --git a/crates/app/src/configuration/mod.rs b/crates/app/src/configuration/mod.rs index 1d78d650..a5144b71 100644 --- a/crates/app/src/configuration/mod.rs +++ b/crates/app/src/configuration/mod.rs @@ -7,6 +7,7 @@ mod cluster_config; mod consumer_config; mod global_config; mod internal_config; +mod workspace; mod yozefu_config; pub use cluster_config::ClusterConfig; @@ -16,6 +17,9 @@ pub use cluster_config::SchemaRegistryConfig; pub use consumer_config::ConsumerConfig; pub use global_config::GlobalConfig; pub use internal_config::InternalConfig; +use tracing::debug; +use tracing::enabled; +pub use workspace::Workspace; pub use yozefu_config::YozefuConfig; pub trait Configuration { @@ -41,6 +45,13 @@ pub trait Configuration { config.set(key, value); } + if enabled!(tracing::Level::DEBUG) { + config.set("debug", "consumer,cgrp,topic"); + for (k, v) in config.config_map().iter() { + debug!("'{}' set to '{}'", k, v); + } + } + config } } diff --git a/crates/app/src/configuration/workspace.rs b/crates/app/src/configuration/workspace.rs new file mode 100644 index 00000000..56d7e44e --- /dev/null +++ b/crates/app/src/configuration/workspace.rs @@ -0,0 +1,99 @@ +use std::{ + collections::HashMap, + fs, + path::{Path, PathBuf}, +}; + +use directories::ProjectDirs; +use itertools::Itertools; +use lib::Error; +use serde_json::Value; + +use crate::{APPLICATION_NAME, configuration::GlobalConfig}; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(test, derive(schemars::JsonSchema))] +/// The workspace is the directory containing yozefu configuration, logs, themes, filters... +pub struct Workspace { + /// Config directory of Yozefu, the `path` is a directory + pub path: PathBuf, + /// Specific config file of Yozefu + pub(super) config: GlobalConfig, + /// Specific log file of Yozefu + log_file: PathBuf, +} + +impl Default for Workspace { + fn default() -> Self { + Self { + path: Self::yozefu_directory().unwrap(), + config: GlobalConfig::new( + &Workspace::yozefu_directory() + .unwrap() + .join(Self::CONFIG_FILENAME), + ), + log_file: Self::yozefu_directory().unwrap().join(Self::LOGS_FILENAME), + } + } +} + +impl Workspace { + pub const CONFIG_FILENAME: &str = "config.json"; + pub const LOGS_FILENAME: &str = "application.log"; + pub const THEMES_FILENAME: &str = "themes.json"; + pub const FILTERS_DIRECTORY: &str = "filters"; + + pub fn new(directory: &Path, config: GlobalConfig, log_file: PathBuf) -> Self { + Self { + path: directory.to_path_buf(), + config, + log_file, + } + } + + /// The default yozefu directory containing themes, filters, config... + fn yozefu_directory() -> Result { + ProjectDirs::from("io", "maif", APPLICATION_NAME) + .ok_or(Error::Error( + "Failed to find the yozefu configuration directory".to_string(), + )) + .map(|e| e.config_dir().to_path_buf()) + } + + /// Returns the name of config file + pub fn config_file(&self) -> PathBuf { + self.config.path.clone() + } + + /// Returns the name of the logs file + pub fn log_file(&self) -> PathBuf { + self.log_file.clone() + } + + pub fn config(&self) -> &GlobalConfig { + &self.config + } + + /// Returns the name of the logs file + pub fn themes_file(&self) -> PathBuf { + self.path.join(Self::THEMES_FILENAME) + } + + /// Returns the list of available theme names. + pub fn themes(&self) -> Vec { + let file = self.themes_file(); + let content = fs::read_to_string(file).unwrap_or("{}".to_string()); + let themes: HashMap = serde_json::from_str(&content).unwrap_or_default(); + themes + .keys() + .map(std::string::ToString::to_string) + .collect_vec() + } + + /// Returns the name of the directory containing wasm filters + pub fn filters_dir(&self) -> PathBuf { + let dir = self.path.join(Self::FILTERS_DIRECTORY); + let _ = fs::create_dir_all(&dir); + dir + } +} diff --git a/crates/app/src/configuration/yozefu_config.rs b/crates/app/src/configuration/yozefu_config.rs index fe355a80..1c02d071 100644 --- a/crates/app/src/configuration/yozefu_config.rs +++ b/crates/app/src/configuration/yozefu_config.rs @@ -10,7 +10,7 @@ use std::{collections::HashMap, path::PathBuf}; pub struct YozefuConfig { cluster: String, cluster_config: ClusterConfig, - pub logs_file: Option, + pub log_file: Option, pub export_directory: Option, } @@ -19,7 +19,7 @@ impl YozefuConfig { Self { cluster: cluster.to_string(), cluster_config, - logs_file: None, + log_file: None, export_directory: None, } } @@ -44,7 +44,7 @@ impl YozefuConfig { Self { cluster: self.cluster, cluster_config: self.cluster_config, - logs_file: self.logs_file, + log_file: self.log_file, export_directory: Some(exported_directory), } } @@ -53,7 +53,7 @@ impl YozefuConfig { Self { cluster: self.cluster, cluster_config: self.cluster_config, - logs_file: Some(logs_file), + log_file: Some(logs_file), export_directory: self.export_directory, } } @@ -67,7 +67,7 @@ impl YozefuConfig { Self { cluster: self.cluster, cluster_config: self.cluster_config.with_kafka_properties(kafka_properties), - logs_file: self.logs_file, + log_file: self.log_file, export_directory: self.export_directory, } } diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs index 6cae7508..b384e65b 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -9,3 +9,6 @@ pub mod search; pub use app::App; /// Name of the application pub const APPLICATION_NAME: &str = "yozefu"; + +/// Name of the application +pub const BINARY_NAME: &str = "yozf"; diff --git a/crates/app/tests/global_config.rs b/crates/app/tests/global_config.rs index 3ec67b66..59a38b46 100644 --- a/crates/app/tests/global_config.rs +++ b/crates/app/tests/global_config.rs @@ -9,9 +9,8 @@ fn check_backwards_compatibility() { let config = GlobalConfig { clusters: IndexMap::new(), default_url_template: String::new(), - yozefu_directory: PathBuf::new(), path: PathBuf::new(), - logs: PathBuf::from("./yozefu.log").into(), + log_file: None, initial_query: "from end - 10".to_string(), theme: "default".to_string(), default_kafka_config: IndexMap::new(), diff --git a/crates/app/tests/inputs/14433042244985688090.json b/crates/app/tests/inputs/14433042244985688090.json new file mode 100644 index 00000000..d4d759e8 --- /dev/null +++ b/crates/app/tests/inputs/14433042244985688090.json @@ -0,0 +1,16 @@ +{ + "default_url_template": "", + "initial_query": "from end - 10", + "theme": "default", + "highlighter_theme": null, + "clusters": {}, + "consumer": { + "buffer_capacity": 1000, + "timeout_in_ms": 10 + }, + "default_kafka_config": {}, + "history": [], + "show_shortcuts": false, + "export_directory": "./yozefu-exports", + "log_file": null +} \ No newline at end of file diff --git a/crates/command/Cargo.toml b/crates/command/Cargo.toml index ffc9ec31..70136605 100644 --- a/crates/command/Cargo.toml +++ b/crates/command/Cargo.toml @@ -19,7 +19,7 @@ rust-version.workspace = true [dependencies] -clap = { version = "4.5.51", features = [ +clap = { version = "4.5.52", features = [ "derive", "env", "color", @@ -28,7 +28,7 @@ clap = { version = "4.5.51", features = [ serde_json = { workspace = true } chrono = "0.4.42" strum = { workspace = true, features = ["derive", "strum_macros"] } -indicatif = { version = "0.18.2", features = ["tokio"] } +indicatif = { version = "0.18.3", features = ["tokio"] } tempfile = "3.23.0" tokio-util = "0.7.17" futures = "0.3.31" @@ -51,6 +51,7 @@ tracing = { workspace = true } tracing-subscriber = { version = "0.3.20", features = ["env-filter"]} futures-batch = "0.7.0" thousands = "0.2.0" +current_platform = "0.2.0" [features] ssl-vendored = ["rdkafka/ssl-vendored", "tui/ssl-vendored", "app/ssl-vendored"] diff --git a/crates/command/src/cli.rs b/crates/command/src/cli.rs index fd4547b4..adac9cb1 100644 --- a/crates/command/src/cli.rs +++ b/crates/command/src/cli.rs @@ -3,8 +3,10 @@ use crate::cluster::Cluster; use crate::command::{Command, MainCommand, UtilityCommands}; use crate::theme::init_themes_file; use crate::version::VERSION_MESSAGE; -use app::APPLICATION_NAME; -use app::configuration::{ClusterConfig, GlobalConfig, SchemaRegistryConfig, YozefuConfig}; +use app::configuration::{ + ClusterConfig, GlobalConfig, SchemaRegistryConfig, Workspace, YozefuConfig, +}; +use app::{APPLICATION_NAME, BINARY_NAME}; use clap::command; use lib::Error; use reqwest::Url; @@ -23,7 +25,7 @@ use indexmap::IndexMap; version = VERSION_MESSAGE, about = "A terminal user interface to navigate Kafka topics and search for Kafka records.", name = APPLICATION_NAME, - bin_name = APPLICATION_NAME, + bin_name = BINARY_NAME, display_name = APPLICATION_NAME, long_about = None, propagate_version = true, @@ -76,7 +78,7 @@ where } async fn run(&self, yozefu_config: Option) -> Result<(), TuiError> { - init_files().await?; + self.init_files().await?; match &self.subcommands { Some(c) => c.execute().await.map_err(std::convert::Into::into), None => { @@ -91,24 +93,31 @@ where } } } -} -/// Initializes a default configuration file if it does not exist. -/// The default cluster is `localhost`. -async fn init_files() -> Result<(), Error> { - init_config_file()?; - init_themes_file().await?; - Ok(()) + /// Initializes a default configuration file if it does not exist. + /// The default cluster is `localhost`. + async fn init_files(&self) -> Result<(), Error> { + let workspace = match &self.subcommands { + Some(UtilityCommands::Config(c)) => &c.global, + _ => &self.default_command.global, + } + .workspace(); + + init_config_file(&workspace)?; + init_themes_file(&workspace).await?; + Ok(()) + } } /// Initializes a default configuration file if it does not exist. /// The default cluster is `localhost`. -fn init_config_file() -> Result { - let path = GlobalConfig::path()?; +fn init_config_file(workspace: &Workspace) -> Result { + let path = workspace.config_file(); if fs::metadata(&path).is_ok() { return Ok(path); } - let mut config = GlobalConfig::try_from(&path)?; + + let mut config = GlobalConfig::new(&path); let mut localhost_config = IndexMap::new(); localhost_config.insert( "bootstrap.servers".to_string(), @@ -132,8 +141,28 @@ fn init_config_file() -> Result { }, ); - fs::create_dir_all(config.filters_dir())?; - fs::write(&path, serde_json::to_string_pretty(&config).unwrap()).unwrap(); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).map_err(|e| { + std::io::Error::new( + e.kind(), + format!("Failed to create the configuration directory '{}': {}. Check you have write permissions.", parent.display(), e) + ) + })?; + fs::create_dir_all(workspace.filters_dir()).map_err(|e| { + std::io::Error::new( + e.kind(), + format!("Failed to create the filters directory '{}': {}. Check you have write permissions.", workspace.filters_dir().display(), e) + ) + })?; + } + fs::write(&path, serde_json::to_string_pretty(&config).unwrap()) + .map_err(|e| { + std::io::Error::new( + e.kind(), + format!("Failed to initialize the configuration file '{}': {}. Check you have write permissions.", path.display(), e) + ) + })?; + #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; @@ -159,3 +188,27 @@ fn test_valid_themes() { let themes: HashMap = serde_json::from_str(content).unwrap(); assert!(themes.keys().len() >= 3) } + +#[test] +fn initialize_config_file_on_readonly_root_partition() { + let workspace = Workspace::new( + &PathBuf::from("/tmp/yozefu-readonly"), + GlobalConfig::new(&PathBuf::from("/tmp/yozefu-readonly/config.json")), + PathBuf::from("/tmp/yozefu-readonly").join(Workspace::LOGS_FILENAME), + ); + let directory = &workspace.path; + + let _ = fs::remove_dir_all(directory); + fs::create_dir_all(directory).unwrap(); + // change permissions of temp_dir to read-only + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms: fs::Permissions = fs::metadata(directory).unwrap().permissions(); + perms.set_mode(0o000); + let _ = fs::set_permissions(directory, perms); + // suppose to return an error + let result = init_config_file(&workspace); + assert!(result.is_err()); + } +} diff --git a/crates/command/src/command/config_command.rs b/crates/command/src/command/config_command.rs index 333e2531..684ecf43 100644 --- a/crates/command/src/command/config_command.rs +++ b/crates/command/src/command/config_command.rs @@ -9,7 +9,7 @@ use clap::Args; use lib::Error; use tracing::info; -use crate::command::Command; +use crate::{GlobalArgs, command::Command}; use super::configure::ConfigureSubCommand; @@ -17,6 +17,8 @@ use super::configure::ConfigureSubCommand; pub struct ConfigCommand { #[command(subcommand)] pub subcommand: Option, + #[command(flatten)] + pub global: GlobalArgs, } impl Command for ConfigCommand { @@ -25,7 +27,7 @@ impl Command for ConfigCommand { return subcommand.execute().await; } - let path = GlobalConfig::path()?; + let path = self.global.workspace().config_file(); info!("The configuration file is located at '{}'", path.display()); let config = GlobalConfig::read(&path)?; diff --git a/crates/command/src/command/configure/get_command.rs b/crates/command/src/command/configure/get_command.rs index fa004a51..2c34fd59 100644 --- a/crates/command/src/command/configure/get_command.rs +++ b/crates/command/src/command/configure/get_command.rs @@ -1,7 +1,7 @@ //! Command to fetch a property of the configuration file. use std::{collections::HashMap, fs}; -use crate::{command::Command as CliCommand, theme::update_themes}; +use crate::{GlobalArgs, command::Command as CliCommand, theme::update_themes}; use app::configuration::GlobalConfig; use clap::Args; use lib::Error; @@ -13,11 +13,14 @@ pub struct ConfigureGetCommand { /// Property you want to read. It must be a JavaScript Object Notation Pointer (RFC 6901) /// Special keywords are also supported: 'config', 'filters', 'logs' etc... property: String, + #[clap(flatten)] + pub global: GlobalArgs, } impl CliCommand for ConfigureGetCommand { async fn execute(&self) -> Result<(), Error> { - let file = GlobalConfig::path()?; + let workspace = self.global.workspace(); + let file = workspace.config_file(); let content = fs::read_to_string(&file)?; let config = serde_json::from_str::(&content)?; let mut property_name = self.property.clone(); @@ -33,7 +36,7 @@ impl CliCommand for ConfigureGetCommand { let config = GlobalConfig::read(&file)?; match self.property.as_str() { "filters" | "filter" | "fn" | "func" | "functions" => { - let paths = fs::read_dir(config.filters_dir())?; + let paths = fs::read_dir(workspace.filters_dir())?; let mut filters = HashMap::new(); for path in paths { let n = path.unwrap(); @@ -51,22 +54,22 @@ impl CliCommand for ConfigureGetCommand { "path" | "file" => println!("{}", config.path.display()), "filter_dir" | "filters_dir" | "filters-dir" | "functions-dir" | "functions_dir" | "function_dir" => { - println!("{}", config.filters_dir().display()); + println!("{}", workspace.filters_dir().display()); } - "log" | "logs" => println!("{}", config.logs_file().display()), + "log" | "logs" => println!("{}", workspace.log_file().display()), "configuration_file" | "configuration-file" | "config" | "conf" => { println!("{}", file.display()); } "directory" | "dir" => println!("{}", file.parent().unwrap().display()), "themes" => { let mut output = HashMap::new(); - let _ = update_themes().await; - output.insert("themes", serde_json::to_value(config.themes())?); + let _ = update_themes(&workspace).await; + output.insert("themes", serde_json::to_value(workspace.themes())?); output.insert("highlighter", serde_json::to_value(HIGHLIGHTER_THEMES)?); println!("{}", serde_json::to_string_pretty(&output)?); } "theme-file" | "themes-file" | "themes_file" | "theme_file" => { - println!("{}", config.themes_file().display()); + println!("{}", workspace.themes_file().display()); } _ => { return Err(Error::Error(format!( diff --git a/crates/command/src/command/configure/mod.rs b/crates/command/src/command/configure/mod.rs index bf692f89..99e33635 100644 --- a/crates/command/src/command/configure/mod.rs +++ b/crates/command/src/command/configure/mod.rs @@ -17,6 +17,8 @@ mod set_command; pub use get_command::ConfigureGetCommand; pub use set_command::ConfigureSetCommand; +use crate::GlobalArgs; + use super::default_editor; /// Command to edit the configuration file. @@ -27,6 +29,8 @@ pub struct ConfigureCommand { pub editor: Option, #[command(subcommand)] pub subcommand: Option, + #[command(flatten)] + global: GlobalArgs, } #[derive(Debug, Subcommand, Clone)] @@ -52,7 +56,7 @@ impl crate::command::Command for ConfigureCommand { return subcommand.execute().await; } - let file = GlobalConfig::path()?; + let file = self.global.workspace().config_file(); let temp_file = tempdir()?.path().join(file.file_name().expect( "the configuration path should be a file, not a directory or something else", @@ -61,10 +65,15 @@ impl crate::command::Command for ConfigureCommand { fs::copy(&file, &temp_file)?; let editor = default_editor(self.editor.as_deref()); - Command::new(editor) - .arg(&temp_file) - .status() - .expect("Something went wrong"); + + if let Err(e) = Command::new(&editor).arg(&temp_file).status() { + return Err(Error::Error(format!( + "Failed to find the editor '{}': {}.\nSpecify another editor with the option '--editor' or edit the configuration file manually '{}'", + editor, + e, + file.display() + ))); + } let new_config = fs::read_to_string(&temp_file)?; fs::remove_file(temp_file)?; diff --git a/crates/command/src/command/configure/set_command.rs b/crates/command/src/command/configure/set_command.rs index 249f3607..d617d6ed 100644 --- a/crates/command/src/command/configure/set_command.rs +++ b/crates/command/src/command/configure/set_command.rs @@ -2,7 +2,7 @@ use std::fs; -use crate::command::Command as CliCommand; +use crate::{GlobalArgs, command::Command as CliCommand}; use app::configuration::GlobalConfig; use clap::Args; use lib::Error; @@ -15,11 +15,13 @@ pub struct ConfigureSetCommand { property: String, /// Its new value value: String, + #[clap(flatten)] + pub global: GlobalArgs, } impl CliCommand for ConfigureSetCommand { async fn execute(&self) -> Result<(), Error> { - let file = GlobalConfig::path()?; + let file = self.global.workspace().config_file(); let content = fs::read_to_string(&file)?; let mut config = serde_json::from_str::(&content)?; diff --git a/crates/command/src/command/create_filter.rs b/crates/command/src/command/create_filter.rs index a1165f08..3046ae9e 100644 --- a/crates/command/src/command/create_filter.rs +++ b/crates/command/src/command/create_filter.rs @@ -142,6 +142,15 @@ async fn test_success() { tracing_subscriber::fmt().init(); + // check if connected to the internet first + if let Err(e) = reqwest::get("https://www.google.com").await { + eprintln!( + "Skipping test_success because there is no internet connection: {}", + e + ); + return; + } + let temp_dir = tempfile::tempdir().expect("Failed to create temp directory"); let command = CreateFilterCommand { language: SupportedLanguages::Rust, diff --git a/crates/command/src/command/import_filter.rs b/crates/command/src/command/import_filter.rs index 9c3a6028..26b2c0b1 100644 --- a/crates/command/src/command/import_filter.rs +++ b/crates/command/src/command/import_filter.rs @@ -1,16 +1,13 @@ //! Command to import a search filter. use std::{fs, path::PathBuf}; -use app::{ - configuration::GlobalConfig, - search::filter::{MATCHES_FUNCTION_NAME, PARSE_PARAMETERS_FUNCTION_NAME}, -}; +use app::search::filter::{MATCHES_FUNCTION_NAME, PARSE_PARAMETERS_FUNCTION_NAME}; use clap::Args; use extism::{Manifest, Plugin, Wasm}; use lib::Error; use tracing::info; -use crate::command::Command; +use crate::{GlobalArgs, command::Command}; /// Import a search filter. /// It also checks that it complies with the tool requirements. @@ -24,6 +21,8 @@ pub(crate) struct ImportFilterCommand { /// Overwrite the search filter file if it already exists #[clap(long)] force: bool, + #[command(flatten)] + global: GlobalArgs, } /// Wasm functions a search filter must expose. @@ -54,8 +53,8 @@ impl ImportFilterCommand { /// Returns the path to the wasm file. pub fn destination(&self) -> Result { let name = self.name(); - let config = GlobalConfig::read(&GlobalConfig::path()?)?; - let dir = config.filters_dir(); + let workspace = self.global.workspace(); + let dir = workspace.filters_dir(); fs::create_dir_all(&dir)?; Ok(dir.join(format!("{name}.wasm"))) } @@ -100,6 +99,7 @@ async fn test_name() { let command = ImportFilterCommand { file: temp_dir.path().join("my_filter.wasm"), filter_name: Some("my-filter".to_string()), + global: GlobalArgs::default(), force: false, }; assert_eq!(command.name(), "my-filter"); @@ -112,6 +112,7 @@ async fn test_name_from_file_path() { file: temp_dir.path().join("random.wasm"), filter_name: None, force: false, + global: GlobalArgs::default(), }; assert_eq!(command.name(), "random"); } diff --git a/crates/command/src/command/main_command.rs b/crates/command/src/command/main_command.rs index fe9ea20b..02c99dc2 100644 --- a/crates/command/src/command/main_command.rs +++ b/crates/command/src/command/main_command.rs @@ -10,7 +10,7 @@ use std::str::FromStr; use std::{fs, io}; use app::configuration::{ - ClusterConfig, Configuration, GlobalConfig, InternalConfig, YozefuConfig, + ClusterConfig, Configuration, GlobalConfig, InternalConfig, Workspace, YozefuConfig, }; use app::search::ValidSearchQuery; @@ -33,7 +33,7 @@ use crate::headless::formatter::{ }; use crate::log::{init_logging_file, init_logging_stderr}; use crate::theme::update_themes; -use crate::{APPLICATION_NAME, Cli, Cluster}; +use crate::{APPLICATION_NAME, Cli, Cluster, GlobalArgs}; fn parse_cluster(s: &str) -> Result where @@ -56,7 +56,7 @@ where /// The cluster to use cluster: T, #[clap(long)] - /// Topics to consume + /// Topics to consume, separated by commas #[clap( short, long, @@ -89,9 +89,8 @@ where #[clap(short, long)] /// Name of the file to export kafka records pub output: Option, - #[clap(long)] - /// Use a specific config file - pub config: Option, + #[command(flatten)] + pub global: GlobalArgs, #[clap(skip)] pub(crate) logs_file: Option, } @@ -150,10 +149,10 @@ where } /// Returns the search query to use. - fn query(&self, config: &GlobalConfig) -> Result { + fn query(&self, initial_query: &str) -> Result { let q = self.query.join(" ").trim().to_string(); if q.is_empty() { - return Ok(config.initial_query.clone()); + return Ok(initial_query.to_string()); } if q == "-" { @@ -178,11 +177,17 @@ where } } - fn config(&self, yozefu_config: &YozefuConfig) -> Result { - let path = self.config.clone().unwrap_or(GlobalConfig::path()?); - let mut config = GlobalConfig::read(&path)?; - config.logs.clone_from(&yozefu_config.logs_file); - Ok(config) + fn workspace(&self, yozefu_config: &YozefuConfig) -> Result { + let workspace = self.global.workspace(); + + Ok(Workspace::new( + &workspace.path, + workspace.config().clone(), + yozefu_config + .log_file + .clone() + .unwrap_or(workspace.log_file()), + )) } fn themes(file: &Path) -> Result, Error> { @@ -228,28 +233,31 @@ where } fn read_config(&self) -> Result { - match GlobalConfig::read(&GlobalConfig::path()?) { - Ok(mut config) => { - config.logs.clone_from(&self.logs_file); + let workspace = self.global.workspace(); + + match GlobalConfig::read(workspace.config_file().as_path()) { + Ok(config) => { + //config.logs.clone_from(&self.logs_file); Ok(config) } Err(e) => Err(e), } } - async fn load_theme(file: &Path, name: &str) -> Result { + async fn load_theme(workspace: &Workspace, name: &str) -> Result { + let file = &workspace.themes_file(); let mut themes = Self::themes(file)?; if !themes.contains_key(name) { info!("Theme '{name}' not found. About to update theme file."); - let _ = update_themes().await; + let _ = update_themes(workspace).await; themes = Self::themes(file)?; } let theme = match themes.get(name) { Some(theme) => theme, None => { - update_themes().await?; + update_themes(workspace).await?; warn!( "Theme '{}' not found. Available themes are [{}]. Make sure it is defined in '{}'", name, @@ -269,23 +277,22 @@ where } fn internal_config(&self, yozefu_config: &YozefuConfig) -> Result { - let config = self.config(yozefu_config)?; - Ok(InternalConfig::new(yozefu_config.clone(), config)) + let workspace = self.workspace(yozefu_config)?; + Ok(InternalConfig::new(yozefu_config.clone(), workspace)) } /// Starts the app in TUI mode async fn tui(&self, yozefu_config: &YozefuConfig) -> Result<(), TuiError> { let cluster = self.cluster(); let internal_config = self.internal_config(yozefu_config)?; - let query = self.query(&internal_config.global)?; + let query = self.query(internal_config.initial_query())?; - let _ = init_logging_file(self.debug, &internal_config.global.logs_file()); + let _ = init_logging_file(self.debug, &internal_config.workspace().log_file()); let theme_name = self .theme .clone() - .unwrap_or(internal_config.global.theme.clone()); - let color_palette = - Self::load_theme(&internal_config.global.themes_file(), &theme_name).await?; + .unwrap_or(internal_config.theme().to_string()); + let color_palette = Self::load_theme(internal_config.workspace(), &theme_name).await?; let state = State::new(&cluster.to_string(), color_palette, &internal_config); let mut ui = Ui::new( self.app(&query, internal_config)?, @@ -306,7 +313,7 @@ where /// Creates the App fn app(&self, query: &str, config: InternalConfig) -> Result { debug!("{config:?}"); - let search_query = ValidSearchQuery::from(query, &config.global.filters_dir())?; + let search_query = ValidSearchQuery::from(query, &config.workspace().filters_dir())?; //let output_file = internal_config.output_file(); Ok(App::new(self.cluster().to_string(), config, search_query)) @@ -315,7 +322,7 @@ where /// Starts the app in headless mode async fn headless(&self, yozefu_config: &YozefuConfig) -> Result<(), Error> { let internal_config = self.internal_config(yozefu_config)?; - let query = self.query(&internal_config.global)?; + let query = self.query(internal_config.initial_query())?; let progress = ProgressBar::new(0); let date = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true); diff --git a/crates/command/src/global_args.rs b/crates/command/src/global_args.rs new file mode 100644 index 00000000..3931d270 --- /dev/null +++ b/crates/command/src/global_args.rs @@ -0,0 +1,135 @@ +use app::configuration::{GlobalConfig, Workspace}; +use clap::Args; +use std::path::PathBuf; +use tracing::debug; + +#[derive(Args, Clone, Debug, Default)] +pub struct GlobalArgs { + #[arg(long, global = true)] + /// Use a specific config file + pub config_file: Option, + #[arg(long, env = "YOZEFU_CONFIG_DIR", global = true)] + /// Use a specific config directory to store the configuration, logs, search filters. + pub config_dir: Option, + #[arg(long, env = "YOZEFU_LOG_FILE", global = true)] + /// Append logs to a specific log file + pub log_file: Option, +} + +impl GlobalArgs { + pub fn workspace(&self) -> Workspace { + let default_workspace = Workspace::default(); + + let (workspace_dir, config_file) = match (&self.config_dir, &self.config_file) { + (Some(dir), Some(file)) => (dir, file), + (Some(dir), None) => (dir, &dir.join(Workspace::CONFIG_FILENAME)), + (None, Some(file)) => (&default_workspace.path, file), + (None, None) => (&default_workspace.path, &default_workspace.config_file()), + }; + + let config = GlobalConfig::read(config_file).unwrap_or(GlobalConfig::new(config_file)); + + let log_file = match &self.log_file { + Some(log_file) => log_file.clone(), + None => config + .log_file + .as_ref() + .unwrap_or(&workspace_dir.join(Workspace::LOGS_FILENAME)) + .clone(), + }; + + let workspace = Workspace::new(workspace_dir, config, log_file); + debug!("Using config directory: {}", workspace.path.display()); + workspace + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn test_config_dir_both_set() { + let args = GlobalArgs { + config_dir: Some(PathBuf::from("/tmp/config_dir")), + config_file: Some(PathBuf::from("/tmp/config_dir/config.json")), + log_file: None, + }; + let ws = args.workspace(); + assert_eq!(ws.path, PathBuf::from("/tmp/config_dir")); + assert_eq!( + ws.config_file(), + PathBuf::from("/tmp/config_dir/config.json") + ); + } + + #[test] + fn test_config_dir_only_dir() { + let args = GlobalArgs { + config_dir: Some(PathBuf::from("/tmp/config_dir")), + config_file: None, + log_file: None, + }; + let ws = args.workspace(); + assert_eq!(ws.path, PathBuf::from("/tmp/config_dir")); + // The config file should be the default for this workspace + assert_eq!( + ws.config_file(), + Workspace::new( + &PathBuf::from("/tmp/config_dir"), + GlobalConfig::new( + &args + .config_dir + .as_ref() + .unwrap() + .join(Workspace::CONFIG_FILENAME) + ), + PathBuf::from("/tmp/config_dir/application.log") + ) + .config_file() + ); + } + + #[test] + fn test_config_dir_only_file() { + let default_ws = Workspace::default(); + let args = GlobalArgs { + config_dir: None, + config_file: Some(PathBuf::from("/tmp/config_dir/config.json")), + log_file: None, + }; + let ws = args.workspace(); + assert_eq!(ws.path, default_ws.path); + assert_eq!( + ws.config_file(), + PathBuf::from("/tmp/config_dir/config.json") + ); + } + + #[test] + fn test_default() { + let default_ws = Workspace::default(); + let args = GlobalArgs { + config_dir: None, + config_file: None, + log_file: None, + }; + let ws = args.workspace(); + assert_eq!(ws.path, default_ws.path); + assert_eq!(ws.config_file(), default_ws.config_file()); + } + + #[test] + fn test_config_file_method() { + let args = GlobalArgs { + config_dir: Some(PathBuf::from("/tmp/config_dir")), + config_file: Some(PathBuf::from("/tmp/config_dir/config.json")), + log_file: None, + }; + assert_eq!( + args.workspace().config_file(), + PathBuf::from("/tmp/config_dir/config.json") + ); + } +} diff --git a/crates/command/src/headless/mod.rs b/crates/command/src/headless/mod.rs index 76ec7f48..aabec8d0 100644 --- a/crates/command/src/headless/mod.rs +++ b/crates/command/src/headless/mod.rs @@ -69,7 +69,7 @@ impl Headless { let mut schema_registry = self.app.schema_registry().clone(); let token_cloned = token.clone(); - let filters_directory = self.app.config.global.filters_dir(); + let filters_directory = self.app.config.workspace().filters_dir(); tokio::task::Builder::new() .name("headless-search-engine") .spawn(async move { diff --git a/crates/command/src/lib.rs b/crates/command/src/lib.rs index 3892c36d..2e2e017c 100644 --- a/crates/command/src/lib.rs +++ b/crates/command/src/lib.rs @@ -4,19 +4,19 @@ mod cli; mod cluster; mod command; +mod global_args; mod headless; mod log; mod theme; mod version; -use app::configuration::GlobalConfig; pub use clap::Parser; pub use cli::Cli; pub use cluster::Cluster; -use lib::Error; +pub use global_args::GlobalArgs; pub use tui::TuiError; pub use app::APPLICATION_NAME; -pub fn read_config() -> Result { - GlobalConfig::read(&GlobalConfig::path()?) -} +//pub fn read_config() -> Result { +// GlobalConfig::read(&GlobalConfig::path()?) +//} diff --git a/crates/command/src/theme.rs b/crates/command/src/theme.rs index 8606c87b..7d012f9c 100644 --- a/crates/command/src/theme.rs +++ b/crates/command/src/theme.rs @@ -1,6 +1,6 @@ use std::{fs, path::PathBuf}; -use app::configuration::GlobalConfig; +use app::configuration::Workspace; use indexmap::IndexMap; use lib::Error; use tracing::{info, warn}; @@ -11,10 +11,8 @@ const THEMES_URL: &str = /// Initializes a default configuration file if it does not exist. /// The default cluster is `localhost`. -pub(crate) async fn init_themes_file() -> Result { - let path = GlobalConfig::path()?; - let config = GlobalConfig::read(&path)?; - let path = config.themes_file(); +pub(crate) async fn init_themes_file(workspace: &Workspace) -> Result { + let path = workspace.themes_file(); if fs::metadata(&path).is_ok() { return Ok(path); } @@ -51,12 +49,10 @@ pub(crate) async fn init_themes_file() -> Result { } /// Update the themes file with the latest themes from the repository. -pub(crate) async fn update_themes() -> Result { - let path = GlobalConfig::path()?; - let config = GlobalConfig::read(&path)?; - let path = config.themes_file(); +pub(crate) async fn update_themes(workspace: &Workspace) -> Result { + let path = workspace.themes_file(); if fs::metadata(&path).is_err() { - return init_themes_file().await; + return init_themes_file(workspace).await; } let content = fs::read_to_string(&path)?; diff --git a/crates/command/src/version.rs b/crates/command/src/version.rs index 5cf96fc7..a212126d 100644 --- a/crates/command/src/version.rs +++ b/crates/command/src/version.rs @@ -8,7 +8,6 @@ //! Yann Prono //! ``` use const_format::{formatcp, str_index}; -use std::env::consts::{ARCH, OS}; #[cfg(debug_assertions)] const BUILD_TYPE: &str = "debug"; @@ -27,13 +26,18 @@ const GIT_COMMIT: &str = match option_env!("GITHUB_SHA") { }; pub(super) const VERSION_MESSAGE: &str = formatcp!( - "{} ({}:{}, {} build, {} [{}]) \n{}\n{}", + r#" + Version {} + Profile {} + Commit {} on branch {} + Target {} + Repository {} + Authors {}"#, env!("CARGO_PKG_VERSION"), - GIT_BRANCH, - str_index!(GIT_COMMIT, 0..7), BUILD_TYPE, - OS, - ARCH, + str_index!(GIT_COMMIT, 0..7), + GIT_BRANCH, + current_platform::CURRENT_PLATFORM, env!("CARGO_PKG_REPOSITORY"), env!("CARGO_PKG_AUTHORS"), ); diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 0db4b022..b15d2f68 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -23,7 +23,7 @@ fuzzydate = {version = "0.2.3", optional = true } nom = "8.0.0" rdkafka = { version = "0.38.0", features = [], optional = true} url = "2.5.7" -apache-avro = "0.20.0" +apache-avro = "0.21.0" reqwest = { version = "0.12.24", features = ["json"] } byteorder = "1.5.0" @@ -31,7 +31,7 @@ byteorder = "1.5.0" insta = { version = "1.43.2", features = ["filters", "glob"] } protobuf = "3.7.2" tokio = { version = "1.48.0", features = ["rt", "macros"] } -schemars = { version = "1.0.5", features = ["indexmap2", "url2", "chrono04"]} +schemars = { version = "1.1.0", features = ["indexmap2", "url2", "chrono04"]} [features] native = [ diff --git a/crates/lib/src/kafka/schema.rs b/crates/lib/src/kafka/schema.rs index 1faa77f1..4f4434f8 100644 --- a/crates/lib/src/kafka/schema.rs +++ b/crates/lib/src/kafka/schema.rs @@ -11,7 +11,9 @@ pub struct SchemaId(pub u32); #[derive(Clone, Debug, Deserialize, Serialize, Hash, PartialEq, Eq)] #[serde(rename_all = "UPPERCASE")] #[cfg_attr(test, derive(schemars::JsonSchema))] +#[derive(Default)] pub enum SchemaType { + #[default] Json, Avro, Protobuf, @@ -55,12 +57,6 @@ impl SchemaId { } } -impl Default for SchemaType { - fn default() -> Self { - Self::Json - } -} - #[cfg(feature = "native")] const MAGIC_BYTE: u8 = 0; diff --git a/crates/lib/src/search/order.rs b/crates/lib/src/search/order.rs index 9dbbca48..4bd83830 100644 --- a/crates/lib/src/search/order.rs +++ b/crates/lib/src/search/order.rs @@ -20,27 +20,17 @@ pub struct OrderBy { pub keyword: OrderKeyword, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum OrderKeyword { Desc, + #[default] Asc, } -impl Default for Order { - fn default() -> Self { - Self::Timestamp - } -} - -impl Default for OrderKeyword { - fn default() -> Self { - Self::Asc - } -} - /// You can order kafka records by the following fields. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum Order { + #[default] Timestamp, Key, Value, diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index 2c0c78a1..cadb539f 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -26,11 +26,11 @@ ratatui = { version = "0.29.0", features = [ ] } crossterm = { version = "0.29.0", features = ["event-stream"] } itertools = "0.14.0" -bytesize = { version = "2.1.0" } +bytesize = { version = "2.2.0" } nom = "8.0.0" throbber-widgets-tui = { version = "0.9.0" } futures = "0.3.31" -open = "5.3.2" +open = "5.3.3" tokio-util = "0.7.17" thousands = "0.2.0" circular-buffer = "1.1.0" diff --git a/crates/tui/src/component/help_component.rs b/crates/tui/src/component/help_component.rs index 48dfaddd..812cd41a 100644 --- a/crates/tui/src/component/help_component.rs +++ b/crates/tui/src/component/help_component.rs @@ -205,15 +205,15 @@ impl Component for HelpComponent { ]), Line::from(vec![ Span::from(" Logs").bold(), - Span::from(format!(" '{}'", state.logs_file.display())) + Span::from(format!(" '{}'", state.workspace().log_file().display())) ]), Line::from(vec![ Span::from(" Filters").bold(), - Span::from(format!(" '{}'", state.filters_dir.display())) + Span::from(format!(" '{}'", state.workspace().filters_dir().display())) ]), Line::from(vec![ Span::from(" Themes").bold(), - Span::from(format!(" '{}'", state.themes_file.display())) + Span::from(format!(" '{}'", state.workspace().themes_file().display())) ]), Line::from(vec![ Span::from(" Version").bold(), diff --git a/crates/tui/src/component/mod.rs b/crates/tui/src/component/mod.rs index 90bef138..cb932693 100644 --- a/crates/tui/src/component/mod.rs +++ b/crates/tui/src/component/mod.rs @@ -20,7 +20,7 @@ pub mod ui; mod vertical_scrollable_block; #[cfg(test)] -use app::configuration::{GlobalConfig, InternalConfig}; +use app::configuration::{GlobalConfig, InternalConfig, Workspace}; use crossterm::event::{KeyEvent, MouseEvent}; use ratatui::prelude::Stylize; use ratatui::{ @@ -47,9 +47,10 @@ pub(crate) type ConcurrentRecordsBuffer = LazyLock>>; static BUFFER: ConcurrentRecordsBuffer = LazyLock::new(|| Arc::new(Mutex::new(RecordsBuffer::new()))); -#[derive(Debug, Clone, Display, Hash, PartialEq, Eq, Deserialize, PartialOrd, Ord)] +#[derive(Debug, Clone, Display, Hash, PartialEq, Eq, Deserialize, PartialOrd, Ord, Default)] pub(crate) enum ComponentName { Records, + #[default] Topics, Header, Footer, @@ -74,12 +75,6 @@ impl ComponentName { } } -impl Default for ComponentName { - fn default() -> Self { - Self::Topics - } -} - pub(crate) trait WithHeight: Component { fn content_height(&self) -> usize { 0 @@ -145,37 +140,37 @@ pub mod records_component_test; pub mod root_component_test; #[cfg(test)] -pub fn default_global_config() -> GlobalConfig { - use app::configuration::{ConsumerConfig, GlobalConfig}; +pub fn default_workspace() -> Workspace { + use app::configuration::{ConsumerConfig, Workspace}; let temp_dir = tempfile::tempdir().unwrap(); let temp_path = temp_dir.path().to_path_buf(); - GlobalConfig { - path: temp_path.clone().join("config.json"), - yozefu_directory: temp_path.join("config"), - logs: None, - default_url_template: String::new(), - initial_query: String::new(), - theme: "light".to_string(), - highlighter_theme: None, - clusters: indexmap::IndexMap::default(), - default_kafka_config: indexmap::IndexMap::default(), - history: vec![], - show_shortcuts: true, - export_directory: std::path::PathBuf::from(""), - consumer: ConsumerConfig::default(), - } + Workspace::new( + &temp_path, + GlobalConfig { + path: temp_path.clone().join(Workspace::CONFIG_FILENAME), + log_file: None, + default_url_template: String::new(), + initial_query: String::new(), + theme: "light".to_string(), + highlighter_theme: None, + clusters: indexmap::IndexMap::default(), + default_kafka_config: indexmap::IndexMap::default(), + history: vec![], + show_shortcuts: true, + export_directory: std::path::PathBuf::from(""), + consumer: ConsumerConfig::default(), + }, + temp_path.join(Workspace::LOGS_FILENAME), + ) } #[cfg(test)] pub fn default_internal_config() -> InternalConfig { use app::configuration::{ClusterConfig, InternalConfig}; - InternalConfig::new( - ClusterConfig::default().create("test"), - default_global_config(), - ) + InternalConfig::new(ClusterConfig::default().create("test"), default_workspace()) } #[cfg(test)] diff --git a/crates/tui/src/component/mod_test.rs b/crates/tui/src/component/mod_test.rs index 438b5af8..fc7e02ad 100644 --- a/crates/tui/src/component/mod_test.rs +++ b/crates/tui/src/component/mod_test.rs @@ -1,11 +1,11 @@ #[macro_export] macro_rules! assert_draw { ($component:expr, $width:expr, $height:expr) => {{ - use app::configuration::GlobalConfig; use app::configuration::InternalConfig; use app::configuration::ClusterConfig; use insta::assert_snapshot; - use app::configuration::ConsumerConfig; + use $crate::component::default_workspace; + use ratatui::{Terminal, backend::TestBackend}; use $crate::{State, Theme}; @@ -15,29 +15,15 @@ macro_rules! assert_draw { env::set_var("TZ", "Europe/Paris"); } - let temp_dir = tempfile::tempdir().unwrap(); - let temp_path = temp_dir.path().to_path_buf(); + let workspace = default_workspace(); + let dir = workspace.path.clone(); let state = State::new( "test", Theme::light(), &InternalConfig::new( - ClusterConfig::default().create("test").with_exported_directory(temp_path.clone()), - GlobalConfig { - path: temp_path.clone().join("config.json"), - yozefu_directory: temp_path.join("config"), - logs: None, - default_url_template: String::new(), - initial_query: String::new(), - theme: "light".to_string(), - highlighter_theme: None, - clusters: indexmap::IndexMap::default(), - default_kafka_config: indexmap::IndexMap::default(), - history: vec![], - show_shortcuts: true, - export_directory: temp_path.clone(), - consumer: ConsumerConfig::default(), - } + ClusterConfig::default().create("test").with_exported_directory(dir.clone()), + workspace ) ); @@ -48,8 +34,9 @@ macro_rules! assert_draw { println!("Snapshot path: {}", &state.config.output_file().display()); + println!("workspace is '{}'", &dir.display()); insta::with_settings!({filters => vec![ - (format!("{}[a-z0-9T\\-\\/\\.+\\s']*", temp_path.display().to_string()).as_str(), "[PATH]"), + (format!("{}[a-z0-9T\\-\\/\\.+\\s']*", dir.display().to_string()).as_str(), "[PATH]"), (&format!("v{}", env!("CARGO_PKG_VERSION")), "[VERSION]"), ]}, { assert_snapshot!(terminal.backend()); diff --git a/crates/tui/src/component/root_component.rs b/crates/tui/src/component/root_component.rs index 3b4c36a2..8d4934c8 100644 --- a/crates/tui/src/component/root_component.rs +++ b/crates/tui/src/component/root_component.rs @@ -1,6 +1,5 @@ //! This component is handles the main layout of the TUI //! and renders components based on the current context. -use app::configuration::GlobalConfig; use copypasta::{ClipboardContext, ClipboardProvider}; use std::{ collections::HashMap, @@ -46,10 +45,10 @@ impl RootComponent { pub fn new( query: &str, selected_topics: Vec, - config: &GlobalConfig, records: &'static ConcurrentRecordsBuffer, state: State, ) -> Self { + let config = state.workspace().config(); let buffer_rx = records.lock().map(|e| e.channels.clone().1).ok().unwrap(); let mut footer = FooterComponent::default(); footer.show_shortcuts(config.show_shortcuts); @@ -66,7 +65,7 @@ impl RootComponent { Arc::new(Mutex::new(SearchComponent::new( query, config.history.clone(), - config.filters_dir(), + state.workspace().filters_dir(), ))), Arc::new(Mutex::new(footer)), Arc::new(Mutex::new(HelpComponent::default())), diff --git a/crates/tui/src/component/root_component_test.rs b/crates/tui/src/component/root_component_test.rs index 8bfe58cd..da94b9b9 100644 --- a/crates/tui/src/component/root_component_test.rs +++ b/crates/tui/src/component/root_component_test.rs @@ -1,7 +1,7 @@ use crate::assert_draw; use crate::component::Component; use crate::{ - component::{ConcurrentRecordsBuffer, RootComponent, default_global_config, default_state}, + component::{ConcurrentRecordsBuffer, RootComponent, default_state}, records_buffer::RecordsBuffer, }; use std::sync::{Arc, LazyLock, Mutex}; @@ -12,11 +12,9 @@ static BUFFER: ConcurrentRecordsBuffer = #[test] fn test_draw() { BUFFER.lock().unwrap().reset(); - let config = default_global_config(); let mut component = RootComponent::new( "from begin", vec!["topic1".to_string(), "topic2".to_string()], - &config, &BUFFER, default_state(), ); diff --git a/crates/tui/src/component/state.rs b/crates/tui/src/component/state.rs index 056467c0..ccd824ca 100644 --- a/crates/tui/src/component/state.rs +++ b/crates/tui/src/component/state.rs @@ -1,6 +1,6 @@ //! The state is a struct containing various information. //! It is passed to all components. -use app::configuration::InternalConfig; +use app::configuration::{InternalConfig, Workspace}; use std::path::PathBuf; use crate::{highlighter::Highlighter, theme::Theme}; @@ -13,11 +13,9 @@ pub struct State { pub cluster: String, pub themes: Vec, pub theme: Theme, + pub internal_config: InternalConfig, pub highlighter_theme: Option, pub configuration_file: PathBuf, - pub logs_file: PathBuf, - pub themes_file: PathBuf, - pub filters_dir: PathBuf, pub config: InternalConfig, } @@ -30,17 +28,19 @@ impl State { theme, highlighter_theme: Highlighter::theme( temp.as_deref(), - config.global.highlighter_theme.as_deref(), + config.workspace().config().highlighter_theme.as_deref(), ), - themes: config.global.themes(), - themes_file: config.global.themes_file(), - configuration_file: config.global.path.clone(), - logs_file: config.global.logs_file(), - filters_dir: config.global.filters_dir(), + internal_config: config.clone(), + themes: config.workspace().themes(), + configuration_file: config.workspace().config_file(), config: config.clone(), } } + pub fn workspace(&self) -> &Workspace { + self.config.workspace() + } + pub(crate) fn is_focused(&self, component_name: &ComponentName) -> bool { &self.focused == component_name } diff --git a/crates/tui/src/component/ui.rs b/crates/tui/src/component/ui.rs index 65f2ce16..a4797595 100644 --- a/crates/tui/src/component/ui.rs +++ b/crates/tui/src/component/ui.rs @@ -13,7 +13,6 @@ use rdkafka::Message; use rdkafka::consumer::{Consumer, StreamConsumer}; use rdkafka::message::OwnedMessage; use std::collections::HashSet; -use std::fs; use std::time::Duration; use tokio::sync::mpsc::{self, UnboundedSender}; use tokio::time::Instant; @@ -42,31 +41,18 @@ pub struct Ui { impl Ui { pub fn new(app: App, query: &str, selected_topics: Vec, state: State) -> Self { - let config = app.config.clone(); Self { should_quit: false, worker: CancellationToken::new(), app, records: &BUFFER, topics: vec![], - root: RootComponent::new(query, selected_topics, &config.global, &BUFFER, state), + root: RootComponent::new(query, selected_topics, &BUFFER, state), records_sender: None, last_tick_key_events: Vec::new(), } } - pub fn save_config(&self) -> Result<(), TuiError> { - let mut config = self.app.config.clone(); - if config.global.history.len() > 1000 { - config.global.history = config.global.history.into_iter().skip(500).collect(); - } - fs::write( - &self.app.config.global.path, - serde_json::to_string_pretty(&self.app.config.global)?, - )?; - Ok(()) - } - pub(crate) fn create_consumer( app: &App, topics: Vec, @@ -137,7 +123,7 @@ impl Ui { let mut schema_registry = app.schema_registry().clone(); let token_cloned = token.clone(); - let filters_directory = self.app.config.global.filters_dir(); + let filters_directory = self.app.config.workspace().filters_dir(); tokio::task::Builder::new() .name("search-engine") .spawn(async move { @@ -340,9 +326,8 @@ impl Ui { while let Ok(action) = action_rx.try_recv() { match action { Action::NewSearchPrompt(ref prompt) => { - self.app.config.global.history.push(prompt.to_string()); - self.app.config.global.history.dedup(); - self.save_config()?; + self.app.config.push_history(prompt); + self.app.config.save_config()?; } Action::RequestTopicDetails(ref topics) => { self.topics_details(topics.clone(), action_tx.clone()); diff --git a/crates/wasm-blueprints/js/.dockerignore b/crates/wasm-blueprints/js/.dockerignore new file mode 100644 index 00000000..1293285e --- /dev/null +++ b/crates/wasm-blueprints/js/.dockerignore @@ -0,0 +1,6 @@ +* +!src +!tests +!Makefile +!package.json +!esbuild.js \ No newline at end of file diff --git a/crates/wasm-blueprints/js/.gitignore b/crates/wasm-blueprints/js/.gitignore index 76add878..3a8ec2b3 100644 --- a/crates/wasm-blueprints/js/.gitignore +++ b/crates/wasm-blueprints/js/.gitignore @@ -1,2 +1,3 @@ node_modules -dist \ No newline at end of file +dist +package-lock.json \ No newline at end of file diff --git a/crates/wasm-blueprints/js/Makefile b/crates/wasm-blueprints/js/Makefile index 48327f1f..b936d622 100644 --- a/crates/wasm-blueprints/js/Makefile +++ b/crates/wasm-blueprints/js/Makefile @@ -23,7 +23,7 @@ test: $(TARGET) ## Run the tests cat "tests/no-match.json" | extism call --stdin $(TARGET) matches | grep -q '"match":false' clean: ## Clean the wasm file and the target directory - rm -rf dist + rm -rf dist $(TARGET) help: ## Show this help @echo "Variables:" diff --git a/crates/wasm-blueprints/js/package-lock.json b/crates/wasm-blueprints/js/package-lock.json deleted file mode 100644 index 8880b354..00000000 --- a/crates/wasm-blueprints/js/package-lock.json +++ /dev/null @@ -1,500 +0,0 @@ -{ - "name": "yozefu-wasm-blueprints-rust", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "yozefu-wasm-blueprints-rust", - "version": "1.0.0", - "license": "BSD-3-Clause", - "devDependencies": { - "esbuild": "^0.25.12" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://nexus.maif.io/repository/npm-public/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - } - } -} diff --git a/deny.toml b/deny.toml index dd94ec89..2e7e94e3 100644 --- a/deny.toml +++ b/deny.toml @@ -69,7 +69,11 @@ feature-depth = 1 # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ - "RUSTSEC-2024-0436" # paste crate + "RUSTSEC-2025-0057", # fxhash - no longer maintained + "RUSTSEC-2024-0436", # paste crate + "RUSTSEC-2025-0046", # Host panic with fd_renumber WASIp1 function + "RUSTSEC-2024-0320", # yaml-rust is unmaintained. + "RUSTSEC-2025-0118" # Unsound API access to a WebAssembly shared linear memory #"RUSTSEC-0000-0000", #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish @@ -109,7 +113,6 @@ exceptions = [ # list { allow = ["Zlib"], crate = "foldhash" }, { allow = ["Zlib"], crate = "throbber-widgets-tui" }, - { allow = ["Zlib"], crate = "adler32" }, { allow = ["Zlib"], crate = "const_format" }, { allow = ["Zlib"], crate = "konst_macro_rules" }, { allow = ["Zlib"], crate = "const_format_proc_macros" }, @@ -157,7 +160,7 @@ registries = [ # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected -multiple-versions = "warn" +multiple-versions = "allow" # Lint level for when a crate version requirement is `*` wildcards = "allow" # The graph highlighting used when creating dotgraphs for crates @@ -213,7 +216,33 @@ deny = [ # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ - #"ansi_term@0.11.0", + "base64", + "windows_x86_64_msvc", + "windows-targets@0.52.6", + "windows_x86_64_gnu@0.53.1", + "windows-sys@ 0.59.0", + "windows-sys@ 0.60.2", + "windows-strings@0.4.2", + "windows-result@0.3.4", + "windows-link@0.1.3", + "wast@35.0.2", + "wasm-encoder@0.224.1", + "unicode-width@0.1.14", + "toml_edit@0.22.27", + "toml_datetime@0.6.11", + "toml@0.8.23", + "serde_spanned@0.6.9", + "strum_macros@0.26.4", + "thiserror-impl@1.0.69", + "thiserror@1.0.69", + "strum@0.26.3", + "rustix@0.38.44", + "rand_core@0.6.4", + "linux-raw-sys@0.4.15", + "rand_chacha@0.3.1", + "rand@0.8.5", + "quick-xml@0.37.5", + "nom@7.1.3" #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate diff --git a/docs/configuration/README.md b/docs/configuration/README.md new file mode 100644 index 00000000..74947722 --- /dev/null +++ b/docs/configuration/README.md @@ -0,0 +1,8 @@ +Work in progress + +| | Default behavior | CLI option | Environment variable | Configuration file | +| ------------------------- | --------------------------------- | --------------: | -------------------: | ---------------------------: | +| Workspace (or config dir) | `~/.config/io.maif.yozefu/` | `--config-dir` | `YOZEFU_CONFIG_DIR` | No | +| Configuration file | `${workspace}/config.json` | `--config-file` | N/A | No | +| Log file | `${workspace}/application.log` | `--log-file` | `YOZEFU_LOG_FILE` | jsonpath `/log_file` | +| Export directory | `$PWD/export-{datetime-now}.json` | `--output` | No | jsonpath `/export_directory` | diff --git a/docs/configuration/json-schema.json b/docs/configuration/json-schema.json deleted file mode 100644 index cbf9ba16..00000000 --- a/docs/configuration/json-schema.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "GlobalConfig", - "description": "Configuration of the application", - "type": "object", - "properties": { - "default_url_template": { - "description": "A placeholder url that will be used when you want to open a kafka record in the browser", - "type": "string", - "default": "http://localhost/cluster/{topic}/{partition}/{offset}" - }, - "initial_query": { - "description": "The initial search query when you start the UI", - "type": "string" - }, - "theme": { - "type": "string", - "default": "light" - }, - "clusters": { - "description": "The kafka properties for each cluster", - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/ClusterConfig" - } - }, - "default_kafka_config": { - "description": "The default kafka properties inherited for every cluster", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "history": { - "description": "History of past search queries", - "type": "array", - "items": { - "type": "string" - } - }, - "show_shortcuts": { - "description": "Show shortcuts", - "type": "boolean", - "default": true - }, - "export_directory": { - "type": "string", - "default": "./yozefu-exports" - } - }, - "required": [ - "initial_query", - "clusters", - "default_kafka_config", - "history" - ], - "$defs": { - "ClusterConfig": { - "description": "Specific configuration for a cluster", - "type": "object", - "properties": { - "url_template": { - "description": "A placeholder url that will be used when you want to open a kafka record in the browser", - "type": [ - "string", - "null" - ] - }, - "schema_registry": { - "description": "Schema registry configuration", - "anyOf": [ - { - "$ref": "#/$defs/SchemaRegistryConfig" - }, - { - "type": "null" - } - ] - }, - "kafka": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "required": [ - "kafka" - ] - }, - "SchemaRegistryConfig": { - "description": "Schema registry configuration of a given cluster", - "type": "object", - "properties": { - "url": { - "description": "Url of the schema registry", - "type": "string", - "format": "uri" - }, - "headers": { - "description": "HTTP headers to be used when communicating with the schema registry", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "default": {} - } - }, - "required": [ - "url" - ] - } - } -} \ No newline at end of file diff --git a/docs/demo/MyProducer.java b/docs/demo/MyProducer.java index 9e5ef01e..b36029ea 100644 --- a/docs/demo/MyProducer.java +++ b/docs/demo/MyProducer.java @@ -11,7 +11,7 @@ //DEPS tech.allegro.schema.json2avro:converter:0.3.0 //DEPS com.google.protobuf:protobuf-java:4.32.1 //DEPS info.picocli:picocli:4.7.7 - +//DEPS org.slf4j:slf4j-api:2.0.17 //FILES avro/key-schema.json=avro/key-schema.json //FILES avro/value-schema.json=avro/value-schema.json diff --git a/docs/json-schemas/global-config.json b/docs/json-schemas/global-config.json index 2ddfc0a2..e8ba3df6 100644 --- a/docs/json-schemas/global-config.json +++ b/docs/json-schemas/global-config.json @@ -63,6 +63,13 @@ "export_directory": { "type": "string", "default": "./yozefu-exports" + }, + "log_file": { + "description": "The file to write logs to", + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/security.md b/security.md new file mode 100644 index 00000000..a33049d0 --- /dev/null +++ b/security.md @@ -0,0 +1,31 @@ +# Security Policy + +## How To Report a Vulnerability + +If you think you have found a vulnerability in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public issues, discussions, or change requests.** + +Instead, report it using one of the following ways: + +* Report a [vulnerability](https://github.com/MAIF/yozefu/security/advisories/new) directly via private vulnerability reporting on GitHub. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + + +* The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) +* Affected version(s) +* Impact of the issue, including how an attacker might exploit the issue +* Step-by-step instructions to reproduce the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Full paths of source file(s) related to the manifestation of the issue +* Configuration required to reproduce the issue +* Log files that are related to this issue (if possible) +* Proof-of-concept or exploit code (if possible) + +This information will help us triage your report more quickly. + +## Supported Versions + +We don't maintain security updates for all versions of our software. +To ensure you are using a version free of known security vulnerabilities, please [use the latest release available on Github](https://github.com/MAIF/yozefu/releases). \ No newline at end of file