diff --git a/.github/actions/setup-integration-test-env/action.yml b/.github/actions/setup-integration-test-env/action.yml index b034ad2c2..38ec1f564 100644 --- a/.github/actions/setup-integration-test-env/action.yml +++ b/.github/actions/setup-integration-test-env/action.yml @@ -50,8 +50,6 @@ runs: if: ${{ inputs.install-viceroy == 'true' }} shell: bash # `.tool-versions` is the single source of truth for the Viceroy pin. - # The pin matters because upstream Viceroy > v0.16.4 has bumped MSRV - # beyond the rustc pin in `rust-toolchain.toml`. run: echo "viceroy-version=$(grep '^viceroy ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" - name: Set up Rust toolchain @@ -72,7 +70,7 @@ runs: - name: Install Viceroy if: ${{ inputs.install-viceroy == 'true' && steps.cache-viceroy.outputs.cache-hit != 'true' }} shell: bash - run: cargo install --git https://github.com/fastly/Viceroy --tag v${{ steps.viceroy-version.outputs.viceroy-version }} viceroy + run: cargo install viceroy --version "${{ steps.viceroy-version.outputs.viceroy-version }}" --locked --force - name: Build WASM binary if: ${{ inputs.build-wasm == 'true' }} @@ -91,7 +89,7 @@ runs: shell: bash run: | docker build -t test-wordpress:latest \ - crates/integration-tests/fixtures/frameworks/wordpress/ + crates/trusted-server-integration-tests/fixtures/frameworks/wordpress/ - name: Build Next.js test container if: ${{ inputs.build-test-images == 'true' }} @@ -100,4 +98,4 @@ runs: docker build \ --build-arg NODE_VERSION=${{ steps.node-version.outputs.node-version }} \ -t test-nextjs:latest \ - crates/integration-tests/fixtures/frameworks/nextjs/ + crates/trusted-server-integration-tests/fixtures/frameworks/nextjs/ diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dac874da3..9c5c2c93b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,7 +11,7 @@ updates: interval: "weekly" - package-ecosystem: "npm" # See documentation for possible values - directory: "crates/js/lib/" # Location of package manifests + directory: "crates/trusted-server-js/lib/" # Location of package manifests schedule: interval: "weekly" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fbe958473..bac92b82d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -26,8 +26,8 @@ Closes # - [ ] `cargo test --workspace` - [ ] `cargo clippy --workspace --all-targets --all-features -- -D warnings` - [ ] `cargo fmt --all -- --check` -- [ ] JS tests: `cd crates/js/lib && npx vitest run` -- [ ] JS format: `cd crates/js/lib && npm run format` +- [ ] JS tests: `cd crates/trusted-server-js/lib && npx vitest run` +- [ ] JS format: `cd crates/trusted-server-js/lib && npm run format` - [ ] Docs format: `cd docs && npm run format` - [ ] WASM build: `cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1` - [ ] Manual testing via `fastly compute serve` diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index b6aba137d..6d990c95e 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: crates/js/lib + working-directory: crates/trusted-server-js/lib steps: - uses: actions/checkout@v4 @@ -55,7 +55,7 @@ jobs: with: node-version: ${{ steps.node-version.outputs.node-version }} cache: "npm" - cache-dependency-path: crates/js/lib/package.json + cache-dependency-path: crates/trusted-server-js/lib/package.json - name: Install dependencies run: npm ci diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index da467583c..39be85e72 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -75,7 +75,7 @@ jobs: - name: Run integration tests run: >- cargo test - --manifest-path crates/integration-tests/Cargo.toml + --manifest-path crates/trusted-server-integration-tests/Cargo.toml --target x86_64-unknown-linux-gnu -- --include-ignored --skip test_wordpress_fastly --skip test_nextjs_fastly --test-threads=1 env: @@ -115,20 +115,20 @@ jobs: with: node-version: ${{ steps.shared-setup.outputs.node-version }} cache: npm - cache-dependency-path: crates/integration-tests/browser/package-lock.json + cache-dependency-path: crates/trusted-server-integration-tests/browser/package-lock.json - name: Install Playwright - working-directory: crates/integration-tests/browser + working-directory: crates/trusted-server-integration-tests/browser run: | npm ci npx playwright install --with-deps chromium - name: Run browser tests (Next.js) - working-directory: crates/integration-tests/browser + working-directory: crates/trusted-server-integration-tests/browser env: WASM_BINARY_PATH: ${{ env.WASM_ARTIFACT_PATH }} INTEGRATION_ORIGIN_PORT: ${{ env.ORIGIN_PORT }} - VICEROY_CONFIG_PATH: ${{ github.workspace }}/crates/integration-tests/fixtures/configs/viceroy-template.toml + VICEROY_CONFIG_PATH: ${{ github.workspace }}/crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml TEST_FRAMEWORK: nextjs PLAYWRIGHT_HTML_REPORT: playwright-report-nextjs run: npx playwright test @@ -138,16 +138,16 @@ jobs: if: always() with: name: playwright-report-nextjs - path: crates/integration-tests/browser/playwright-report-nextjs/ + path: crates/trusted-server-integration-tests/browser/playwright-report-nextjs/ retention-days: 7 - name: Run browser tests (WordPress) if: always() - working-directory: crates/integration-tests/browser + working-directory: crates/trusted-server-integration-tests/browser env: WASM_BINARY_PATH: ${{ env.WASM_ARTIFACT_PATH }} INTEGRATION_ORIGIN_PORT: ${{ env.ORIGIN_PORT }} - VICEROY_CONFIG_PATH: ${{ github.workspace }}/crates/integration-tests/fixtures/configs/viceroy-template.toml + VICEROY_CONFIG_PATH: ${{ github.workspace }}/crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml TEST_FRAMEWORK: wordpress PLAYWRIGHT_HTML_REPORT: playwright-report-wordpress run: npx playwright test @@ -157,7 +157,7 @@ jobs: if: always() with: name: playwright-report-wordpress - path: crates/integration-tests/browser/playwright-report-wordpress/ + path: crates/trusted-server-integration-tests/browser/playwright-report-wordpress/ retention-days: 7 - name: Upload Playwright traces and screenshots @@ -165,5 +165,5 @@ jobs: if: failure() with: name: playwright-traces - path: crates/integration-tests/browser/test-results/ + path: crates/trusted-server-integration-tests/browser/test-results/ retention-days: 7 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2da273aa0..a4133b574 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,8 +25,6 @@ jobs: id: viceroy-version # `.tool-versions` is the single source of truth so this workflow and # `.github/actions/setup-integration-test-env/action.yml` can't drift. - # The pin matters because upstream Viceroy > v0.16.4 has bumped MSRV - # beyond the rustc pin in `rust-toolchain.toml`. run: echo "viceroy-version=$(grep '^viceroy ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash @@ -46,7 +44,7 @@ jobs: - name: Install Viceroy if: steps.cache-viceroy.outputs.cache-hit != 'true' - run: cargo install --git https://github.com/fastly/Viceroy --tag v${{ steps.viceroy-version.outputs.viceroy-version }} viceroy + run: cargo install viceroy --version "${{ steps.viceroy-version.outputs.viceroy-version }}" --locked --force - name: Run tests run: cargo test --workspace @@ -64,7 +62,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: crates/js/lib + working-directory: crates/trusted-server-js/lib steps: - uses: actions/checkout@v4 @@ -79,7 +77,7 @@ jobs: with: node-version: ${{ steps.node-version.outputs.node-version }} cache: "npm" - cache-dependency-path: crates/js/lib/package.json + cache-dependency-path: crates/trusted-server-js/lib/package.json - name: Install dependencies run: npm ci diff --git a/.gitignore b/.gitignore index af70c452a..25e2fa11f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /bin /pkg /target -/crates/integration-tests/target +/crates/trusted-server-integration-tests/target # env .env* @@ -32,7 +32,7 @@ src/*.html /benchmark-results/** # Playwright browser tests -/crates/integration-tests/browser/node_modules/ -/crates/integration-tests/browser/test-results/ -/crates/integration-tests/browser/playwright-report/ -/crates/integration-tests/browser/.browser-test-state.json +/crates/trusted-server-integration-tests/browser/node_modules/ +/crates/trusted-server-integration-tests/browser/test-results/ +/crates/trusted-server-integration-tests/browser/playwright-report/ +/crates/trusted-server-integration-tests/browser/.browser-test-state.json diff --git a/.tool-versions b/.tool-versions index 8d8751b80..758146800 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,5 @@ -fastly 13.3.0 -rust 1.91.1 +fastly 15.1.0 +rust 1.95.0 nodejs 24.12.0 -viceroy 0.16.4 +viceroy 0.17.0 +wasmtime 44.0.1 diff --git a/AGENTS.md b/AGENTS.md index bcbcd179f..4f89735b7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ If you cannot read `CLAUDE.md`, follow these rules: 2. Keep changes minimal — do not refactor unrelated code. 3. Run `cargo test --workspace` after every code change. 4. Run `cargo fmt --all -- --check` and `cargo clippy --workspace --all-targets --all-features -- -D warnings`. -5. Run JS tests with `cd crates/js/lib && npx vitest run` when touching JS/TS code. +5. Run JS tests with `cd crates/trusted-server-js/lib && npx vitest run` when touching JS/TS code. 6. Use `error-stack` (`Report`) for error handling — not anyhow, eyre, or thiserror. 7. Use `log` macros (not `println!`) and `expect("should ...")` (not `unwrap()`). 8. Target is `wasm32-wasip1` — no Tokio or OS-specific dependencies in core crates. diff --git a/CLAUDE.md b/CLAUDE.md index f37a1ac30..9583fa2f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ real-time bidding integration, and publisher-side JavaScript injection. crates/ trusted-server-core/ # Core library — shared logic, integrations, HTML processing trusted-server-adapter-fastly/ # Fastly Compute entry point (wasm32-wasip1 binary) - js/ # TypeScript/JS build — per-integration IIFE bundles + trusted-server-js/ # TypeScript/JS build — per-integration IIFE bundles lib/ # TS source, Vitest tests, esbuild pipeline ``` @@ -26,10 +26,12 @@ Supporting files: `fastly.toml`, `trusted-server.toml`, `.env.dev`, | Tool | Version / Target | | ----------- | ---------------------------------------- | -| Rust | 1.91.1 (pinned in `rust-toolchain.toml`) | +| Rust | 1.95.0 (pinned in `rust-toolchain.toml`) | | WASM target | `wasm32-wasip1` | -| Node | LTS (for JS build) | -| Viceroy | Latest (Fastly local simulator) | +| Node | 24.12.0 (from `.tool-versions`) | +| Fastly CLI | 15.1.0 (from `.tool-versions`) | +| Viceroy | 0.17.0 (from `.tool-versions`) | +| Wasmtime | 44.0.1 (from `.tool-versions`) | --- @@ -67,22 +69,22 @@ cargo clippy --workspace --all-targets --all-features -- -D warnings cargo check # JS tests -cd crates/js/lib && npx vitest run +cd crates/trusted-server-js/lib && npx vitest run # JS format -cd crates/js/lib && npm run format +cd crates/trusted-server-js/lib && npm run format # Docs format cd docs && npm run format # JS build -cd crates/js/lib && node build-all.mjs +cd crates/trusted-server-js/lib && node build-all.mjs ``` ### Install prerequisites ```bash -cargo install viceroy # Fastly local test runtime +cargo install viceroy --version 0.17.0 --locked --force ``` --- @@ -266,7 +268,7 @@ IntegrationRegistration::builder(ID) | --------------------- | ---------------------------------------------------------- | | `fastly.toml` | Fastly service configuration and build settings | | `trusted-server.toml` | Application settings (ad servers, KV stores, ID templates) | -| `rust-toolchain.toml` | Pins Rust version to 1.91.1 | +| `rust-toolchain.toml` | Pins Rust version to 1.95.0 | | `.env.dev` | Local development environment variables | --- @@ -278,8 +280,8 @@ Every PR must pass: 1. `cargo fmt --all -- --check` 2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` 3. `cargo test --workspace` -4. JS build and test (`cd crates/js/lib && npx vitest run`) -5. JS format (`cd crates/js/lib && npm run format`) +4. JS build and test (`cd crates/trusted-server-js/lib && npx vitest run`) +5. JS format (`cd crates/trusted-server-js/lib && npm run format`) 6. Docs format (`cd docs && npm run format`) --- @@ -378,8 +380,8 @@ both runtime behavior and build/tooling changes. | `crates/trusted-server-core/src/cookies.rs` | Cookie handling | | `crates/trusted-server-core/src/consent/mod.rs` | GDPR and broader consent management | | `crates/trusted-server-core/src/http_util.rs` | HTTP abstractions and request utilities | -| `crates/js/build.rs` | Discovers dist files, generates `tsjs_modules.rs` | -| `crates/js/src/bundle.rs` | Module map, concatenation, hashing | +| `crates/trusted-server-js/build.rs` | Discovers dist files, generates `tsjs_modules.rs` | +| `crates/trusted-server-js/src/bundle.rs` | Module map, concatenation, hashing | --- diff --git a/Cargo.lock b/Cargo.lock index ed7b74869..80ff085a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -750,7 +750,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?rev=170b74b#170b74bd2c9933b7d561f7ccdb67c53b239e9527" +source = "git+https://github.com/stackpop/edgezero?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" dependencies = [ "anyhow", "async-stream", @@ -766,12 +766,13 @@ dependencies = [ "futures-util", "log", "log-fastly", + "thiserror 2.0.18", ] [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?rev=170b74b#170b74bd2c9933b7d561f7ccdb67c53b239e9527" +source = "git+https://github.com/stackpop/edgezero?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" dependencies = [ "anyhow", "async-compression", @@ -799,7 +800,7 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?rev=170b74b#170b74bd2c9933b7d561f7ccdb67c53b239e9527" +source = "git+https://github.com/stackpop/edgezero?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" dependencies = [ "log", "proc-macro2", @@ -882,9 +883,9 @@ dependencies = [ [[package]] name = "fastly" -version = "0.11.13" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f767502306f09f6dcb76302d09cd2ea8542e228d5f155166f0c2da925e16c61" +checksum = "531e4c3df48350d9f4fc95b4deaf87fd29820336b7926bb84bf460457c2a126b" dependencies = [ "anyhow", "bytes", @@ -910,9 +911,9 @@ dependencies = [ [[package]] name = "fastly-macros" -version = "0.11.13" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ae08eeeb5ed0c1a8b454fc89dca0e316e13b7889e81fc9a435503c1e84a2d7" +checksum = "cc2aef5f9690b04c8890f9a54ddb591b12b9779ec25ee0e572d207106e52e3d8" dependencies = [ "proc-macro2", "quote", @@ -921,9 +922,9 @@ dependencies = [ [[package]] name = "fastly-shared" -version = "0.11.13" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64ed1bba12ca45d1a2a80c2c55d903297adb3eeb4edc9d327c1d51ee709d404" +checksum = "080ad138403159fd366d3e0b14bb49cb0c01dc18c25095bbbd1c85e3338f5413" dependencies = [ "bitflags 1.3.2", "http", @@ -931,14 +932,15 @@ dependencies = [ [[package]] name = "fastly-sys" -version = "0.11.13" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1b82ebd99583740a074d8962ca75d7d17065b185a94e4919c3a3f2193268b6" +checksum = "de75ef193f6c29c43d667458bede648970715aedd5db2d42c2eba3ffa3ad738b" dependencies = [ "bitflags 1.3.2", "fastly-shared", + "http", "wasip2", - "wit-bindgen 0.46.0", + "wit-bindgen 0.51.0", ] [[package]] @@ -1582,9 +1584,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "log-fastly" -version = "0.11.13" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a5d864949b863161476a8129ef0322c56c77bb15f98d88991002072f497b1e" +checksum = "51dae5def13a2d557fdb63862d642f8d4641ec3773c036bb14092697b6764013" dependencies = [ "fastly", "log", @@ -3088,21 +3090,13 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -dependencies = [ - "bitflags 2.11.1", -] - [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ + "bitflags 2.11.1", "wit-bindgen-rust-macro", ] diff --git a/Cargo.toml b/Cargo.toml index 9f2f4c673..09c98ca7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,15 +3,15 @@ resolver = "2" members = [ "crates/trusted-server-core", "crates/trusted-server-adapter-fastly", - "crates/js", - "crates/openrtb", + "crates/trusted-server-js", + "crates/trusted-server-openrtb", ] -# integration-tests is intentionally excluded from workspace members because it +# trusted-server-integration-tests is intentionally excluded from workspace members because it # requires a native target (testcontainers, reqwest) while the workspace default # is wasm32-wasip1. Run it via: ./scripts/integration-tests.sh exclude = [ - "crates/integration-tests", - "crates/openrtb-codegen", + "crates/trusted-server-integration-tests", + "crates/trusted-server-openrtb-codegen", ] default-members = [ @@ -19,28 +19,6 @@ default-members = [ "crates/trusted-server-adapter-fastly", ] -[workspace.lints.clippy] -# Correctness - Force explicit error handling in production code -unwrap_used = "deny" -expect_used = "allow" # Allow expect with context message -panic = "deny" - -# Style -module_name_repetitions = "allow" -must_use_candidate = "warn" - -# Pedantic (selective) -doc_markdown = "warn" -missing_errors_doc = "warn" -missing_panics_doc = "warn" -needless_pass_by_value = "warn" # Encourage borrowing over ownership -redundant_closure_for_method_calls = "warn" - -# Restriction (selective) -print_stdout = "warn" -print_stderr = "warn" -dbg_macro = "warn" - [profile.release] debug = 1 @@ -56,12 +34,12 @@ config = "0.15.19" cookie = "0.18.1" derive_more = { version = "2.0", features = ["display", "error"] } ed25519-dalek = { version = "2.2", features = ["rand_core"] } -edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", rev = "170b74b", default-features = false } -edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", rev = "170b74b", default-features = false } -edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", rev = "170b74b", default-features = false } -edgezero-core = { git = "https://github.com/stackpop/edgezero", rev = "170b74b", default-features = false } +edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", branch = "main", default-features = false } +edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", branch = "main", default-features = false } +edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", branch = "main", default-features = false } +edgezero-core = { git = "https://github.com/stackpop/edgezero", branch = "main", default-features = false } error-stack = "0.6" -fastly = "0.11.12" +fastly = "0.12" fern = "0.7.1" flate2 = "1.1" futures = "0.3" @@ -71,7 +49,7 @@ http = "1.4.0" iab_gpp = "0.1" jose-jwk = "0.1.2" log = "0.4.29" -log-fastly = "0.11.12" +log-fastly = "0.12" lol_html = "2.7.2" matchit = "0.9" mime = "0.3" @@ -91,3 +69,25 @@ uuid = { version = "1.18", features = ["v4"] } validator = { version = "0.20", features = ["derive"] } which = "8" criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } + +[workspace.lints.clippy] +# Correctness - Force explicit error handling in production code +unwrap_used = "deny" +expect_used = "allow" # Allow expect with context message +panic = "deny" + +# Style +module_name_repetitions = "allow" +must_use_candidate = "warn" + +# Pedantic (selective) +doc_markdown = "warn" +missing_errors_doc = "warn" +missing_panics_doc = "warn" +needless_pass_by_value = "warn" # Encourage borrowing over ownership +redundant_closure_for_method_calls = "warn" + +# Restriction (selective) +print_stdout = "warn" +print_stderr = "warn" +dbg_macro = "warn" diff --git a/clippy.toml b/clippy.toml index 075649326..d767c2a6c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,40 +1,13 @@ -# Clippy configuration for trusted-server -# See: https://doc.rust-lang.org/clippy/configuration.html - -# ============================================================================= -# Complexity Thresholds -# ============================================================================= - -# Cognitive complexity threshold for functions (default: 25) -# Set to 30 to accommodate existing complex HTML/RSC processing functions -# like `create_html_processor` and `rewrite_rsc_scripts_combined`. -# Consider refactoring functions that exceed this threshold. -cognitive-complexity-threshold = 30 - -# Maximum number of lines in a function (default: 100) -# Set to 200 to allow larger handler functions that process HTTP requests -# with multiple validation steps. Prefer extracting helpers when possible. -too-many-lines-threshold = 200 - -# ============================================================================= -# Test Allowances -# ============================================================================= - -# Allow expect() in tests for clearer failure messages than unwrap() +# Clippy configuration. See https://doc.rust-lang.org/clippy/lint_configuration.html +# +# Test code uses `.unwrap()`, `.expect()`, `panic!`, `assert!`, indexing, and +# other "if this fails, the test fails" idioms by convention. We keep the +# corresponding restriction lints active in production code but exempt tests. allow-expect-in-tests = true - -# Allow unwrap() in tests where panicking on failure is acceptable +allow-indexing-slicing-in-tests = true +allow-panic-in-tests = true allow-unwrap-in-tests = true -# ============================================================================= -# Future Considerations -# ============================================================================= -# -# As the codebase matures, consider tightening these thresholds: -# - cognitive-complexity-threshold = 25 (default) -# - too-many-lines-threshold = 100 (default) -# -# Additional lints to consider enabling in Cargo.toml: -# - needless_pass_by_value = "warn" -# - redundant_closure_for_method_calls = "warn" -# - missing_const_for_fn = "warn" +# Keep current Trusted Server thresholds while strict lint cleanup is underway. +cognitive-complexity-threshold = 30 +too-many-lines-threshold = 200 diff --git a/crates/trusted-server-adapter-fastly/src/main.rs b/crates/trusted-server-adapter-fastly/src/main.rs index 9cd591cbe..04b19d2f5 100644 --- a/crates/trusted-server-adapter-fastly/src/main.rs +++ b/crates/trusted-server-adapter-fastly/src/main.rs @@ -341,8 +341,14 @@ fn build_ja4_debug_response(req: &FastlyRequest) -> FastlyResponse { .unwrap_or(FALLBACK_UNAVAILABLE); let cipher = req .get_tls_cipher_openssl_name() + .ok() + .flatten() + .unwrap_or(FALLBACK_UNAVAILABLE); + let tls_version = req + .get_tls_protocol() + .ok() + .flatten() .unwrap_or(FALLBACK_UNAVAILABLE); - let tls_version = req.get_tls_protocol().unwrap_or(FALLBACK_UNAVAILABLE); let ua = req.get_header_str("user-agent").unwrap_or(FALLBACK_NONE); let ch_mobile = req .get_header_str("sec-ch-ua-mobile") diff --git a/crates/trusted-server-adapter-fastly/src/platform.rs b/crates/trusted-server-adapter-fastly/src/platform.rs index 7e96a5455..29ff7778f 100644 --- a/crates/trusted-server-adapter-fastly/src/platform.rs +++ b/crates/trusted-server-adapter-fastly/src/platform.rs @@ -569,8 +569,12 @@ pub fn build_runtime_services( .geo(Arc::new(FastlyPlatformGeo)) .client_info(ClientInfo { client_ip: req.get_client_ip_addr(), - tls_protocol: req.get_tls_protocol().map(str::to_string), - tls_cipher: req.get_tls_cipher_openssl_name().map(str::to_string), + tls_protocol: req.get_tls_protocol().ok().flatten().map(str::to_string), + tls_cipher: req + .get_tls_cipher_openssl_name() + .ok() + .flatten() + .map(str::to_string), tls_ja4: req.get_tls_ja4().map(str::to_string), h2_fingerprint: req.get_client_h2_fingerprint().map(str::to_string), server_hostname: std::env::var("FASTLY_HOSTNAME").ok(), diff --git a/crates/trusted-server-adapter-fastly/src/route_tests.rs b/crates/trusted-server-adapter-fastly/src/route_tests.rs index 1abe16b63..c496a05ea 100644 --- a/crates/trusted-server-adapter-fastly/src/route_tests.rs +++ b/crates/trusted-server-adapter-fastly/src/route_tests.rs @@ -547,8 +547,12 @@ fn test_runtime_services_with_secret_http_client_and_geo( .geo(geo) .client_info(ClientInfo { client_ip: req.get_client_ip_addr(), - tls_protocol: req.get_tls_protocol().map(str::to_string), - tls_cipher: req.get_tls_cipher_openssl_name().map(str::to_string), + tls_protocol: req.get_tls_protocol().ok().flatten().map(str::to_string), + tls_cipher: req + .get_tls_cipher_openssl_name() + .ok() + .flatten() + .map(str::to_string), tls_ja4: req.get_tls_ja4().map(str::to_string), h2_fingerprint: req.get_client_h2_fingerprint().map(str::to_string), server_hostname: None, diff --git a/crates/trusted-server-core/Cargo.toml b/crates/trusted-server-core/Cargo.toml index 95ef3a035..2564e792e 100644 --- a/crates/trusted-server-core/Cargo.toml +++ b/crates/trusted-server-core/Cargo.toml @@ -42,8 +42,8 @@ sha2 = { workspace = true } subtle = { workspace = true } tokio = { workspace = true } toml = { workspace = true } -trusted-server-js = { path = "../js" } -trusted-server-openrtb = { path = "../openrtb" } +trusted-server-js = { path = "../trusted-server-js" } +trusted-server-openrtb = { path = "../trusted-server-openrtb" } url = { workspace = true } urlencoding = { workspace = true } uuid = { workspace = true } diff --git a/crates/trusted-server-core/benches/consent_decode.rs b/crates/trusted-server-core/benches/consent_decode.rs index 80c2e4f1f..d0978eeff 100644 --- a/crates/trusted-server-core/benches/consent_decode.rs +++ b/crates/trusted-server-core/benches/consent_decode.rs @@ -40,7 +40,7 @@ fn build_tc_bytes(vendor_count: u16, use_range_encoding: bool) -> Vec { 213 + 17 + usize::from(vendor_count) }; let total_bytes = total_bits.div_ceil(8); - let mut buf = vec![0u8; total_bytes]; + let mut buf = vec![0_u8; total_bytes]; // Version (6 bits) = 2 write_bits(&mut buf, 0, 6, 2); diff --git a/crates/trusted-server-core/build.rs b/crates/trusted-server-core/build.rs index 8a26af409..3f79c6906 100644 --- a/crates/trusted-server-core/build.rs +++ b/crates/trusted-server-core/build.rs @@ -1,7 +1,14 @@ // Build script includes source modules (`error`, `auction_config_types`, etc.) // for compile-time config validation. Not all items from those modules are used // in the build context, so `dead_code` is expected. -#![allow(clippy::unwrap_used, clippy::panic, dead_code)] +#![allow( + dead_code, + clippy::expect_used, + clippy::pedantic, + clippy::panic, + clippy::restriction, + reason = "build script validates checked-in configuration and should fail Cargo on invalid input" +)] #[path = "src/error.rs"] mod error; @@ -43,8 +50,9 @@ fn main() { // Read init config let init_config_path = Path::new(TRUSTED_SERVER_INIT_CONFIG_PATH); - let toml_content = fs::read_to_string(init_config_path) - .unwrap_or_else(|_| panic!("Failed to read {init_config_path:?}")); + let toml_content = fs::read_to_string(init_config_path).unwrap_or_else(|err| { + panic!("Failed to read {}: {err}", init_config_path.display()); + }); // Merge base TOML with environment variable overrides and write output. // Panics if admin endpoints are not covered by a handler. @@ -58,7 +66,8 @@ fn main() { let dest_path = Path::new(TRUSTED_SERVER_OUTPUT_CONFIG_PATH); let current = fs::read_to_string(dest_path).unwrap_or_default(); if current != merged_toml { - fs::write(dest_path, merged_toml) - .unwrap_or_else(|_| panic!("Failed to write {dest_path:?}")); + fs::write(dest_path, merged_toml).unwrap_or_else(|err| { + panic!("Failed to write {}: {err}", dest_path.display()); + }); } } diff --git a/crates/trusted-server-core/src/asset_image_optimizer.rs b/crates/trusted-server-core/src/asset_image_optimizer.rs index 051220ba5..bbc2393fb 100644 --- a/crates/trusted-server-core/src/asset_image_optimizer.rs +++ b/crates/trusted-server-core/src/asset_image_optimizer.rs @@ -202,8 +202,9 @@ fn normalize_offset(value: Option<&str>, config: &ImageOptimizerCropOffsetsConfi return config.default; }; for window in config.buckets.windows(2) { - let current = window[0]; - let next = window[1]; + let &[current, next] = window else { + continue; + }; let midpoint = current + (next.saturating_sub(current) / 2); if parsed < midpoint { return current; diff --git a/crates/trusted-server-core/src/auction/context.rs b/crates/trusted-server-core/src/auction/context.rs index 0e5c3ebbf..f7fd78294 100644 --- a/crates/trusted-server-core/src/auction/context.rs +++ b/crates/trusted-server-core/src/auction/context.rs @@ -57,10 +57,10 @@ pub fn build_url_with_context_params( ) -> String { let Ok(mut url) = url::Url::parse(base_url) else { log::warn!("build_url_with_context_params: failed to parse base URL, returning as-is"); - return base_url.to_string(); + return base_url.to_owned(); }; - let mut appended = 0usize; + let mut appended = 0_usize; for (context_key, param_name) in mapping { if let Some(value) = context.get(context_key) { @@ -73,10 +73,7 @@ pub fn build_url_with_context_params( } if appended > 0 { - log::info!( - "build_url_with_context_params: appended {} context query params", - appended - ); + log::info!("build_url_with_context_params: appended {appended} context query params"); } url.to_string() @@ -100,10 +97,10 @@ mod tests { #[test] fn test_build_url_with_context_params_appends_array() { let context = HashMap::from([( - "permutive_segments".to_string(), + "permutive_segments".to_owned(), ContextValue::StringList(vec!["10000001".into(), "10000003".into(), "adv".into()]), )]); - let mapping = BTreeMap::from([("permutive_segments".to_string(), "permutive".to_string())]); + let mapping = BTreeMap::from([("permutive_segments".to_owned(), "permutive".to_owned())]); let url = build_url_with_context_params( "http://localhost:6767/adserver/mediate", @@ -119,10 +116,10 @@ mod tests { #[test] fn test_build_url_with_context_params_preserves_existing_query() { let context = HashMap::from([( - "permutive_segments".to_string(), + "permutive_segments".to_owned(), ContextValue::StringList(vec!["123".into(), "adv".into()]), )]); - let mapping = BTreeMap::from([("permutive_segments".to_string(), "permutive".to_string())]); + let mapping = BTreeMap::from([("permutive_segments".to_owned(), "permutive".to_owned())]); let url = build_url_with_context_params( "http://localhost:6767/adserver/mediate?debug=true", @@ -138,7 +135,7 @@ mod tests { #[test] fn test_build_url_with_context_params_no_matching_keys() { let context = HashMap::new(); - let mapping = BTreeMap::from([("permutive_segments".to_string(), "permutive".to_string())]); + let mapping = BTreeMap::from([("permutive_segments".to_owned(), "permutive".to_owned())]); let url = build_url_with_context_params( "http://localhost:6767/adserver/mediate", @@ -151,10 +148,10 @@ mod tests { #[test] fn test_build_url_with_context_params_empty_array_skipped() { let context = HashMap::from([( - "permutive_segments".to_string(), + "permutive_segments".to_owned(), ContextValue::StringList(vec![]), )]); - let mapping = BTreeMap::from([("permutive_segments".to_string(), "permutive".to_string())]); + let mapping = BTreeMap::from([("permutive_segments".to_owned(), "permutive".to_owned())]); let url = build_url_with_context_params( "http://localhost:6767/adserver/mediate", @@ -168,17 +165,17 @@ mod tests { fn test_build_url_with_context_params_multiple_mappings() { let context = HashMap::from([ ( - "permutive_segments".to_string(), + "permutive_segments".to_owned(), ContextValue::StringList(vec!["seg1".into()]), ), ( - "lockr_ids".to_string(), + "lockr_ids".to_owned(), ContextValue::Text("lockr-abc-123".into()), ), ]); let mapping = BTreeMap::from([ - ("lockr_ids".to_string(), "lockr".to_string()), - ("permutive_segments".to_string(), "permutive".to_string()), + ("lockr_ids".to_owned(), "lockr".to_owned()), + ("permutive_segments".to_owned(), "permutive".to_owned()), ]); let url = build_url_with_context_params( @@ -192,8 +189,8 @@ mod tests { #[test] fn test_build_url_with_context_params_scalar_number() { - let context = HashMap::from([("count".to_string(), ContextValue::Number(42.0))]); - let mapping = BTreeMap::from([("count".to_string(), "n".to_string())]); + let context = HashMap::from([("count".to_owned(), ContextValue::Number(42.0))]); + let mapping = BTreeMap::from([("count".to_owned(), "n".to_owned())]); let url = build_url_with_context_params( "http://localhost:6767/adserver/mediate", diff --git a/crates/trusted-server-core/src/auction/endpoints.rs b/crates/trusted-server-core/src/auction/endpoints.rs index f265eb1bf..bc3f3aecb 100644 --- a/crates/trusted-server-core/src/auction/endpoints.rs +++ b/crates/trusted-server-core/src/auction/endpoints.rs @@ -78,7 +78,7 @@ pub async fn handle_auction( } let (parts, body) = req.into_parts(); - let body_bytes = body.into_bytes(); + let body_bytes = body.into_bytes().unwrap_or_default(); if body_bytes.len() > MAX_AUCTION_BODY_SIZE { return Response::builder() .status(StatusCode::PAYLOAD_TOO_LARGE) diff --git a/crates/trusted-server-core/src/auction/formats.rs b/crates/trusted-server-core/src/auction/formats.rs index a00af8d08..f6e6b43f4 100644 --- a/crates/trusted-server-core/src/auction/formats.rs +++ b/crates/trusted-server-core/src/auction/formats.rs @@ -441,7 +441,7 @@ mod tests { } fn response_json(response: Response) -> JsonValue { - serde_json::from_slice(&response.into_body().into_bytes()) + serde_json::from_slice(&response.into_body().into_bytes().unwrap_or_default()) .expect("should parse JSON response") } diff --git a/crates/trusted-server-core/src/auction/mod.rs b/crates/trusted-server-core/src/auction/mod.rs index ac61a4fa8..b0b0545fd 100644 --- a/crates/trusted-server-core/src/auction/mod.rs +++ b/crates/trusted-server-core/src/auction/mod.rs @@ -97,9 +97,8 @@ mod tests { } fn assert_orchestrator_error_contains(settings: &Settings, expected: &str) { - let err = match build_orchestrator(settings) { - Ok(_) => panic!("build_orchestrator should reject invalid auction providers"), - Err(err) => err, + let Err(err) = build_orchestrator(settings) else { + panic!("build_orchestrator should reject invalid auction providers"); }; assert!( err.to_string().contains(expected), diff --git a/crates/trusted-server-core/src/auction/types.rs b/crates/trusted-server-core/src/auction/types.rs index d068078f4..80314318e 100644 --- a/crates/trusted-server-core/src/auction/types.rs +++ b/crates/trusted-server-core/src/auction/types.rs @@ -273,12 +273,12 @@ mod tests { fn make_bid(bidder: &str) -> Bid { Bid { - slot_id: "slot-1".to_string(), + slot_id: "slot-1".to_owned(), price: Some(1.0), - currency: "USD".to_string(), + currency: "USD".to_owned(), creative: None, adomain: None, - bidder: bidder.to_string(), + bidder: bidder.to_owned(), width: 300, height: 250, nurl: None, diff --git a/crates/trusted-server-core/src/auction_config_types.rs b/crates/trusted-server-core/src/auction_config_types.rs index bc486ded9..11edd2778 100644 --- a/crates/trusted-server-core/src/auction_config_types.rs +++ b/crates/trusted-server-core/src/auction_config_types.rs @@ -54,14 +54,17 @@ fn default_timeout() -> u32 { } fn default_creative_store() -> String { - "creative_store".to_string() + "creative_store".to_owned() } fn default_allowed_context_keys() -> HashSet { HashSet::new() } -#[allow(dead_code)] // Methods used in runtime but not in build script +#[allow( + dead_code, + reason = "methods are used by the runtime crate but not by build.rs path inclusion" +)] impl AuctionConfig { /// Get all provider names. #[must_use] diff --git a/crates/trusted-server-core/src/auth.rs b/crates/trusted-server-core/src/auth.rs index 088d27e84..2254e3c08 100644 --- a/crates/trusted-server-core/src/auth.rs +++ b/crates/trusted-server-core/src/auth.rs @@ -34,9 +34,8 @@ pub fn enforce_basic_auth( return Ok(None); }; - let (username, password) = match extract_credentials(req) { - Some(credentials) => credentials, - None => return Ok(Some(unauthorized_response())), + let Some((username, password)) = extract_credentials(req) else { + return Ok(Some(unauthorized_response())); }; // Hash before comparing to normalise lengths — `ct_eq` on raw byte slices @@ -80,8 +79,8 @@ fn extract_credentials(req: &Request) -> Option<(String, String)> { let credentials = String::from_utf8(decoded).ok()?; let mut credentials_parts = credentials.splitn(2, ':'); - let username = credentials_parts.next()?.to_string(); - let password = credentials_parts.next()?.to_string(); + let username = credentials_parts.next()?.to_owned(); + let password = credentials_parts.next()?.to_owned(); Some((username, password)) } diff --git a/crates/trusted-server-core/src/backend.rs b/crates/trusted-server-core/src/backend.rs index 291c0863a..e7a65bb80 100644 --- a/crates/trusted-server-core/src/backend.rs +++ b/crates/trusted-server-core/src/backend.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use fastly::backend::Backend; use url::Url; @@ -27,10 +27,10 @@ fn default_port_for_scheme(scheme: &str) -> u16 { /// would generate URLs without the port when the Host header didn't include it. #[inline] fn compute_host_header(scheme: &str, host: &str, port: u16) -> String { - if port != default_port_for_scheme(scheme) { - format!("{}:{}", host, port) + if port == default_port_for_scheme(scheme) { + host.to_owned() } else { - host.to_string() + format!("{host}:{port}") } } @@ -124,17 +124,17 @@ impl<'a> BackendConfig<'a> { fn compute_name(&self) -> Result<(String, u16), Report> { if self.host.is_empty() { return Err(Report::new(TrustedServerError::Proxy { - message: "missing host".to_string(), + message: "missing host".to_owned(), })); } if self.host.chars().any(char::is_control) { return Err(Report::new(TrustedServerError::Proxy { - message: "host contains control characters".to_string(), + message: "host contains control characters".to_owned(), })); } if self.scheme.chars().any(char::is_control) { return Err(Report::new(TrustedServerError::Proxy { - message: "scheme contains control characters".to_string(), + message: "scheme contains control characters".to_owned(), })); } if let Some(host_header_override) = self.host_header_override { @@ -200,10 +200,10 @@ impl<'a> BackendConfig<'a> { let host_with_port = format!("{}:{}", self.host, target_port); - let host_header = self - .host_header_override - .map(str::to_owned) - .unwrap_or_else(|| compute_host_header(self.scheme, self.host, target_port)); + let host_header = self.host_header_override.map_or_else( + || compute_host_header(self.scheme, self.host, target_port), + str::to_owned, + ); // Target base is host[:port]; SSL is enabled only for https scheme let mut builder = Backend::builder(&backend_name, &host_with_port) @@ -219,33 +219,25 @@ impl<'a> BackendConfig<'a> { .sni_hostname(self.host) .check_certificate(self.host); } else { - log::warn!( - "INSECURE: certificate check disabled for backend: {}", - backend_name - ); + log::warn!("INSECURE: certificate check disabled for backend: {backend_name}"); } - log::info!("enable ssl for backend: {}", backend_name); + log::info!("enable ssl for backend: {backend_name}"); } match builder.finish() { Ok(_) => { - log::info!( - "created dynamic backend: {} -> {}", - backend_name, - host_with_port - ); + log::info!("created dynamic backend: {backend_name} -> {host_with_port}"); Ok(backend_name) } Err(e) => { let msg = e.to_string(); if msg.contains("NameInUse") || msg.contains("already in use") { - log::info!("reusing existing dynamic backend: {}", backend_name); + log::info!("reusing existing dynamic backend: {backend_name}"); Ok(backend_name) } else { Err(Report::new(TrustedServerError::Proxy { message: format!( - "dynamic backend creation failed ({} -> {}): {}", - backend_name, host_with_port, msg + "dynamic backend creation failed ({backend_name} -> {host_with_port}): {msg}" ), })) } @@ -264,7 +256,7 @@ impl<'a> BackendConfig<'a> { origin_url: &str, ) -> Result<(String, String, Option), Report> { let parsed_url = Url::parse(origin_url).change_context(TrustedServerError::Proxy { - message: format!("Invalid origin_url: {}", origin_url), + message: format!("Invalid origin_url: {origin_url}"), })?; let scheme = parsed_url.scheme().to_owned(); @@ -272,7 +264,7 @@ impl<'a> BackendConfig<'a> { .host_str() .ok_or_else(|| { Report::new(TrustedServerError::Proxy { - message: "Missing host in origin_url".to_string(), + message: "Missing host in origin_url".to_owned(), }) })? .to_owned(); @@ -574,7 +566,7 @@ mod tests { use std::time::Duration; let (name_a, _) = BackendConfig::new("https", "origin.example.com") - .first_byte_timeout(Duration::from_millis(2000)) + .first_byte_timeout(Duration::from_secs(2)) .compute_name() .expect("should compute name with 2000ms timeout"); let (name_b, _) = BackendConfig::new("https", "origin.example.com") diff --git a/crates/trusted-server-core/src/consent/extraction.rs b/crates/trusted-server-core/src/consent/extraction.rs index 98633baac..110ac622a 100644 --- a/crates/trusted-server-core/src/consent/extraction.rs +++ b/crates/trusted-server-core/src/consent/extraction.rs @@ -49,8 +49,7 @@ pub fn extract_consent_signals( .headers() .get(HEADER_SEC_GPC) .and_then(|v| v.to_str().ok()) - .map(|v| v.trim() == "1") - .unwrap_or(false); + .is_some_and(|v| v.trim() == "1"); RawConsentSignals { raw_tc_string, diff --git a/crates/trusted-server-core/src/consent/gpp.rs b/crates/trusted-server-core/src/consent/gpp.rs index b84413cfa..5a367af50 100644 --- a/crates/trusted-server-core/src/consent/gpp.rs +++ b/crates/trusted-server-core/src/consent/gpp.rs @@ -195,12 +195,11 @@ pub fn parse_gpp_sid_cookie(raw: &str) -> Option> { .split(',') .filter_map(|s| { let s = s.trim(); - match s.parse::() { - Ok(id) => Some(id), - Err(_) => { - log::debug!("Ignoring invalid __gpp_sid entry: {s:?}"); - None - } + if let Ok(id) = s.parse::() { + Some(id) + } else { + log::debug!("Ignoring invalid __gpp_sid entry: {s:?}"); + None } }) .collect(); diff --git a/crates/trusted-server-core/src/consent/jurisdiction.rs b/crates/trusted-server-core/src/consent/jurisdiction.rs index bcc825c54..18c600ac8 100644 --- a/crates/trusted-server-core/src/consent/jurisdiction.rs +++ b/crates/trusted-server-core/src/consent/jurisdiction.rs @@ -46,9 +46,8 @@ impl fmt::Display for Jurisdiction { /// Returns [`Jurisdiction::Unknown`] when no geo data is available. #[must_use] pub fn detect_jurisdiction(geo: Option<&GeoInfo>, config: &ConsentConfig) -> Jurisdiction { - let geo = match geo { - Some(g) => g, - None => return Jurisdiction::Unknown, + let Some(geo) = geo else { + return Jurisdiction::Unknown; }; // Check GDPR countries first (EU/EEA/UK). This ordering also resolves diff --git a/crates/trusted-server-core/src/consent/mod.rs b/crates/trusted-server-core/src/consent/mod.rs index 5d949b443..ec2e4cd3a 100644 --- a/crates/trusted-server-core/src/consent/mod.rs +++ b/crates/trusted-server-core/src/consent/mod.rs @@ -200,13 +200,14 @@ fn decode_or_warn( label: &str, decode: fn(&str) -> Result, ) -> Option { - raw.and_then(|s| match decode(s) { + let s = raw?; + match decode(s) { Ok(value) => Some(value), Err(e) => { log::warn!("Failed to decode {label}: {e}"); None } - }) + } } /// Builds a [`ConsentContext`] from previously extracted raw signals. @@ -281,9 +282,10 @@ fn has_eu_tcf_signal(raw_tc_present: bool, gpp_section_ids: Option<&[u16]>) -> b /// Returns the effective decoded TCF consent for enforcement decisions. #[must_use] fn effective_tcf(ctx: &ConsentContext) -> Option<&types::TcfConsent> { - ctx.tcf - .as_ref() - .or_else(|| ctx.gpp.as_ref().and_then(|g| g.eu_tcf.as_ref())) + ctx.tcf.as_ref().or_else(|| { + let g = ctx.gpp.as_ref()?; + g.eu_tcf.as_ref() + }) } /// Returns whether TCF consent allows EID transmission. @@ -577,7 +579,7 @@ fn log_consent_signals(signals: &RawConsentSignals) { if signals.is_empty() { log::debug!("No consent signals found on request"); } else { - log::info!("Consent signals: {}", signals); + log::info!("Consent signals: {signals}"); } } @@ -1045,7 +1047,7 @@ mod tests { }; assert!( !allows_ec_creation(&ctx), - "US state + no consent signals should block EC (spec §6.1.1: fail-closed)" + "US state + no consent signals should block EC (spec \u{a7}6.1.1: fail-closed)" ); } diff --git a/crates/trusted-server-core/src/consent/tcf.rs b/crates/trusted-server-core/src/consent/tcf.rs index 9261b5141..6d9aada13 100644 --- a/crates/trusted-server-core/src/consent/tcf.rs +++ b/crates/trusted-server-core/src/consent/tcf.rs @@ -190,22 +190,7 @@ fn decode_vendor_section( let max_vendor_id = raw_max_vendor_id.min(MAX_VENDOR_ID); let is_range = reader.read_bool(offset + 16); - if !is_range { - // Bitfield: one bit per vendor, 1..=max_vendor_id - let mut vendors = Vec::new(); - let bitfield_start = offset + 17; - for i in 0..usize::from(max_vendor_id) { - let bit_pos = bitfield_start + i; - if bit_pos >= reader.bit_len() { - break; - } - if reader.read_bool(bit_pos) { - // Vendor IDs are 1-indexed - vendors.push((i + 1) as u16); - } - } - Ok(vendors) - } else { + if is_range { // Range encoding let num_entries_offset = offset + 17; if num_entries_offset + 12 > reader.bit_len() { @@ -252,6 +237,21 @@ fn decode_vendor_section( } } Ok(vendors) + } else { + // Bitfield: one bit per vendor, 1..=max_vendor_id + let mut vendors = Vec::new(); + let bitfield_start = offset + 17; + for i in 0..usize::from(max_vendor_id) { + let bit_pos = bitfield_start + i; + if bit_pos >= reader.bit_len() { + break; + } + if reader.read_bool(bit_pos) { + // Vendor IDs are 1-indexed + vendors.push((i + 1) as u16); + } + } + Ok(vendors) } } @@ -270,9 +270,7 @@ fn vendor_section_end_offset( let max_vendor_id = reader.read_u16(offset, 16); let is_range = reader.read_bool(offset + 16); - if !is_range { - Ok(offset + 17 + usize::from(max_vendor_id)) - } else { + if is_range { let num_entries_offset = offset + 17; if num_entries_offset + 12 > reader.bit_len() { return Ok(num_entries_offset); @@ -294,6 +292,8 @@ fn vendor_section_end_offset( } } Ok(pos) + } else { + Ok(offset + 17 + usize::from(max_vendor_id)) } } @@ -445,7 +445,7 @@ mod tests { #[test] fn rejects_too_short() { - let encoded = URL_SAFE_NO_PAD.encode([0u8; 10]); // only 80 bits + let encoded = URL_SAFE_NO_PAD.encode([0_u8; 10]); // only 80 bits let result = decode_tc_string(&encoded); assert!(result.is_err(), "should reject short bitfield"); } @@ -484,7 +484,7 @@ mod tests { // + entry: isRange(1) + start(16) + end(16) = 262 bits let total_bits = core_bits + 16 + 1 + 12 + 1 + 16 + 16; let total_bytes = total_bits.div_ceil(8); - let mut buf = vec![0u8; total_bytes]; + let mut buf = vec![0_u8; total_bytes]; let mut writer = BitWriter::new(&mut buf); // Write minimal core fields (version=2, rest zeroed/defaults) @@ -544,7 +544,7 @@ mod tests { let core_bits: usize = 213; let total_bits = core_bits + 16 + 1 + 12 + 1 + 16 + 16; let total_bytes = total_bits.div_ceil(8); - let mut buf = vec![0u8; total_bytes]; + let mut buf = vec![0_u8; total_bytes]; let mut writer = BitWriter::new(&mut buf); writer.write(0, 6, 2); @@ -589,7 +589,7 @@ mod tests { // Core fields: 213 bits + 16 (maxVendorId) + 1 (isRange) + max_vendor_id (bitfield) let total_bits = 213 + 17 + usize::from(max_vendor_id); let total_bytes = total_bits.div_ceil(8); - let mut buf = vec![0u8; total_bytes]; + let mut buf = vec![0_u8; total_bytes]; let mut writer = BitWriter::new(&mut buf); diff --git a/crates/trusted-server-core/src/consent/types.rs b/crates/trusted-server-core/src/consent/types.rs index 7c721c62a..ac82aaf2e 100644 --- a/crates/trusted-server-core/src/consent/types.rs +++ b/crates/trusted-server-core/src/consent/types.rs @@ -79,13 +79,13 @@ impl fmt::Display for RawConsentSignals { write!(f, ", __gpp_sid=")?; match &self.raw_gpp_sid { - Some(s) => write!(f, "\"{}\"", s)?, + Some(s) => write!(f, "\"{s}\"")?, None => write!(f, "absent")?, } write!(f, ", us_privacy=")?; match &self.raw_us_privacy { - Some(s) => write!(f, "\"{}\"", s)?, + Some(s) => write!(f, "\"{s}\"")?, None => write!(f, "absent")?, } diff --git a/crates/trusted-server-core/src/consent/us_privacy.rs b/crates/trusted-server-core/src/consent/us_privacy.rs index eea27e40e..5537ab24c 100644 --- a/crates/trusted-server-core/src/consent/us_privacy.rs +++ b/crates/trusted-server-core/src/consent/us_privacy.rs @@ -14,7 +14,7 @@ //! //! - [IAB US Privacy String specification](https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/US%20Privacy%20String.md) -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use super::types::{ConsentDecodeError, PrivacyFlag, UsPrivacy}; @@ -34,10 +34,10 @@ pub fn decode_us_privacy(s: &str) -> Result 1u8, + '1' => 1_u8, other => { return Err(Report::new(ConsentDecodeError::InvalidUsPrivacy { - reason: format!("unsupported version '{}', expected '1'", other), + reason: format!("unsupported version '{other}', expected '1'"), })); } }; diff --git a/crates/trusted-server-core/src/consent_config.rs b/crates/trusted-server-core/src/consent_config.rs index e5fed1a9c..dc106b69d 100644 --- a/crates/trusted-server-core/src/consent_config.rs +++ b/crates/trusted-server-core/src/consent_config.rs @@ -96,7 +96,7 @@ impl ConsentConfig { let clamped = self.max_consent_age_days.clamp(1, 3650); if clamped != self.max_consent_age_days { log::warn!( - "max_consent_age_days={} is outside the valid range 1–3650; clamped to {clamped}", + "max_consent_age_days={} is outside the valid range 1\u{2013}3650; clamped to {clamped}", self.max_consent_age_days ); self.max_consent_age_days = clamped; diff --git a/crates/trusted-server-core/src/cookies.rs b/crates/trusted-server-core/src/cookies.rs index a002d9c8f..f3afd6b18 100644 --- a/crates/trusted-server-core/src/cookies.rs +++ b/crates/trusted-server-core/src/cookies.rs @@ -5,7 +5,7 @@ use cookie::{Cookie, CookieJar}; use edgezero_core::body::Body as EdgeBody; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use http::header; use http::Request; @@ -51,21 +51,18 @@ pub fn parse_cookies_to_jar(s: &str) -> CookieJar { pub fn handle_request_cookies( req: &Request, ) -> Result, Report> { - match req.headers().get(header::COOKIE) { - Some(header_value) => { - let header_value_str = - header_value - .to_str() - .change_context(TrustedServerError::InvalidHeaderValue { - message: "Cookie header contains invalid UTF-8".to_string(), - })?; - let jar = parse_cookies_to_jar(header_value_str); - Ok(Some(jar)) - } - None => { - log::debug!("No cookie header found in request"); - Ok(None) - } + if let Some(header_value) = req.headers().get(header::COOKIE) { + let header_value_str = + header_value + .to_str() + .change_context(TrustedServerError::InvalidHeaderValue { + message: "Cookie header contains invalid UTF-8".to_owned(), + })?; + let jar = parse_cookies_to_jar(header_value_str); + Ok(Some(jar)) + } else { + log::debug!("No cookie header found in request"); + Ok(None) } } diff --git a/crates/trusted-server-core/src/creative.rs b/crates/trusted-server-core/src/creative.rs index e6c897e37..93d9b5ba8 100644 --- a/crates/trusted-server-core/src/creative.rs +++ b/crates/trusted-server-core/src/creative.rs @@ -74,9 +74,9 @@ pub(super) fn to_abs(settings: &Settings, u: &str) -> Option { } if t.starts_with("//") { - Some(format!("https:{}", t)) + Some(format!("https:{t}")) } else if lower.starts_with("http://") || lower.starts_with("https://") { - Some(t.to_string()) + Some(t.to_owned()) } else { None } @@ -87,20 +87,19 @@ pub(super) fn rewrite_style_urls(settings: &Settings, style: &str) -> String { // naive url(...) rewrite for absolute/protocol-relative URLs let lower = style.to_ascii_lowercase(); let mut out = String::with_capacity(style.len() + 16); - let mut write_pos = 0usize; - let mut scan = 0usize; + let mut write_pos = 0_usize; + let mut scan = 0_usize; while let Some(off) = lower[scan..].find("url(") { let start = scan + off; let open = start + 4; // after 'url(' // write prefix including 'url(' out.push_str(&style[write_pos..open]); // find closing ')' - let close = match lower[open..].find(')') { - Some(c) => open + c, - None => { - out.push_str(&style[open..]); - return out; - } + let close = if let Some(c) = lower[open..].find(')') { + open + c + } else { + out.push_str(&style[open..]); + return out; }; // trim spaces and quotes let bytes = style.as_bytes(); @@ -123,7 +122,7 @@ pub(super) fn rewrite_style_urls(settings: &Settings, style: &str) -> String { let new_val = if let Some(abs) = to_abs(settings, url_val) { build_proxy_url(settings, &abs) } else { - url_val.to_string() + url_val.to_owned() }; if quoted { let q = style.as_bytes()[s] as char; @@ -149,7 +148,7 @@ fn build_signed_url_for( extra: &[(String, String)], ) -> String { let Ok(mut u) = url::Url::parse(clear_url) else { - return clear_url.to_string(); + return clear_url.to_owned(); }; let mut pairs: Vec<(String, String)> = u @@ -223,8 +222,8 @@ pub(super) fn proxy_if_abs(settings: &Settings, val: &str) -> Option { pub(super) fn split_srcset_candidates(s: &str) -> Vec<&str> { let bytes = s.as_bytes(); let mut items = Vec::new(); - let mut start = 0usize; - let mut i = 0usize; + let mut start = 0_usize; + let mut i = 0_usize; while i < bytes.len() { if bytes[i] == b',' { // Determine if this comma is the mediatype/data separator in a data: URL. @@ -277,12 +276,12 @@ pub(super) fn rewrite_srcset(settings: &Settings, srcset: &str) -> String { let rewritten = if let Some(abs) = to_abs(settings, url) { build_proxy_url(settings, &abs) } else { - url.to_string() + url.to_owned() }; if descriptor.is_empty() { out_items.push(rewritten); } else { - out_items.push(format!("{} {}", rewritten, descriptor)); + out_items.push(format!("{rewritten} {descriptor}")); } } out_items.join(", ") @@ -388,7 +387,7 @@ pub fn sanitize_creative_html(markup: &str) -> String { .attributes() .iter() .filter(|a| a.name().to_ascii_lowercase().starts_with("on")) - .map(|a| a.name().to_string()) + .map(|a| a.name().clone()) .collect(); for attr in &on_attrs { el.remove_attribute(attr); @@ -674,7 +673,7 @@ pub fn rewrite_creative_html(settings: &Settings, markup: &str) -> String { let _ = rewriter.write(markup.as_bytes()); let _ = rewriter.end(); - String::from_utf8(out).unwrap_or_else(|_| markup.to_string()) + String::from_utf8(out).unwrap_or_else(|_| markup.to_owned()) } /// Stream processor for creative HTML that rewrites URLs to first-party proxy. @@ -701,15 +700,14 @@ impl StreamProcessor for CreativeHtmlProcessor<'_> { fn process_chunk(&mut self, chunk: &[u8], is_last: bool) -> Result, io::Error> { if self.buffer.len() + chunk.len() > MAX_REWRITABLE_BODY_SIZE { return Err(io::Error::other(format!( - "HTML response body exceeds maximum rewritable size of {} bytes", - MAX_REWRITABLE_BODY_SIZE + "HTML response body exceeds maximum rewritable size of {MAX_REWRITABLE_BODY_SIZE} bytes" ))); } self.buffer.extend_from_slice(chunk); if is_last { let markup = String::from_utf8(std::mem::take(&mut self.buffer)) - .map_err(|e| io::Error::other(format!("Invalid UTF-8 in HTML: {}", e)))?; + .map_err(|e| io::Error::other(format!("Invalid UTF-8 in HTML: {e}")))?; let rewritten = rewrite_creative_html(self.settings, &markup); Ok(rewritten.into_bytes()) @@ -747,15 +745,14 @@ impl StreamProcessor for CreativeCssProcessor<'_> { fn process_chunk(&mut self, chunk: &[u8], is_last: bool) -> Result, io::Error> { if self.buffer.len() + chunk.len() > MAX_REWRITABLE_BODY_SIZE { return Err(io::Error::other(format!( - "CSS response body exceeds maximum rewritable size of {} bytes", - MAX_REWRITABLE_BODY_SIZE + "CSS response body exceeds maximum rewritable size of {MAX_REWRITABLE_BODY_SIZE} bytes" ))); } self.buffer.extend_from_slice(chunk); if is_last { let css = String::from_utf8(std::mem::take(&mut self.buffer)) - .map_err(|e| io::Error::other(format!("Invalid UTF-8 in CSS: {}", e)))?; + .map_err(|e| io::Error::other(format!("Invalid UTF-8 in CSS: {e}")))?; let rewritten = rewrite_css_body(self.settings, &css); Ok(rewritten.into_bytes()) @@ -777,7 +774,7 @@ mod tests { fn rewrite_srcset_attr(attr_name: &str, attr_value: &str) -> String { let settings = crate::test_support::tests::create_test_settings(); - let html = format!(r#""#, attr_name, attr_value); + let html = format!(r#""#); rewrite_creative_html(&settings, &html) } @@ -839,12 +836,11 @@ mod tests { #[test] fn injects_tsjs_creative_when_body_present() { let settings = crate::test_support::tests::create_test_settings(); - let html = r#"

hello

"#; + let html = "

hello

"; let out = rewrite_creative_html(&settings, html); assert!( out.contains("/static/tsjs=tsjs-unified.min.js"), - "expected unified tsjs injection: {}", - out + "expected unified tsjs injection: {out}" ); // Inject only once assert_eq!(out.matches("/static/tsjs=tsjs-unified.min.js").count(), 1); @@ -853,7 +849,7 @@ mod tests { #[test] fn injects_tsjs_unified_once_with_multiple_bodies() { let settings = crate::test_support::tests::create_test_settings(); - let html = r#"onetwo"#; + let html = "onetwo"; let out = rewrite_creative_html(&settings, html); assert_eq!(out.matches("/static/tsjs=tsjs-unified.min.js").count(), 1); } @@ -863,20 +859,20 @@ mod tests { let settings = crate::test_support::tests::create_test_settings(); assert_eq!( to_abs(&settings, "//cdn.example/x"), - Some("https://cdn.example/x".to_string()) + Some("https://cdn.example/x".to_owned()) ); assert_eq!( to_abs(&settings, "HTTPS://cdn.example/x"), - Some("HTTPS://cdn.example/x".to_string()) + Some("HTTPS://cdn.example/x".to_owned()) ); assert_eq!( to_abs(&settings, "http://cdn.example/x"), - Some("http://cdn.example/x".to_string()) + Some("http://cdn.example/x".to_owned()) ); assert_eq!(to_abs(&settings, "/local/x"), None); assert_eq!( to_abs(&settings, " //cdn.example/y "), - Some("https://cdn.example/y".to_string()) + Some("https://cdn.example/y".to_owned()) ); assert_eq!(to_abs(&settings, "data:image/png;base64,abcd"), None); assert_eq!(to_abs(&settings, "javascript:alert(1)"), None); @@ -888,12 +884,12 @@ mod tests { let settings = crate::test_support::tests::create_test_settings(); assert_eq!( to_abs(&settings, "//cdn.example.com:8080/asset.js"), - Some("https://cdn.example.com:8080/asset.js".to_string()), + Some("https://cdn.example.com:8080/asset.js".to_owned()), "should preserve port 8080 in protocol-relative URL" ); assert_eq!( to_abs(&settings, "//cdn.example.com:9443/img.png"), - Some("https://cdn.example.com:9443/img.png".to_string()), + Some("https://cdn.example.com:9443/img.png".to_owned()), "should preserve port 9443 in protocol-relative URL" ); } @@ -916,8 +912,7 @@ mod tests { // Port 9443 should be preserved (URL-encoded as %3A9443) assert!( out.contains("cdn.example.com%3A9443"), - "Port 9443 should be preserved in rewritten URLs: {}", - out + "Port 9443 should be preserved in rewritten URLs: {out}" ); } @@ -1037,8 +1032,7 @@ mod tests { // Two rewritten absolute candidates expected assert!( out.matches("/first-party/proxy?tsurl=").count() >= 2, - "srcset not fully rewritten: {}", - out + "srcset not fully rewritten: {out}" ); // Relative preserved assert!(out.contains("/local/img.webp 1x")); @@ -1068,7 +1062,7 @@ mod tests { "#; let out = rewrite_creative_html(&settings, html); let cnt = out.matches("/first-party/proxy?tsurl=").count(); - assert!(cnt >= 3, "expected 3 rewritten links: {}", out); + assert!(cnt >= 3, "expected 3 rewritten links: {out}"); } #[test] @@ -1141,8 +1135,7 @@ mod tests { let out = rewrite_creative_html(&settings, html); assert!( out.matches("/first-party/proxy?tsurl=").count() >= 4, - "svg hrefs not rewritten: {}", - out + "svg hrefs not rewritten: {out}" ); } @@ -1157,8 +1150,7 @@ mod tests { let out = rewrite_creative_html(&settings, html); assert!( out.matches("/first-party/proxy?tsurl=").count() >= 3, - "style url() not rewritten: {}", - out + "style url() not rewritten: {out}" ); assert!(!out.contains("https://cdn.example/bg.png")); } @@ -1166,17 +1158,16 @@ mod tests { #[test] fn rewrites_style_block_url_variants() { let settings = crate::test_support::tests::create_test_settings(); - let html = r#" + let html = " - "#; + "; let out = rewrite_creative_html(&settings, html); assert!( out.matches("/first-party/proxy?tsurl=").count() >= 2, - "style block url() not rewritten: {}", - out + "style block url() not rewritten: {out}" ); } @@ -1208,7 +1199,7 @@ mod tests { fn split_srcset_handles_no_space_after_commas() { let s = "https://cdn.example/a.png 1x,//cdn.example/b.png 2x,/local/c.png 1x"; let items = super::split_srcset_candidates(s); - assert_eq!(items.len(), 3, "{:?}", items); + assert_eq!(items.len(), 3, "{items:?}"); assert!(items[0].contains("a.png 1x")); assert!(items[1].contains("b.png 2x")); assert!(items[2].contains("/local/c.png 1x")); @@ -1218,7 +1209,7 @@ mod tests { fn split_srcset_preserves_data_url_comma() { let s = "data:image/png;base64,AAAA 1x,//cdn.example/b.png 2x"; let items = super::split_srcset_candidates(s); - assert_eq!(items.len(), 2, "{:?}", items); + assert_eq!(items.len(), 2, "{items:?}"); assert_eq!(items[0].trim(), "data:image/png;base64,AAAA 1x"); assert!(items[1].trim().starts_with("//cdn.example/b.png 2x")); } @@ -1226,9 +1217,9 @@ mod tests { #[test] fn link_rel_case_and_multi_values_rewritten() { let settings = crate::test_support::tests::create_test_settings(); - let html = r#" + let html = " - "#; + "; let out = rewrite_creative_html(&settings, html); // href + one imagesrcset candidate should be rewritten assert!( @@ -1300,7 +1291,7 @@ mod tests { let settings = crate::test_support::tests::create_test_settings(); assert_eq!( to_abs(&settings, " https://cdn.example/a "), - Some("https://cdn.example/a".to_string()) + Some("https://cdn.example/a".to_owned()) ); assert_eq!(to_abs(&settings, "blob:xyz"), None); assert_eq!(to_abs(&settings, "tel:+123"), None); @@ -1324,7 +1315,7 @@ mod tests { #[test] fn to_abs_respects_exclude_domains() { let mut settings = crate::test_support::tests::create_test_settings(); - settings.rewrite.exclude_domains = vec!["trusted-cdn.example.com".to_string()]; + settings.rewrite.exclude_domains = vec!["trusted-cdn.example.com".to_owned()]; // Excluded domain should return None (not proxied) assert_eq!( @@ -1335,14 +1326,14 @@ mod tests { // Non-excluded domain should return Some assert_eq!( to_abs(&settings, "https://other-cdn.example.com/lib.js"), - Some("https://other-cdn.example.com/lib.js".to_string()) + Some("https://other-cdn.example.com/lib.js".to_owned()) ); } #[test] fn to_abs_respects_wildcard_domains() { let mut settings = crate::test_support::tests::create_test_settings(); - settings.rewrite.exclude_domains = vec!["*.cloudflare.com".to_string()]; + settings.rewrite.exclude_domains = vec!["*.cloudflare.com".to_owned()]; // Should exclude base domain assert_eq!(to_abs(&settings, "https://cloudflare.com/cdn.js"), None); @@ -1356,14 +1347,14 @@ mod tests { // Should not exclude different domain assert_eq!( to_abs(&settings, "https://notcloudflare.com/lib.js"), - Some("https://notcloudflare.com/lib.js".to_string()) + Some("https://notcloudflare.com/lib.js".to_owned()) ); } #[test] fn rewrite_html_excludes_blacklisted_domains() { let mut settings = crate::test_support::tests::create_test_settings(); - settings.rewrite.exclude_domains = vec!["trusted-cdn.example.com".to_string()]; + settings.rewrite.exclude_domains = vec!["trusted-cdn.example.com".to_owned()]; let html = r#" @@ -1383,7 +1374,7 @@ mod tests { #[test] fn rewrite_srcset_excludes_blacklisted_domains() { let mut settings = crate::test_support::tests::create_test_settings(); - settings.rewrite.exclude_domains = vec!["trusted.example.com".to_string()]; + settings.rewrite.exclude_domains = vec!["trusted.example.com".to_owned()]; let html = r#" @@ -1402,9 +1393,9 @@ mod tests { #[test] fn rewrite_style_urls_excludes_blacklisted_domains() { let mut settings = crate::test_support::tests::create_test_settings(); - settings.rewrite.exclude_domains = vec!["fonts.googleapis.com".to_string()]; + settings.rewrite.exclude_domains = vec!["fonts.googleapis.com".to_owned()]; - let html = r#" + let html = " - "#; + "; let out = rewrite_creative_html(&settings, html); @@ -1429,7 +1420,7 @@ mod tests { #[test] fn rewrite_click_urls_excludes_blacklisted_domains() { let mut settings = crate::test_support::tests::create_test_settings(); - settings.rewrite.exclude_domains = vec!["trusted-landing.example.com".to_string()]; + settings.rewrite.exclude_domains = vec!["trusted-landing.example.com".to_owned()]; let html = r#" Trusted Link @@ -1729,7 +1720,7 @@ mod tests { fn sanitize_removes_style_element() { // "#; + let html = "
ad
"; let out = sanitize_creative_html(html); assert!(!out.contains(" bool { /// attributes (e.g. adding `Partitioned`) only need updating in one place. fn format_set_cookie(domain: &str, value: &str, max_age: i32) -> String { format!( - "{}={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}; HttpOnly", - COOKIE_TS_EC, value, domain, max_age, + "{COOKIE_TS_EC}={value}; Domain={domain}; Path=/; Secure; SameSite=Lax; Max-Age={max_age}; HttpOnly", ) } @@ -201,7 +200,7 @@ mod tests { let settings = create_test_settings(); let result = create_ec_cookie(&settings, "evil;injected\r\nfoo=bar\0baz"); let value = result - .strip_prefix(&format!("{}=", COOKIE_TS_EC)) + .strip_prefix(&format!("{COOKIE_TS_EC}=")) .and_then(|s| s.split_once(';').map(|(v, _)| v)) .expect("should have cookie value portion"); @@ -217,7 +216,7 @@ mod tests { let id = "abc123def0123456789abcdef0123456789abcdef0123456789abcdef01234567.xk92ab"; let result = create_ec_cookie(&settings, id); let value = result - .strip_prefix(&format!("{}=", COOKIE_TS_EC)) + .strip_prefix(&format!("{COOKIE_TS_EC}=")) .and_then(|s| s.split_once(';').map(|(v, _)| v)) .expect("should have cookie value portion"); @@ -276,7 +275,7 @@ mod tests { #[test] fn is_safe_cookie_value_rejects_non_ascii() { assert!( - !is_safe_cookie_value("valüe"), + !is_safe_cookie_value("val\u{fc}e"), "should reject non-ASCII UTF-8 characters" ); } @@ -317,7 +316,7 @@ mod tests { "should set Max-Age=0 to expire cookie" ); assert!( - cookie_str.starts_with(&format!("{}=;", COOKIE_TS_EC)), + cookie_str.starts_with(&format!("{COOKIE_TS_EC}=;")), "should clear cookie value" ); assert!( diff --git a/crates/trusted-server-core/src/ec/eids.rs b/crates/trusted-server-core/src/ec/eids.rs index 727c62b4f..1dd8b1795 100644 --- a/crates/trusted-server-core/src/ec/eids.rs +++ b/crates/trusted-server-core/src/ec/eids.rs @@ -5,7 +5,7 @@ //! build base64-encoded response headers. use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use crate::error::TrustedServerError; use crate::openrtb::{Eid, Uid}; diff --git a/crates/trusted-server-core/src/ec/generation.rs b/crates/trusted-server-core/src/ec/generation.rs index 480682927..ab78450e4 100644 --- a/crates/trusted-server-core/src/ec/generation.rs +++ b/crates/trusted-server-core/src/ec/generation.rs @@ -5,9 +5,9 @@ use std::net::IpAddr; -use error_stack::{Report, ResultExt}; -use hmac::{Hmac, Mac}; -use rand::Rng; +use error_stack::{Report, ResultExt as _}; +use hmac::{Hmac, Mac as _}; +use rand::Rng as _; use sha2::Sha256; use crate::error::TrustedServerError; @@ -86,7 +86,7 @@ pub fn generate_ec_id( ) -> Result> { let mut mac = HmacSha256::new_from_slice(settings.ec.passphrase.expose().as_bytes()) .change_context(TrustedServerError::EdgeCookie { - message: "Failed to create HMAC instance".to_string(), + message: "Failed to create HMAC instance".to_owned(), })?; mac.update(client_ip.as_bytes()); let hmac_hash = hex::encode(mac.finalize().into_bytes()); @@ -112,7 +112,7 @@ pub fn generate_ec_id( pub fn extract_client_ip(req: &fastly::Request) -> Result> { req.get_client_ip_addr().map(normalize_ip).ok_or_else(|| { Report::new(TrustedServerError::EdgeCookie { - message: "Client IP required for EC generation but unavailable".to_string(), + message: "Client IP required for EC generation but unavailable".to_owned(), }) }) } diff --git a/crates/trusted-server-core/src/ec/identify.rs b/crates/trusted-server-core/src/ec/identify.rs index e9da1bd68..7008b5602 100644 --- a/crates/trusted-server-core/src/ec/identify.rs +++ b/crates/trusted-server-core/src/ec/identify.rs @@ -3,7 +3,7 @@ //! Partners authenticate with a Bearer token and receive only their own //! synced UID for the active EC ID. -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use fastly::http::{header, StatusCode}; use fastly::{Request, Response}; use url::Url; @@ -75,12 +75,7 @@ pub fn handle_identify( match kv.get(ec_id) { Ok(Some((entry, generation))) => { - if !entry.consent.ok { - // Tombstone entries preserve the withdrawal signal for 24 hours. - // Do not extract IDs or evaluate cluster size because that would - // write back with the live-entry TTL. - log::trace!("Identify found tombstone for '{}'", log_id(ec_id)); - } else { + if entry.consent.ok { // Extract only this partner's UID. if let Some(partner_uid) = entry.ids.get(&partner.source_domain) { if !partner_uid.uid.is_empty() { @@ -98,6 +93,11 @@ pub fn handle_identify( log::warn!("Cluster evaluation failed for '{}': {err:?}", log_id(ec_id)); } } + } else { + // Tombstone entries preserve the withdrawal signal for 24 hours. + // Do not extract IDs or evaluate cluster size because that would + // write back with the live-entry TTL. + log::trace!("Identify found tombstone for '{}'", log_id(ec_id)); } } Ok(None) => {} diff --git a/crates/trusted-server-core/src/ec/kv.rs b/crates/trusted-server-core/src/ec/kv.rs index ccd6b95a4..c87fcfa1c 100644 --- a/crates/trusted-server-core/src/ec/kv.rs +++ b/crates/trusted-server-core/src/ec/kv.rs @@ -10,7 +10,7 @@ use std::collections::BTreeMap; use std::time::Duration; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use fastly::kv_store::{InsertMode, KVStore}; use crate::error::TrustedServerError; @@ -29,10 +29,10 @@ const MAX_CAS_RETRIES: u32 = 5; const CLUSTER_LIST_LIMIT: u32 = 100; /// TTL for live entries (1 year), matching the EC cookie `Max-Age`. -const ENTRY_TTL: Duration = Duration::from_secs(365 * 24 * 60 * 60); +const ENTRY_TTL: Duration = Duration::from_hours(8_760); /// TTL for withdrawal tombstones (24 hours). -const TOMBSTONE_TTL: Duration = Duration::from_secs(24 * 60 * 60); +const TOMBSTONE_TTL: Duration = Duration::from_hours(24); /// Outcome of an [`KvIdentityGraph::upsert_partner_id_if_exists`] call. /// @@ -248,9 +248,8 @@ impl KvIdentityGraph { } }; - let meta_bytes = match response.metadata() { - Some(bytes) => bytes, - None => return Ok(None), + let Some(meta_bytes) = response.metadata() else { + return Ok(None); }; let meta: KvMetadata = @@ -345,10 +344,9 @@ impl KvIdentityGraph { } // Key exists — read it to determine if it's live or a tombstone. - let (existing, generation) = match self.get(ec_id)? { - Some(pair) => pair, + let Some((existing, generation)) = self.get(ec_id)? else { // Raced with a delete — try create again. - None => return self.create(ec_id, entry), + return self.create(ec_id, entry); }; // Live entry — nothing to do. @@ -440,23 +438,21 @@ impl KvIdentityGraph { let store = self.open_store()?; for attempt in 0..MAX_CAS_RETRIES { - let (mut entry, generation) = match Self::lookup_entry(&store, &self.store_name, ec_id)? - { - Some(pair) => pair, - None => { - log::info!( - "upsert_partner_ids: no entry for '{}', rejecting {} partner updates", - log_id(ec_id), + let Some((mut entry, generation)) = + Self::lookup_entry(&store, &self.store_name, ec_id)? + else { + log::info!( + "upsert_partner_ids: no entry for '{}', rejecting {} partner updates", + log_id(ec_id), + updates.len(), + ); + return Err(Report::new(TrustedServerError::KvStore { + store_name: self.store_name.clone(), + message: format!( + "Cannot upsert {} partner IDs for missing key '{ec_id}'", updates.len(), - ); - return Err(Report::new(TrustedServerError::KvStore { - store_name: self.store_name.clone(), - message: format!( - "Cannot upsert {} partner IDs for missing key '{ec_id}'", - updates.len(), - ), - })); - } + ), + })); }; // Reject upserts on withdrawn entries — a late sync must not @@ -546,20 +542,17 @@ impl KvIdentityGraph { let store = self.open_store()?; for attempt in 0..MAX_CAS_RETRIES { - let (mut entry, generation) = match self.get(ec_id)? { - Some(pair) => pair, - None => { - log::info!( - "upsert_partner_id: no entry for '{}', rejecting partner upsert", - log_id(ec_id) - ); - return Err(Report::new(TrustedServerError::KvStore { - store_name: self.store_name.clone(), - message: format!( - "Cannot upsert partner '{partner_id}' for missing key '{ec_id}'" - ), - })); - } + let Some((mut entry, generation)) = self.get(ec_id)? else { + log::info!( + "upsert_partner_id: no entry for '{}', rejecting partner upsert", + log_id(ec_id) + ); + return Err(Report::new(TrustedServerError::KvStore { + store_name: self.store_name.clone(), + message: format!( + "Cannot upsert partner '{partner_id}' for missing key '{ec_id}'" + ), + })); }; // Reject upserts on withdrawn entries — a late sync must not @@ -655,9 +648,8 @@ impl KvIdentityGraph { let store = self.open_store()?; for attempt in 0..MAX_CAS_RETRIES { - let (mut entry, generation) = match self.get(ec_id)? { - Some(pair) => pair, - None => return Ok(UpsertResult::NotFound), + let Some((mut entry, generation)) = self.get(ec_id)? else { + return Ok(UpsertResult::NotFound); }; if !entry.consent.ok { @@ -783,7 +775,10 @@ impl KvIdentityGraph { ), })?; - #[allow(clippy::cast_possible_truncation)] + #[allow( + clippy::cast_possible_truncation, + reason = "Fastly list page sizes are bounded well below u32::MAX" + )] let count = page.keys().len() as u32; Ok(count) } @@ -895,8 +890,8 @@ mod tests { #[test] fn constants_have_expected_values() { assert_eq!(MAX_CAS_RETRIES, 5); - assert_eq!(ENTRY_TTL, Duration::from_secs(31_536_000)); - assert_eq!(TOMBSTONE_TTL, Duration::from_secs(86_400)); + assert_eq!(ENTRY_TTL, Duration::from_hours(8_760)); + assert_eq!(TOMBSTONE_TTL, Duration::from_hours(24)); assert_eq!(CLUSTER_LIST_LIMIT, 100); } diff --git a/crates/trusted-server-core/src/ec/kv_types.rs b/crates/trusted-server-core/src/ec/kv_types.rs index 5f2c43007..7a6996ea0 100644 --- a/crates/trusted-server-core/src/ec/kv_types.rs +++ b/crates/trusted-server-core/src/ec/kv_types.rs @@ -442,11 +442,7 @@ impl KvGeo { pub fn from_geo_info(geo: Option<&GeoInfo>) -> Self { match geo { Some(info) => { - let dma = if info.metro_code > 0 { - Some(info.metro_code) - } else { - None - }; + let dma = (info.metro_code > 0).then_some(info.metro_code); Self { country: info.country.clone(), region: info.region.clone(), @@ -493,7 +489,7 @@ mod tests { fn entry_serialization_roundtrip() { let geo = sample_geo_info(); let consent = sample_consent_context(); - let mut entry = KvEntry::new(&consent, Some(&geo), 1741824000, "example.com"); + let mut entry = KvEntry::new(&consent, Some(&geo), 1_741_824_000, "example.com"); entry.ids.insert( "liveramp".to_owned(), KvPartnerId { @@ -506,7 +502,7 @@ mod tests { serde_json::from_str(&json).expect("should deserialize KvEntry"); assert_eq!(deserialized.v, SCHEMA_VERSION); - assert_eq!(deserialized.created, 1741824000); + assert_eq!(deserialized.created, 1_741_824_000); assert_eq!( deserialized.consent.tcf.as_deref(), Some("CP_test_tc_string") @@ -846,7 +842,7 @@ mod tests { #[test] fn minimal_entry_has_partner_id_and_placeholder_geo() { - let entry = KvEntry::minimal("ssp_x", "abc123", 1741824000); + let entry = KvEntry::minimal("ssp_x", "abc123", 1_741_824_000); assert_eq!(entry.v, SCHEMA_VERSION); assert!(entry.consent.ok, "should be a live entry"); @@ -862,13 +858,13 @@ mod tests { #[test] fn tombstone_entry_has_correct_shape() { - let entry = KvEntry::tombstone(1741910400); + let entry = KvEntry::tombstone(1_741_910_400); assert_eq!(entry.v, SCHEMA_VERSION); assert!(!entry.consent.ok, "should be a tombstone"); assert!(entry.ids.is_empty(), "tombstone should have no partner IDs"); assert_eq!(entry.geo.country, "ZZ"); - assert_eq!(entry.consent.updated, 1741910400); + assert_eq!(entry.consent.updated, 1_741_910_400); assert!( entry.pub_properties.is_none(), "tombstone should have no pub_properties" diff --git a/crates/trusted-server-core/src/ec/mod.rs b/crates/trusted-server-core/src/ec/mod.rs index c71b2e61c..b4fb60e22 100644 --- a/crates/trusted-server-core/src/ec/mod.rs +++ b/crates/trusted-server-core/src/ec/mod.rs @@ -54,7 +54,7 @@ pub mod registry; #[must_use] pub fn log_id(ec_id: &str) -> String { let prefix = ec_id.get(..8).unwrap_or(ec_id); - format!("{prefix}…") + format!("{prefix}\u{2026}") } use cookie::CookieJar; @@ -266,7 +266,7 @@ impl EcContext { let client_ip = self.client_ip.as_deref().ok_or_else(|| { Report::new(TrustedServerError::EdgeCookie { - message: "Client IP required for EC generation but unavailable".to_string(), + message: "Client IP required for EC generation but unavailable".to_owned(), }) })?; @@ -472,11 +472,13 @@ impl EcContext { pub(crate) fn current_timestamp() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or_else(|err| { - log::error!("SystemTime::now() failed, falling back to epoch 0: {err}"); - 0 - }) + .map_or_else( + |err| { + log::error!("SystemTime::now() failed, falling back to epoch 0: {err}"); + 0 + }, + |duration| duration.as_secs(), + ) } #[cfg(test)] diff --git a/crates/trusted-server-core/src/ec/partner.rs b/crates/trusted-server-core/src/ec/partner.rs index 27753c936..d824b3f4b 100644 --- a/crates/trusted-server-core/src/ec/partner.rs +++ b/crates/trusted-server-core/src/ec/partner.rs @@ -3,7 +3,7 @@ //! Provides source-domain normalization for EC partner configuration and //! API key hashing. The partner registry is in [`super::registry`]. -use sha2::{Digest, Sha256}; +use sha2::{Digest as _, Sha256}; /// Maximum allowed length for partner source domains. const MAX_SOURCE_DOMAIN_LENGTH: usize = 255; diff --git a/crates/trusted-server-core/src/ec/prebid_eids.rs b/crates/trusted-server-core/src/ec/prebid_eids.rs index 476b77218..22620e599 100644 --- a/crates/trusted-server-core/src/ec/prebid_eids.rs +++ b/crates/trusted-server-core/src/ec/prebid_eids.rs @@ -29,7 +29,10 @@ const MAX_EIDS_COOKIE_BYTES: usize = 8 * 1024; struct LegacyCookieEid { source: String, id: String, - #[allow(dead_code)] + #[allow( + dead_code, + reason = "legacy cookie field is deserialized for compatibility but not emitted" + )] atype: u8, } @@ -180,12 +183,9 @@ fn collect_prebid_eid_updates( cookie_value: &str, registry: &PartnerRegistry, ) -> Vec { - let eids = match parse_prebid_eids_cookie(cookie_value) { - Ok(eids) => eids, - Err(_) => { - log::trace!("Prebid EIDs: failed to decode ts-eids cookie; dropping"); - return Vec::new(); - } + let Ok(eids) = parse_prebid_eids_cookie(cookie_value) else { + log::trace!("Prebid EIDs: failed to decode ts-eids cookie; dropping"); + return Vec::new(); }; let mut updates = Vec::new(); diff --git a/crates/trusted-server-core/src/ec/pull_sync.rs b/crates/trusted-server-core/src/ec/pull_sync.rs index 6c935badd..6bef93c7c 100644 --- a/crates/trusted-server-core/src/ec/pull_sync.rs +++ b/crates/trusted-server-core/src/ec/pull_sync.rs @@ -268,10 +268,7 @@ fn drain_pull_batch(kv: &KvIdentityGraph, ec_id: &str, in_flight: &mut Vec response, Err(err) => { - log::warn!( - "Pull sync: request failed for partner '{}': {err:?}", - source_domain - ); + log::warn!("Pull sync: request failed for partner '{source_domain}': {err:?}"); continue; } }; @@ -303,25 +300,21 @@ fn response_content_length_exceeds_limit(response: &fastly::Response, source_dom let Some(value) = value.to_str().ok() else { log::warn!( - "Pull sync: partner '{}' returned invalid Content-Length header, rejecting", - source_domain + "Pull sync: partner '{source_domain}' returned invalid Content-Length header, rejecting" ); return true; }; let Ok(length) = value.parse::() else { log::warn!( - "Pull sync: partner '{}' returned malformed Content-Length header, rejecting", - source_domain + "Pull sync: partner '{source_domain}' returned malformed Content-Length header, rejecting" ); return true; }; if length > MAX_PULL_RESPONSE_BYTES { log::warn!( - "Pull sync: partner '{}' returned oversized Content-Length ({} bytes), rejecting", - source_domain, - length + "Pull sync: partner '{source_domain}' returned oversized Content-Length ({length} bytes), rejecting" ); return true; } @@ -333,19 +326,12 @@ fn extract_pull_uid(mut response: fastly::Response, source_domain: &str) -> Opti let status = response.get_status(); if status == StatusCode::NOT_FOUND { - log::debug!( - "Pull sync: partner '{}' returned 404, treating as no-op", - source_domain - ); + log::debug!("Pull sync: partner '{source_domain}' returned 404, treating as no-op"); return None; } if !status.is_success() { - log::warn!( - "Pull sync: partner '{}' returned non-success status {}", - source_domain, - status - ); + log::warn!("Pull sync: partner '{source_domain}' returned non-success status {status}"); return None; } @@ -365,10 +351,7 @@ fn extract_pull_uid(mut response: fastly::Response, source_domain: &str) -> Opti let payload = match serde_json::from_slice::(&body) { Ok(payload) => payload, Err(err) => { - log::warn!( - "Pull sync: partner '{}' returned invalid JSON body: {err}", - source_domain - ); + log::warn!("Pull sync: partner '{source_domain}' returned invalid JSON body: {err}"); return None; } }; @@ -379,8 +362,7 @@ fn extract_pull_uid(mut response: fastly::Response, source_domain: &str) -> Opti match uid { None => { log::debug!( - "Pull sync: partner '{}' returned null/empty uid, treating as no-op", - source_domain + "Pull sync: partner '{source_domain}' returned null/empty uid, treating as no-op" ); None } @@ -634,15 +616,15 @@ mod tests { assert_eq!(offset_h0, 0, "hour 0 should start at index 0"); // Hour 1: offset = (3600 / 3600) % 3 = 1 → [beta, gamma, alpha] - let offset_h1 = (3600u64 / 3600) as usize % ids.len(); + let offset_h1 = (3600_u64 / 3600) as usize % ids.len(); assert_eq!(offset_h1, 1, "hour 1 should start at index 1"); // Hour 2: offset = (7200 / 3600) % 3 = 2 → [gamma, alpha, beta] - let offset_h2 = (7200u64 / 3600) as usize % ids.len(); + let offset_h2 = (7200_u64 / 3600) as usize % ids.len(); assert_eq!(offset_h2, 2, "hour 2 should start at index 2"); // Hour 3: offset = (10800 / 3600) % 3 = 0 → wraps back to [alpha, beta, gamma] - let offset_h3 = (10800u64 / 3600) as usize % ids.len(); + let offset_h3 = (10800_u64 / 3600) as usize % ids.len(); assert_eq!(offset_h3, 0, "hour 3 should wrap back to index 0"); // Verify rotate_left produces expected ordering diff --git a/crates/trusted-server-core/src/ec/registry.rs b/crates/trusted-server-core/src/ec/registry.rs index 7ed144bfb..6b688d30e 100644 --- a/crates/trusted-server-core/src/ec/registry.rs +++ b/crates/trusted-server-core/src/ec/registry.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use crate::error::TrustedServerError; use crate::redacted::Redacted; @@ -82,10 +82,7 @@ impl PartnerRegistry { if by_source_domain.contains_key(&normalized_source) { return Err(Report::new(TrustedServerError::Configuration { - message: format!( - "ec.partners: duplicate source_domain '{}'", - normalized_source - ), + message: format!("ec.partners: duplicate source_domain '{normalized_source}'"), })); } @@ -96,9 +93,8 @@ impl PartnerRegistry { if by_api_key_hash.contains_key(&api_key_hash) { return Err(Report::new(TrustedServerError::Configuration { message: format!( - "ec.partners: source_domain '{}' has an API token that collides \ - with another partner's token hash", - normalized_source + "ec.partners: source_domain '{normalized_source}' has an API token that collides \ + with another partner's token hash" ), })); } @@ -150,9 +146,8 @@ impl PartnerRegistry { /// Looks up a partner by the SHA-256 hex hash of their API token. #[must_use] pub fn find_by_api_key_hash(&self, hash: &str) -> Option<&PartnerConfig> { - self.by_api_key_hash - .get(hash) - .and_then(|source_domain| self.by_source_domain.get(source_domain)) + let source_domain = self.by_api_key_hash.get(hash)?; + self.by_source_domain.get(source_domain) } /// Looks up a partner by their `source_domain`. @@ -262,8 +257,7 @@ fn validate_pull_sync(config: &PartnerConfig) -> Result<(), Report GeoInfo { n => Some(n), }; GeoInfo { - city: geo.city().to_string(), - country: geo.country_code().to_string(), + city: geo.city().to_owned(), + country: geo.country_code().to_owned(), continent: format!("{:?}", geo.continent()), latitude: geo.latitude(), longitude: geo.longitude(), @@ -122,13 +122,13 @@ mod tests { fn sample_geo_info() -> GeoInfo { GeoInfo { - city: "San Francisco".to_string(), - country: "US".to_string(), - continent: "NorthAmerica".to_string(), + city: "San Francisco".to_owned(), + country: "US".to_owned(), + continent: "NorthAmerica".to_owned(), latitude: 37.7749, longitude: -122.4194, metro_code: 807, - region: Some("CA".to_string()), + region: Some("CA".to_owned()), asn: Some(7922), } } diff --git a/crates/trusted-server-core/src/host_header.rs b/crates/trusted-server-core/src/host_header.rs index b782dbd9c..c3c4a1d1e 100644 --- a/crates/trusted-server-core/src/host_header.rs +++ b/crates/trusted-server-core/src/host_header.rs @@ -154,9 +154,8 @@ fn validate_port(port: &str) -> Result<(), &'static str> { } match port.parse::() { - Ok(0) => Err("port must be between 1 and 65535"), + Ok(0) | Err(_) => Err("port must be between 1 and 65535"), Ok(_) => Ok(()), - Err(_) => Err("port must be between 1 and 65535"), } } diff --git a/crates/trusted-server-core/src/host_rewrite.rs b/crates/trusted-server-core/src/host_rewrite.rs index 4bc14f581..816dc52b8 100644 --- a/crates/trusted-server-core/src/host_rewrite.rs +++ b/crates/trusted-server-core/src/host_rewrite.rs @@ -52,7 +52,7 @@ pub(crate) fn rewrite_bare_host_at_boundaries( replaced_any = true; search = end; } else { - out.push_str(&text[search..pos + 1]); + out.push_str(&text[search..=pos]); search = pos + 1; } } diff --git a/crates/trusted-server-core/src/html_processor.rs b/crates/trusted-server-core/src/html_processor.rs index eafe1e1e1..8260cdd0e 100644 --- a/crates/trusted-server-core/src/html_processor.rs +++ b/crates/trusted-server-core/src/html_processor.rs @@ -127,9 +127,9 @@ impl HtmlProcessorConfig { request_scheme: &str, ) -> Self { Self { - origin_host: origin_host.to_string(), - request_host: request_host.to_string(), - request_scheme: request_scheme.to_string(), + origin_host: origin_host.to_owned(), + request_host: request_host.to_owned(), + request_scheme: request_scheme.to_owned(), integrations: integrations.clone(), } } @@ -188,10 +188,7 @@ pub fn create_html_processor(config: HtmlProcessorConfig) -> impl StreamProcesso if rewritten.starts_with(&self.origin_host) { let suffix = &rewritten[self.origin_host.len()..]; let boundary_ok = suffix.is_empty() - || matches!( - suffix.as_bytes().first(), - Some(b'/') | Some(b'?') | Some(b'#') - ); + || matches!(suffix.as_bytes().first(), Some(b'/' | b'?' | b'#')); if boundary_ok { rewritten = format!("{}{}", self.request_host, suffix); } @@ -516,9 +513,9 @@ mod tests { fn create_test_config() -> HtmlProcessorConfig { HtmlProcessorConfig { - origin_host: "origin.example.com".to_string(), - request_host: "test.example.com".to_string(), - request_scheme: "https".to_string(), + origin_host: "origin.example.com".to_owned(), + request_host: "test.example.com".to_owned(), + request_scheme: "https".to_owned(), integrations: IntegrationRegistry::default(), } } @@ -587,11 +584,11 @@ mod tests { } fn head_inserts(&self, _ctx: &IntegrationHtmlContext<'_>) -> Vec { - vec![r#""#.to_string()] + vec!["".to_owned()] } } - let html = r#"Test"#; + let html = "Test"; let mut config = create_test_config(); config.integrations = IntegrationRegistry::from_rewriters_with_head_injectors( @@ -715,14 +712,14 @@ mod tests { let protocol_relative_urls = html.matches("//www.test-publisher.com").count(); println!("Test HTML stats:"); - println!(" Total URLs: {}", original_urls); - println!(" HTTPS URLs: {}", https_urls); - println!(" Protocol-relative URLs: {}", protocol_relative_urls); + println!(" Total URLs: {original_urls}"); + println!(" HTTPS URLs: {https_urls}"); + println!(" Protocol-relative URLs: {protocol_relative_urls}"); // Process - replace test-publisher.com with our edge domain let mut config = create_test_config(); - config.origin_host = "www.test-publisher.com".to_string(); // Match what's in the HTML - config.request_host = "test-publisher-ts.edgecompute.app".to_string(); + config.origin_host = "www.test-publisher.com".to_owned(); // Match what's in the HTML + config.request_host = "test-publisher-ts.edgecompute.app".to_owned(); let processor = create_html_processor(config); let pipeline_config = PipelineConfig { @@ -745,8 +742,8 @@ mod tests { let replaced_urls = result.matches("test-publisher-ts.edgecompute.app").count(); println!("After processing:"); - println!(" Remaining original URLs: {}", remaining_urls); - println!(" Edge domain URLs: {}", replaced_urls); + println!(" Remaining original URLs: {remaining_urls}"); + println!(" Edge domain URLs: {replaced_urls}"); // Expect at least some replacements and fewer originals than before assert!(replaced_urls > 0, "Should replace some URLs in attributes"); @@ -788,7 +785,7 @@ mod tests { "#; let mut settings = Settings::default(); - let shim_src = "https://edge.example.com/static/testlight.js".to_string(); + let shim_src = "https://edge.example.com/static/testlight.js".to_owned(); settings .integrations .insert_config( @@ -816,7 +813,7 @@ mod tests { let mut output = Vec::new(); let result = pipeline.process(Cursor::new(html.as_bytes()), &mut output); - assert!(result.is_ok()); + result.unwrap(); let processed = String::from_utf8_lossy(&output); assert!( @@ -834,7 +831,7 @@ mod tests { use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression as GzCompression; - use std::io::{Read, Write}; + use std::io::{Read as _, Write as _}; let html = include_str!("html_processor.test.html"); @@ -852,8 +849,8 @@ mod tests { // Process with compression let mut config = create_test_config(); - config.origin_host = "www.test-publisher.com".to_string(); // Match what's in the HTML - config.request_host = "test-publisher-ts.edgecompute.app".to_string(); + config.origin_host = "www.test-publisher.com".to_owned(); // Match what's in the HTML + config.request_host = "test-publisher-ts.edgecompute.app".to_owned(); let processor = create_html_processor(config); let pipeline_config = PipelineConfig { @@ -875,7 +872,7 @@ mod tests { ); // Decompress and verify - let mut decoder = GzDecoder::new(&compressed_output[..]); + let mut decoder = GzDecoder::new(&*compressed_output); let mut decompressed = String::new(); decoder .read_to_string(&mut decompressed) @@ -924,10 +921,10 @@ mod tests { // This simulates receiving already-truncated HTML from origin let truncated_html = - r#"Test

This is a test that gets cut o"#; + "Test

This is a test that gets cut o"; println!("Testing already-truncated HTML"); - println!("Input: '{}'", truncated_html); + println!("Input: '{truncated_html}'"); let config = create_test_config(); let processor = create_html_processor(config); @@ -947,7 +944,7 @@ mod tests { ); let processed = String::from_utf8_lossy(&output); - println!("Output: '{}'", processed); + println!("Output: '{processed}'"); // The processor should pass through the truncated HTML // It might add some closing tags, but shouldn't truncate further @@ -982,8 +979,8 @@ mod tests { // Process it through our pipeline let mut config = create_test_config(); - config.origin_host = "www.test-publisher.com".to_string(); // Match what's in the HTML - config.request_host = "test-publisher-ts.edgecompute.app".to_string(); + config.origin_host = "www.test-publisher.com".to_owned(); // Match what's in the HTML + config.request_host = "test-publisher-ts.edgecompute.app".to_owned(); let processor = create_html_processor(config); let pipeline_config = PipelineConfig { @@ -1033,7 +1030,7 @@ mod tests { #[test] fn post_processors_accumulate_while_streaming_path_passes_through() { - use crate::streaming_processor::{HtmlRewriterAdapter, StreamProcessor}; + use crate::streaming_processor::{HtmlRewriterAdapter, StreamProcessor as _}; use lol_html::Settings; // --- Streaming path: no post-processors → output emitted per chunk --- @@ -1118,7 +1115,7 @@ mod tests { #[test] fn active_post_processor_receives_full_document_and_mutates_output() { - use crate::streaming_processor::{HtmlRewriterAdapter, StreamProcessor}; + use crate::streaming_processor::{HtmlRewriterAdapter, StreamProcessor as _}; use lol_html::Settings; struct AppendCommentProcessor; diff --git a/crates/trusted-server-core/src/http_util.rs b/crates/trusted-server-core/src/http_util.rs index a7bcd88cb..194647238 100644 --- a/crates/trusted-server-core/src/http_util.rs +++ b/crates/trusted-server-core/src/http_util.rs @@ -1,9 +1,9 @@ use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use chacha20poly1305::{aead::Aead, aead::KeyInit, XChaCha20Poly1305, XNonce}; +use chacha20poly1305::{aead::Aead as _, aead::KeyInit as _, XChaCha20Poly1305, XNonce}; use edgezero_core::body::Body as EdgeBody; use error_stack::Report; use http::{header, Request, Response, StatusCode}; -use sha2::{Digest, Sha256}; +use sha2::{Digest as _, Sha256}; use subtle::ConstantTimeEq as _; use crate::constants::INTERNAL_HEADERS; @@ -52,7 +52,7 @@ pub(crate) const SPOOFABLE_FORWARDED_HEADERS: &[&str] = &[ pub fn sanitize_forwarded_headers(req: &mut Request) { for header in SPOOFABLE_FORWARDED_HEADERS { if req.headers().contains_key(*header) { - log::debug!("Stripped spoofable header: {}", header); + log::debug!("Stripped spoofable header: {header}"); req.headers_mut().remove(*header); } } @@ -160,7 +160,7 @@ fn extract_request_host(req: &Request) -> String { .and_then(|h| h.to_str().ok()) }) .unwrap_or_default() - .to_string() + .to_owned() } fn parse_forwarded_param<'a>(forwarded: &'a str, param: &str) -> Option<&'a str> { @@ -203,11 +203,7 @@ fn strip_quotes(value: &str) -> &str { fn normalize_scheme(value: &str) -> Option { let scheme = value.trim().to_ascii_lowercase(); - if scheme == "https" || scheme == "http" { - Some(scheme) - } else { - None - } + (scheme == "https" || scheme == "http").then_some(scheme) } /// Detects the request scheme (HTTP or HTTPS) using Fastly SDK methods and headers. @@ -225,14 +221,14 @@ fn detect_request_scheme( ) -> String { // 1. First try ClientInfo TLS fields populated at the adapter entry point. if let Some(tls_protocol) = tls_protocol { - log::debug!("TLS protocol detected: {}", tls_protocol); - return "https".to_string(); + log::debug!("TLS protocol detected: {tls_protocol}"); + return "https".to_owned(); } // Also check TLS cipher - if present, connection is HTTPS. if tls_cipher.is_some() { log::debug!("TLS cipher detected, using HTTPS"); - return "https".to_string(); + return "https".to_owned(); } // 2. Try the Forwarded header (RFC 7239) @@ -261,13 +257,13 @@ fn detect_request_scheme( if let Some(ssl) = req.headers().get("fastly-ssl") { if let Ok(ssl_str) = ssl.to_str() { if ssl_str == "1" || ssl_str.to_lowercase() == "true" { - return "https".to_string(); + return "https".to_owned(); } } } // Default to HTTP - "http".to_string() + "http".to_owned() } /// Build a static text response with strong `ETag` and standard caching headers. @@ -339,7 +335,7 @@ pub fn encode_url(settings: &Settings, plaintext_url: &str) -> String { hasher.update(settings.publisher.proxy_secret.expose().as_bytes()); hasher.update(plaintext_url.as_bytes()); let nonce_full = hasher.finalize(); - let mut nonce = [0u8; 24]; + let mut nonce = [0_u8; 24]; nonce[..24].copy_from_slice(&nonce_full[..24]); let nonce = XNonce::from_slice(&nonce); @@ -370,10 +366,8 @@ pub fn decode_url(settings: &Settings, token: &str) -> Option { let key_bytes = Sha256::digest(settings.publisher.proxy_secret.expose().as_bytes()); let cipher = XChaCha20Poly1305::new(&key_bytes); - cipher - .decrypt(nonce, ciphertext) - .ok() - .and_then(|pt| String::from_utf8(pt).ok()) + let pt = cipher.decrypt(nonce, ciphertext).ok()?; + String::from_utf8(pt).ok() } /// Compute a deterministic signature token (tstoken) for a clear-text URL using the @@ -498,11 +492,8 @@ mod tests { let src = "https://t.example/p.gif"; let enc = encode_url(&settings, src); assert!(!enc.ends_with('=')); - let dec = match decode_url(&settings, &enc) { - Some(s) => s, - None => { - panic!("decode failed for token: {}", enc); - } + let Some(dec) = decode_url(&settings, &enc) else { + panic!("decode failed for token: {enc}"); }; assert_eq!(dec, src); } diff --git a/crates/trusted-server-core/src/integrations/datadome/protection.rs b/crates/trusted-server-core/src/integrations/datadome/protection.rs index 8512bcc91..e5bbc6295 100644 --- a/crates/trusted-server-core/src/integrations/datadome/protection.rs +++ b/crates/trusted-server-core/src/integrations/datadome/protection.rs @@ -389,7 +389,7 @@ impl DataDomeIntegration { ); return RequestFilterDecision::Continue(RequestFilterEffects::default()); } - let body_bytes = body.into_bytes(); + let body_bytes = body.into_bytes().unwrap_or_default(); EdgeBody::from(body_bytes.as_ref().to_vec()) }; let challenge = Response::builder() @@ -754,7 +754,11 @@ mod tests { "should preserve challenge status" ); assert_eq!( - response.into_body().into_bytes().as_ref(), + response + .into_body() + .into_bytes() + .unwrap_or_default() + .as_ref(), b"", "HEAD challenges should not include a response body" ); diff --git a/crates/trusted-server-core/src/integrations/nextjs/html_post_process.rs b/crates/trusted-server-core/src/integrations/nextjs/html_post_process.rs index 90cb48437..84864b062 100644 --- a/crates/trusted-server-core/src/integrations/nextjs/html_post_process.rs +++ b/crates/trusted-server-core/src/integrations/nextjs/html_post_process.rs @@ -134,16 +134,13 @@ impl NextJsHtmlPostProcessor { let expected = rewritten_payloads.len(); if replaced != expected { log::warn!( - "NextJs post-process placeholder substitution count mismatch: expected={}, replaced={}", - expected, - replaced + "NextJs post-process placeholder substitution count mismatch: expected={expected}, replaced={replaced}" ); } if contains_rsc_payload_placeholders(&updated) { log::error!( - "NextJs post-process left RSC placeholders in output; attempting fallback substitution (scripts={})", - expected + "NextJs post-process left RSC placeholders in output; attempting fallback substitution (scripts={expected})" ); let fallback = @@ -151,8 +148,7 @@ impl NextJsHtmlPostProcessor { if contains_rsc_payload_placeholders(&fallback) { log::error!( - "NextJs post-process fallback substitution still left RSC placeholders in output; hydration may break (scripts={})", - expected + "NextJs post-process fallback substitution still left RSC placeholders in output; hydration may break (scripts={expected})" ); } @@ -166,7 +162,7 @@ impl NextJsHtmlPostProcessor { } fn contains_rsc_payload_placeholders(html: &str) -> bool { - let mut cursor = 0usize; + let mut cursor = 0_usize; while let Some(next) = html[cursor..].find(RSC_PAYLOAD_PLACEHOLDER_PREFIX) { let start = cursor + next; let after_prefix = start + RSC_PAYLOAD_PLACEHOLDER_PREFIX.len(); @@ -184,8 +180,8 @@ fn contains_rsc_payload_placeholders(html: &str) -> bool { fn substitute_rsc_payload_placeholders(html: &str, replacements: &[String]) -> (String, usize) { let mut output = String::with_capacity(html.len()); - let mut cursor = 0usize; - let mut replaced = 0usize; + let mut cursor = 0_usize; + let mut replaced = 0_usize; while let Some(next) = html[cursor..].find(RSC_PAYLOAD_PLACEHOLDER_PREFIX) { let start = cursor + next; @@ -232,7 +228,7 @@ fn substitute_rsc_payload_placeholders(html: &str, replacements: &[String]) -> ( } fn substitute_rsc_payload_placeholders_exact(html: &str, replacements: &[String]) -> String { - let mut out = html.to_string(); + let mut out = html.to_owned(); for (index, replacement) in replacements.iter().enumerate() { let placeholder = format!("{RSC_PAYLOAD_PLACEHOLDER_PREFIX}{index}{RSC_PAYLOAD_PLACEHOLDER_SUFFIX}"); @@ -255,7 +251,7 @@ fn find_rsc_push_scripts(html: &str) -> Vec { let ranges: Rc>> = Rc::new(RefCell::new(Vec::new())); let buffer: Rc> = Rc::new(RefCell::new(String::new())); let buffering = Rc::new(Cell::new(false)); - let buffer_start = Rc::new(Cell::new(0usize)); + let buffer_start = Rc::new(Cell::new(0_usize)); let settings = RewriterSettings { element_content_handlers: vec![text!("script", { @@ -348,8 +344,8 @@ pub fn post_process_rsc_html( request_host: &str, request_scheme: &str, ) -> String { - let mut result = html.to_string(); - #[allow(deprecated)] + let mut result = html.to_owned(); + #[allow(deprecated, reason = "wrapper preserves the deprecated legacy API")] post_process_rsc_html_in_place(&mut result, origin_host, request_host, request_scheme); result } @@ -395,7 +391,7 @@ fn post_process_rsc_html_in_place_with_limit( } scripts.sort_by_key(|s| s.payload_start); - let mut previous_end = 0usize; + let mut previous_end = 0_usize; for script in &scripts { if script.payload_start > script.payload_end { log::warn!( @@ -497,7 +493,10 @@ fn post_process_rsc_html_in_place_with_limit( } #[cfg(test)] -#[allow(deprecated)] // Tests use deprecated post_process_rsc_html for legacy API coverage +#[allow( + deprecated, + reason = "tests cover deprecated post_process_rsc_html legacy API" +)] mod tests { use super::*; @@ -512,7 +511,7 @@ mod tests { let ranges: Rc>> = Rc::new(RefCell::new(Vec::new())); let buffer: Rc> = Rc::new(RefCell::new(String::new())); let buffering = Rc::new(Cell::new(false)); - let buffer_start = Rc::new(Cell::new(0usize)); + let buffer_start = Rc::new(Cell::new(0_usize)); let saw_partial = Rc::new(Cell::new(false)); let settings = RewriterSettings { @@ -607,13 +606,11 @@ mod tests { assert!( result.contains("test.example.com/page"), - "URL should be rewritten. Got: {}", - result + "URL should be rewritten. Got: {result}" ); assert!( result.contains(":T3c,"), - "T-chunk length should be updated. Got: {}", - result + "T-chunk length should be updated. Got: {result}" ); assert!(result.contains("") && result.contains("")); assert!(result.contains("self.__next_f.push")); @@ -666,7 +663,7 @@ mod tests { #[test] fn finds_window_next_f_push_with_case_insensitive_script_tags() { - let html = r#""#; + let html = ""; let scripts = find_rsc_push_scripts(html); assert_eq!( scripts.len(), @@ -698,18 +695,15 @@ mod tests { assert!( result.contains("test.example.com/news"), - "First URL should be rewritten. Got: {}", - result + "First URL should be rewritten. Got: {result}" ); assert!( result.contains("test.example.com/reviews"), - "Second URL should be rewritten. Got: {}", - result + "Second URL should be rewritten. Got: {result}" ); assert!( !result.contains("origin.example.com"), - "No origin URLs should remain. Got: {}", - result + "No origin URLs should remain. Got: {result}" ); assert!(result.contains("") && result.contains("")); assert!(result.contains("self.__next_f.push")); @@ -719,8 +713,8 @@ mod tests { fn post_process_rewrites_html_href_inside_tchunk() { fn calculate_unescaped_byte_length_for_test(s: &str) -> usize { let bytes = s.as_bytes(); - let mut pos = 0usize; - let mut count = 0usize; + let mut pos = 0_usize; + let mut count = 0_usize; while pos < bytes.len() { if bytes[pos] == b'\\' && pos + 1 < bytes.len() { @@ -763,7 +757,7 @@ mod tests { } } - let c = char::from_u32(code_unit as u32).unwrap_or('\u{FFFD}'); + let c = char::from_u32(u32::from(code_unit)).unwrap_or('\u{FFFD}'); pos += 6; count += c.len_utf8(); continue; @@ -791,14 +785,14 @@ mod tests { calculate_unescaped_byte_length_for_test(tchunk_content) ); let html = format!( - r#" + " -"# +" ); let result = @@ -806,18 +800,15 @@ mod tests { assert!( result.contains("test.example.com/about-us"), - "HTML href URL in T-chunk should be rewritten. Got: {}", - result + "HTML href URL in T-chunk should be rewritten. Got: {result}" ); assert!( !result.contains("origin.example.com"), - "No origin URLs should remain. Got: {}", - result + "No origin URLs should remain. Got: {result}" ); assert!( !result.contains(&format!(":T{declared_len_hex},")), - "T-chunk length should have been recalculated. Got: {}", - result + "T-chunk length should have been recalculated. Got: {result}" ); } diff --git a/crates/trusted-server-core/src/integrations/nextjs/mod.rs b/crates/trusted-server-core/src/integrations/nextjs/mod.rs index 6414284d0..dd446f565 100644 --- a/crates/trusted-server-core/src/integrations/nextjs/mod.rs +++ b/crates/trusted-server-core/src/integrations/nextjs/mod.rs @@ -18,7 +18,10 @@ mod shared; // Re-export deprecated legacy functions for backward compatibility. // Production code should use the placeholder-based approach via NextJsHtmlPostProcessor. -#[allow(deprecated)] +#[allow( + deprecated, + reason = "legacy HTML post-processing functions remain re-exported for compatibility" +)] pub use html_post_process::{post_process_rsc_html, post_process_rsc_html_in_place}; pub use rsc::rewrite_rsc_scripts_combined; @@ -51,7 +54,7 @@ fn default_enabled() -> bool { } fn default_rewrite_attributes() -> Vec { - vec!["href".to_string(), "link".to_string(), "url".to_string()] + vec!["href".to_owned(), "link".to_owned(), "url".to_owned()] } fn default_max_combined_payload_bytes() -> usize { @@ -76,20 +79,17 @@ pub(super) fn configuration_error(message: impl Into) -> Report Result, Report> { - let config = match build(settings)? { - Some(config) => { - log::info!( - "NextJS integration registered: enabled={}, rewrite_attributes={:?}, max_combined_payload_bytes={}", - config.enabled, - config.rewrite_attributes, - config.max_combined_payload_bytes - ); - config - } - None => { - log::info!("NextJS integration not registered (disabled or missing config)"); - return Ok(None); - } + let config = if let Some(config) = build(settings)? { + log::info!( + "NextJS integration registered: enabled={}, rewrite_attributes={:?}, max_combined_payload_bytes={}", + config.enabled, + config.rewrite_attributes, + config.max_combined_payload_bytes + ); + config + } else { + log::info!("NextJS integration not registered (disabled or missing config)"); + return Ok(None); }; // Register a structured (Pages Router __NEXT_DATA__) rewriter. let structured = Arc::new(NextJsNextDataRewriter::new(config.clone())?); @@ -336,13 +336,11 @@ mod tests { // RSC payloads should be rewritten via end-of-document post-processing assert!( final_html.contains("test.example.com"), - "RSC stream payloads should be rewritten to proxy host via post-processing. Output: {}", - final_html + "RSC stream payloads should be rewritten to proxy host via post-processing. Output: {final_html}" ); assert!( !final_html.contains(RSC_PAYLOAD_PLACEHOLDER_PREFIX), - "RSC placeholder markers should not appear in final HTML. Output: {}", - final_html + "RSC placeholder markers should not appear in final HTML. Output: {final_html}" ); } @@ -384,13 +382,11 @@ mod tests { // RSC payloads should be rewritten via end-of-document post-processing assert!( final_html.contains("test.example.com"), - "RSC stream payloads should be rewritten to proxy host with chunked input. Output: {}", - final_html + "RSC stream payloads should be rewritten to proxy host with chunked input. Output: {final_html}" ); assert!( !final_html.contains(RSC_PAYLOAD_PLACEHOLDER_PREFIX), - "RSC placeholder markers should not appear in final HTML. Output: {}", - final_html + "RSC placeholder markers should not appear in final HTML. Output: {final_html}" ); } @@ -434,18 +430,15 @@ mod tests { assert!( final_html.contains("https://origin.example.com/page"), - "Origin URL should remain when rewrite is skipped due to size limit. Output: {}", - final_html + "Origin URL should remain when rewrite is skipped due to size limit. Output: {final_html}" ); assert!( !final_html.contains("test.example.com"), - "Proxy host should not be introduced when rewrite is skipped. Output: {}", - final_html + "Proxy host should not be introduced when rewrite is skipped. Output: {final_html}" ); assert!( !final_html.contains(RSC_PAYLOAD_PLACEHOLDER_PREFIX), - "RSC placeholder markers should not appear in final HTML. Output: {}", - final_html + "RSC placeholder markers should not appear in final HTML. Output: {final_html}" ); } @@ -500,8 +493,7 @@ mod tests { // RSC payloads should be rewritten via post-processing assert!( final_html.contains("test.example.com"), - "RSC payload URLs should be rewritten to proxy host. Output: {}", - final_html + "RSC payload URLs should be rewritten to proxy host. Output: {final_html}" ); // Verify the RSC payload structure is preserved @@ -520,14 +512,12 @@ mod tests { // Verify \n separators are preserved (crucial for RSC parsing) assert!( - final_html.contains(r#"\n442:"#), - "RSC record separator \\n should be preserved. Output: {}", - final_html + final_html.contains(r"\n442:"), + "RSC record separator \\n should be preserved. Output: {final_html}" ); assert!( !final_html.contains(RSC_PAYLOAD_PLACEHOLDER_PREFIX), - "RSC placeholder markers should not appear in final HTML. Output: {}", - final_html + "RSC placeholder markers should not appear in final HTML. Output: {final_html}" ); } @@ -573,30 +563,25 @@ mod tests { // Non-RSC scripts should be preserved assert!( final_html.contains(r#"console.log("hello world");"#), - "First non-RSC script should be preserved intact. Output: {}", - final_html + "First non-RSC script should be preserved intact. Output: {final_html}" ); assert!( final_html.contains("window.analytics"), - "Third non-RSC script should be preserved. Output: {}", - final_html + "Third non-RSC script should be preserved. Output: {final_html}" ); assert!( final_html.contains("track: function(e)"), - "Third non-RSC script content should be intact. Output: {}", - final_html + "Third non-RSC script content should be intact. Output: {final_html}" ); // RSC scripts should be rewritten assert!( final_html.contains("test.example.com"), - "RSC URL should be rewritten. Output: {}", - final_html + "RSC URL should be rewritten. Output: {final_html}" ); assert!( !final_html.contains(RSC_PAYLOAD_PLACEHOLDER_PREFIX), - "No placeholders should remain. Output: {}", - final_html + "No placeholders should remain. Output: {final_html}" ); } @@ -720,7 +705,7 @@ mod tests { ); assert!( processed.contains(r#""])"#), - "push call must close properly — `\"])` followed by . Got: {processed}" + "push call must close properly \u{2014} `\"])` followed by . Got: {processed}" ); } } diff --git a/crates/trusted-server-core/src/integrations/nextjs/rsc.rs b/crates/trusted-server-core/src/integrations/nextjs/rsc.rs index 66a5e26fd..c7983552b 100644 --- a/crates/trusted-server-core/src/integrations/nextjs/rsc.rs +++ b/crates/trusted-server-core/src/integrations/nextjs/rsc.rs @@ -9,7 +9,7 @@ use super::shared::RscUrlRewriter; /// This is a static code-defined literal rather than a config-derived pattern, /// so it intentionally stays outside startup preparation. static TCHUNK_PATTERN: LazyLock = - LazyLock::new(|| Regex::new(r"([0-9a-fA-F]+):T([0-9a-fA-F]+),").expect("valid T-chunk regex")); + LazyLock::new(|| Regex::new("([0-9a-fA-F]+):T([0-9a-fA-F]+),").expect("valid T-chunk regex")); /// Marker used to track script boundaries when combining RSC content. pub(crate) const RSC_MARKER: &str = "\x00SPLIT\x00"; @@ -141,7 +141,7 @@ impl Iterator for EscapeSequenceIter<'_> { } } - let c = char::from_u32(code_unit as u32).unwrap_or('\u{FFFD}'); + let c = char::from_u32(u32::from(code_unit)).unwrap_or('\u{FFFD}'); self.pos += 6; return Some(EscapeElement { byte_count: c.len_utf8(), @@ -206,11 +206,7 @@ struct TChunkInfo { fn find_tchunks_impl(content: &str, skip_markers: bool) -> Option> { let mut chunks = Vec::new(); let mut search_pos = 0; - let marker = if skip_markers { - Some(RSC_MARKER.as_bytes()) - } else { - None - }; + let marker = skip_markers.then(|| RSC_MARKER.as_bytes()); while search_pos < content.len() { if let Some(cap) = TCHUNK_PATTERN.captures(&content[search_pos..]) { @@ -289,7 +285,7 @@ pub(crate) fn rewrite_rsc_tchunks_with_rewriter( log::warn!( "RSC payload contains invalid or incomplete T-chunks; skipping rewriting to avoid breaking hydration" ); - return content.to_string(); + return content.to_owned(); }; if chunks.is_empty() { @@ -406,7 +402,7 @@ pub(crate) fn rewrite_rsc_scripts_combined_with_limit( // Early exit if no payload contains the origin host - avoids regex compilation if !payloads.iter().any(|p| p.contains(origin_host)) { - return payloads.iter().map(|p| (*p).to_string()).collect(); + return payloads.iter().map(|p| (*p).to_owned()).collect(); } if payloads.len() == 1 { @@ -434,9 +430,7 @@ pub(crate) fn rewrite_rsc_scripts_combined_with_limit( // per-script rewriting is unsafe because it may rewrite T-chunk content without updating // the original header, breaking React hydration. log::warn!( - "RSC combined payload size {} exceeds limit {}, skipping cross-script combining", - total_size, - max_combined_payload_bytes + "RSC combined payload size {total_size} exceeds limit {max_combined_payload_bytes}, skipping cross-script combining" ); if payloads @@ -446,7 +440,7 @@ pub(crate) fn rewrite_rsc_scripts_combined_with_limit( log::warn!( "RSC payloads contain cross-script T-chunks; skipping RSC URL rewriting to avoid breaking hydration (consider increasing integrations.nextjs.max_combined_payload_bytes)" ); - return payloads.iter().map(|p| (*p).to_string()).collect(); + return payloads.iter().map(|p| (*p).to_owned()).collect(); } return payloads @@ -474,7 +468,7 @@ pub(crate) fn rewrite_rsc_scripts_combined_with_limit( log::warn!( "RSC combined payload contains invalid or incomplete T-chunks; skipping rewriting to avoid breaking hydration" ); - return payloads.iter().map(|p| (*p).to_string()).collect(); + return payloads.iter().map(|p| (*p).to_owned()).collect(); }; if chunks.is_empty() { return payloads @@ -542,8 +536,7 @@ mod tests { ); assert!( result.starts_with("1a:T27,"), - "T-chunk length should be updated from 29 (41) to 27 (39). Got: {}", - result + "T-chunk length should be updated from 29 (41) to 27 (39). Got: {result}" ); } @@ -565,21 +558,20 @@ mod tests { ); assert!( result.starts_with("1a:T24,"), - "T-chunk length should be updated from 1c (28) to 24 (36). Got: {}", - result + "T-chunk length should be updated from 1c (28) to 24 (36). Got: {result}" ); } #[test] fn calculate_unescaped_byte_length_handles_common_escapes() { assert_eq!(calculate_unescaped_byte_length("hello"), 5); - assert_eq!(calculate_unescaped_byte_length(r#"\n"#), 1); - assert_eq!(calculate_unescaped_byte_length(r#"\r\n"#), 2); + assert_eq!(calculate_unescaped_byte_length(r"\n"), 1); + assert_eq!(calculate_unescaped_byte_length(r"\r\n"), 2); assert_eq!(calculate_unescaped_byte_length(r#"\""#), 1); - assert_eq!(calculate_unescaped_byte_length(r#"\\"#), 1); - assert_eq!(calculate_unescaped_byte_length(r#"\x41"#), 1); - assert_eq!(calculate_unescaped_byte_length(r#"\u0041"#), 1); - assert_eq!(calculate_unescaped_byte_length(r#"\u00e9"#), 2); + assert_eq!(calculate_unescaped_byte_length(r"\\"), 1); + assert_eq!(calculate_unescaped_byte_length(r"\x41"), 1); + assert_eq!(calculate_unescaped_byte_length(r"\u0041"), 1); + assert_eq!(calculate_unescaped_byte_length(r"\u00e9"), 2); } #[test] @@ -604,15 +596,12 @@ mod tests { #[test] fn cross_script_tchunk_rewriting() { - let script0 = r#"other:data\n1a:T3e,partial content"#; - let script1 = r#" with https://origin.example.com/page goes here"#; + let script0 = r"other:data\n1a:T3e,partial content"; + let script1 = " with https://origin.example.com/page goes here"; let combined_content = "partial content with https://origin.example.com/page goes here"; let combined_len = calculate_unescaped_byte_length(combined_content); - println!( - "Combined T-chunk content length: {} bytes = 0x{:x}", - combined_len, combined_len - ); + println!("Combined T-chunk content length: {combined_len} bytes = 0x{combined_len:x}"); let payloads: Vec<&str> = vec![script0, script1]; let results = rewrite_rsc_scripts_combined( @@ -631,7 +620,7 @@ mod tests { let rewritten_content = "partial content with https://test.example.com/page goes here"; let rewritten_len = calculate_unescaped_byte_length(rewritten_content); - let expected_header = format!(":T{:x},", rewritten_len); + let expected_header = format!(":T{rewritten_len:x},"); assert!( results[0].contains(&expected_header), "T-chunk length in script 0 should be updated to {}. Got: {}", @@ -643,7 +632,7 @@ mod tests { #[test] fn cross_script_preserves_non_tchunk_content() { let script0 = r#"{"url":"https://origin.example.com/first"}\n1a:T38,partial"#; - let script1 = r#" content with https://origin.example.com/page end"#; + let script1 = " content with https://origin.example.com/page end"; let payloads: Vec<&str> = vec![script0, script1]; let results = rewrite_rsc_scripts_combined( @@ -799,8 +788,8 @@ mod tests { #[test] fn size_limit_skips_rewrite_when_cross_script_tchunk_detected() { - let script0 = r#"other:data\n1a:T40,partial content"#; - let script1 = r#" with https://origin.example.com/page goes here"#; + let script0 = r"other:data\n1a:T40,partial content"; + let script1 = " with https://origin.example.com/page goes here"; let payloads: Vec<&str> = vec![script0, script1]; let results = rewrite_rsc_scripts_combined_with_limit( diff --git a/crates/trusted-server-core/src/integrations/nextjs/rsc_placeholders.rs b/crates/trusted-server-core/src/integrations/nextjs/rsc_placeholders.rs index 10101a70c..88bd85d4b 100644 --- a/crates/trusted-server-core/src/integrations/nextjs/rsc_placeholders.rs +++ b/crates/trusted-server-core/src/integrations/nextjs/rsc_placeholders.rs @@ -98,7 +98,7 @@ impl IntegrationScriptRewriter for NextJsRscPlaceholderRewriter { .payloads .push(content[payload_start..payload_end].to_string()); - let mut rewritten = content.to_string(); + let mut rewritten = content.to_owned(); rewritten.replace_range(payload_start..payload_end, &placeholder); ScriptRewriteAction::replace(rewritten) } @@ -109,10 +109,10 @@ mod tests { use super::*; use crate::integrations::IntegrationDocumentState; - fn ctx<'a>( + fn ctx( is_last_in_text_node: bool, - document_state: &'a IntegrationDocumentState, - ) -> IntegrationScriptContext<'a> { + document_state: &IntegrationDocumentState, + ) -> IntegrationScriptContext<'_> { IntegrationScriptContext { selector: "script", request_host: "proxy.example.com", diff --git a/crates/trusted-server-core/src/integrations/nextjs/script_rewriter.rs b/crates/trusted-server-core/src/integrations/nextjs/script_rewriter.rs index eaf00a16c..b78ba1c2d 100644 --- a/crates/trusted-server-core/src/integrations/nextjs/script_rewriter.rs +++ b/crates/trusted-server-core/src/integrations/nextjs/script_rewriter.rs @@ -143,13 +143,11 @@ impl UrlRewriter { .collect::>() .join("|"); let pattern = format!( - r#"(?P(?:\\*")?(?:{attrs})(?:\\*")?\s*:\s*\\*")(?P[^"\\]*)(?P\\*")"#, - attrs = attr_alternation, + r#"(?P(?:\\*")?(?:{attr_alternation})(?:\\*")?\s*:\s*\\*")(?P[^"\\]*)(?P\\*")"#, ); Some(Regex::new(&pattern).map_err(|err| { super::configuration_error(format!( - "failed to compile __NEXT_DATA__ URL rewrite regex for attributes {:?}: {err}", - attributes + "failed to compile __NEXT_DATA__ URL rewrite regex for attributes {attributes:?}: {err}" )) })?) }; @@ -177,7 +175,7 @@ impl UrlRewriter { return Some(format!("//{request_host}{path}")); } } else if url == origin_host { - return Some(request_host.to_string()); + return Some(request_host.to_owned()); } else if let Some(path) = strip_origin_host_with_optional_port(url, origin_host) { return Some(format!("{request_host}{path}")); } @@ -210,7 +208,7 @@ impl UrlRewriter { caps.get(0) .expect("should capture matched attribute value") .as_str() - .to_string() + .to_owned() } }); diff --git a/crates/trusted-server-core/src/integrations/nextjs/shared.rs b/crates/trusted-server-core/src/integrations/nextjs/shared.rs index 673d89569..ce7ad0572 100644 --- a/crates/trusted-server-core/src/integrations/nextjs/shared.rs +++ b/crates/trusted-server-core/src/integrations/nextjs/shared.rs @@ -71,10 +71,7 @@ pub(crate) fn strip_origin_host_with_optional_port<'a>( return Some(suffix); } - if matches!( - suffix.as_bytes().first(), - Some(b'/') | Some(b'?') | Some(b'#') - ) { + if matches!(suffix.as_bytes().first(), Some(b'/' | b'?' | b'#')) { return Some(suffix); } @@ -85,16 +82,8 @@ pub(crate) fn strip_origin_host_with_optional_port<'a>( } let rest = &port_and_rest[port_len..]; - if rest.is_empty() - || matches!( - rest.as_bytes().first(), - Some(b'/') | Some(b'?') | Some(b'#') - ) - { - Some(suffix) - } else { - None - } + (rest.is_empty() || matches!(rest.as_bytes().first(), Some(b'/' | b'?' | b'#'))) + .then_some(suffix) } // ============================================================================= @@ -111,7 +100,7 @@ pub(crate) fn strip_origin_host_with_optional_port<'a>( fn build_origin_url_pattern(origin_host: &str) -> Result { let escaped = regex::escape(origin_host); Regex::new(&format!( - r#"(https?)?(:)?(\\\\\\\\\\\\\\\\//|\\\\\\\\//|\\/\\/|//)(?P{escaped}(?::\d+)?)"# + r"(https?)?(:)?(\\\\\\\\\\\\\\\\//|\\\\\\\\//|\\/\\/|//)(?P{escaped}(?::\d+)?)" )) } @@ -174,7 +163,7 @@ impl RscUrlRewriter { Ok(pattern) => { let result = Self::apply_rewrite(input, &pattern, origin_host, request_host, request_scheme); - *self.cached_pattern.borrow_mut() = Some((origin_host.to_string(), pattern)); + *self.cached_pattern.borrow_mut() = Some((origin_host.to_owned(), pattern)); result } Err(e) => { @@ -199,7 +188,7 @@ impl RscUrlRewriter { .get(0) .expect("should capture the matched RSC URL") .as_str() - .to_string(); + .to_owned(); }; let slashes = caps.get(3).map_or("//", |m| m.as_str()); @@ -252,7 +241,7 @@ mod tests { #[test] fn finds_single_quoted_payload() { - let script = r#"self.__next_f.push([1,'hello world'])"#; + let script = "self.__next_f.push([1,'hello world'])"; let (start, end) = find_rsc_push_payload_range(script).expect("should find payload"); assert_eq!(&script[start..end], "hello world"); } diff --git a/crates/trusted-server-core/src/integrations/prebid.rs b/crates/trusted-server-core/src/integrations/prebid.rs index 8a2c61575..647d38385 100644 --- a/crates/trusted-server-core/src/integrations/prebid.rs +++ b/crates/trusted-server-core/src/integrations/prebid.rs @@ -2064,8 +2064,14 @@ server_url = "https://prebid.example" .expect("should have cache-control"); assert!(cache_control.contains("max-age=31536000")); - let body = String::from_utf8(response.into_body().into_bytes().to_vec()) - .expect("should parse script body as utf-8"); + let body = String::from_utf8( + response + .into_body() + .into_bytes() + .unwrap_or_default() + .to_vec(), + ) + .expect("should parse script body as utf-8"); assert!(body.contains("// Script overridden by Trusted Server")); } diff --git a/crates/trusted-server-core/src/integrations/registry.rs b/crates/trusted-server-core/src/integrations/registry.rs index 99df1c73b..4f991d2a9 100644 --- a/crates/trusted-server-core/src/integrations/registry.rs +++ b/crates/trusted-server-core/src/integrations/registry.rs @@ -136,10 +136,9 @@ impl IntegrationDocumentState { .inner .lock() .expect("should lock integration document state"); - guard.get(integration_id).and_then(|value| { - let cloned: Arc = Arc::clone(value); - cloned.downcast::().ok() - }) + let value = guard.get(integration_id)?; + let cloned: Arc = Arc::clone(value); + cloned.downcast::().ok() } /// Retrieves or initializes a value for an integration. @@ -866,19 +865,13 @@ impl IntegrationRegistry { } inner .html_rewriters - .extend(registration.attribute_rewriters.into_iter()); - inner - .script_rewriters - .extend(registration.script_rewriters.into_iter()); + .extend(registration.attribute_rewriters); + inner.script_rewriters.extend(registration.script_rewriters); inner .html_post_processors - .extend(registration.html_post_processors.into_iter()); - inner - .head_injectors - .extend(registration.head_injectors.into_iter()); - inner - .request_filters - .extend(registration.request_filters.into_iter()); + .extend(registration.html_post_processors); + inner.head_injectors.extend(registration.head_injectors); + inner.request_filters.extend(registration.request_filters); if registration.js_deferred { inner.deferred_js_ids.push(registration.integration_id); } @@ -996,8 +989,7 @@ impl IntegrationRegistry { } } else { log::debug!( - "EC generation skipped for integration proxy: non-document request (path={})", - path, + "EC generation skipped for integration proxy: non-document request (path={path})", ); } @@ -1018,7 +1010,7 @@ impl IntegrationRegistry { attr_value: &str, ctx: &IntegrationAttributeContext<'_>, ) -> AttributeRewriteOutcome { - let mut current = attr_value.to_string(); + let mut current = attr_value.to_owned(); let mut changed = false; for rewriter in &self.inner.html_rewriters { if !rewriter.handles_attribute(attr_name) { @@ -1270,7 +1262,7 @@ impl IntegrationRegistry { path.strip_suffix("/*").expect("path should end with '/*'") ) } else { - path.to_string() + path.to_owned() }; let router = match method { @@ -1729,11 +1721,11 @@ mod tests { vec![ IntegrationEndpoint { method: Method::GET, - path: "/integrations/test/ec".to_string(), + path: "/integrations/test/ec".to_owned(), }, IntegrationEndpoint { method: Method::POST, - path: "/integrations/test/ec".to_string(), + path: "/integrations/test/ec".to_owned(), }, ] } @@ -2044,10 +2036,10 @@ mod tests { let all = registry.js_module_ids(); let mut recombined = registry.js_module_ids_immediate(); recombined.extend(registry.js_module_ids_deferred()); - recombined.sort(); + recombined.sort_unstable(); let mut all_sorted = all; - all_sorted.sort(); + all_sorted.sort_unstable(); assert_eq!( recombined, all_sorted, diff --git a/crates/trusted-server-core/src/integrations/testlight.rs b/crates/trusted-server-core/src/integrations/testlight.rs index f4d1e3263..d6f5d30e4 100644 --- a/crates/trusted-server-core/src/integrations/testlight.rs +++ b/crates/trusted-server-core/src/integrations/testlight.rs @@ -458,7 +458,7 @@ mod tests { "should route outbound request through PlatformHttpClient" ); let response_json: serde_json::Value = - serde_json::from_slice(&response.into_body().into_bytes()) + serde_json::from_slice(&response.into_body().into_bytes().unwrap_or_default()) .expect("should parse JSON response"); assert_eq!( response_json["ok"], true, diff --git a/crates/trusted-server-core/src/lib.rs b/crates/trusted-server-core/src/lib.rs index 2a5632e99..d2a218c90 100644 --- a/crates/trusted-server-core/src/lib.rs +++ b/crates/trusted-server-core/src/lib.rs @@ -26,6 +26,7 @@ clippy::panic, clippy::dbg_macro, clippy::unwrap_used, + reason = "tests use direct diagnostics and panic-on-failure helpers" ) )] diff --git a/crates/trusted-server-core/src/models.rs b/crates/trusted-server-core/src/models.rs index a6178b68c..87cadcb94 100644 --- a/crates/trusted-server-core/src/models.rs +++ b/crates/trusted-server-core/src/models.rs @@ -9,7 +9,10 @@ use serde::Deserialize; /// /// Contains all the information needed to display an ad and track /// its performance through various callbacks. -#[allow(dead_code)] +#[allow( + dead_code, + reason = "model includes fields deserialized for external ad-server compatibility" +)] #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct AdResponse { @@ -158,7 +161,7 @@ mod tests { }); let result: Result = serde_json::from_value(json_data); - assert!(result.is_err()); + result.unwrap_err(); } #[test] @@ -193,7 +196,7 @@ mod tests { }); let result: Result = serde_json::from_value(json_data); - assert!(result.is_err()); + result.unwrap_err(); } #[test] @@ -215,24 +218,24 @@ mod tests { #[test] fn test_ad_response_debug_format() { let callback = Callback { - callback_type: "test".to_string(), - url: "https://test.com".to_string(), + callback_type: "test".to_owned(), + url: "https://test.com".to_owned(), }; let ad_response = AdResponse { - network_id: "123".to_string(), - site_id: "456".to_string(), - page_id: "789".to_string(), - format_id: "000".to_string(), - advertiser_id: "111".to_string(), - campaign_id: "222".to_string(), - insertion_id: "333".to_string(), - creative_id: "444".to_string(), - creative_url: "https://example.com/ad.jpg".to_string(), + network_id: "123".to_owned(), + site_id: "456".to_owned(), + page_id: "789".to_owned(), + format_id: "000".to_owned(), + advertiser_id: "111".to_owned(), + campaign_id: "222".to_owned(), + insertion_id: "333".to_owned(), + creative_id: "444".to_owned(), + creative_url: "https://example.com/ad.jpg".to_owned(), callbacks: vec![callback], }; - let debug_str = format!("{:?}", ad_response); + let debug_str = format!("{ad_response:?}"); assert!(debug_str.contains("AdResponse")); assert!(debug_str.contains("network_id")); assert!(debug_str.contains("123")); @@ -241,11 +244,11 @@ mod tests { #[test] fn test_callback_debug_format() { let callback = Callback { - callback_type: "debug_test".to_string(), - url: "https://debug.test.com".to_string(), + callback_type: "debug_test".to_owned(), + url: "https://debug.test.com".to_owned(), }; - let debug_str = format!("{:?}", callback); + let debug_str = format!("{callback:?}"); assert!(debug_str.contains("Callback")); assert!(debug_str.contains("callback_type")); assert!(debug_str.contains("debug_test")); @@ -276,10 +279,7 @@ mod tests { let callback: Callback = serde_json::from_value(json_data).expect("should deserialize callback type"); assert_eq!(callback.callback_type, cb_type); - assert_eq!( - callback.url, - format!("https://example.com/track/{}", cb_type) - ); + assert_eq!(callback.url, format!("https://example.com/track/{cb_type}")); } } } diff --git a/crates/trusted-server-core/src/openrtb.rs b/crates/trusted-server-core/src/openrtb.rs index d480615ad..a9f02adf3 100644 --- a/crates/trusted-server-core/src/openrtb.rs +++ b/crates/trusted-server-core/src/openrtb.rs @@ -15,17 +15,13 @@ pub use trusted_server_openrtb::{ /// returning `None` if the value exceeds `i32::MAX`. #[must_use] pub fn to_openrtb_i32(value: u32, field_name: &str, context: &str) -> Option { - match i32::try_from(value) { - Ok(converted) => Some(converted), - Err(_) => { - log::warn!( - "openrtb: omitting {}={} for {} because value exceeds i32::MAX", - field_name, - value, - context - ); - None - } + if let Ok(converted) = i32::try_from(value) { + Some(converted) + } else { + log::warn!( + "openrtb: omitting {field_name}={value} for {context} because value exceeds i32::MAX" + ); + None } } @@ -176,26 +172,26 @@ mod tests { #[test] fn openrtb_response_round_trips_with_struct_literals() { let bid = OpenRtbBid { - id: Some("bidder-a-slot-1".to_string()), - impid: Some("slot-1".to_string()), + id: Some("bidder-a-slot-1".to_owned()), + impid: Some("slot-1".to_owned()), price: Some(1.25), - adm: Some("

Test Creative HTML
".to_string()), - crid: Some("bidder-a-creative".to_string()), + adm: Some("
Test Creative HTML
".to_owned()), + crid: Some("bidder-a-creative".to_owned()), w: Some(300), h: Some(250), - adomain: vec!["example.com".to_string()], + adomain: vec!["example.com".to_owned()], ..Default::default() }; let seatbid = SeatBid { - seat: Some("bidder-a".to_string()), + seat: Some("bidder-a".to_owned()), bid: vec![bid], ..Default::default() }; let ext = ResponseExt { orchestrator: OrchestratorExt { - strategy: "parallel_only".to_string(), + strategy: "parallel_only".to_owned(), providers: 2, total_bids: 3, time_ms: 12, @@ -205,7 +201,7 @@ mod tests { .to_ext(); let response = OpenRtbResponse { - id: Some("auction-1".to_string()), + id: Some("auction-1".to_owned()), seatbid: vec![seatbid], ext, ..Default::default() @@ -246,8 +242,8 @@ mod tests { // Mirror the production pattern: build ext, then duplicate into top-level. let ext = RegsExt { gdpr: Some(1), - us_privacy: Some("1YNN".to_string()), - gpp: Some("DBACNY~CPXxRfA".to_string()), + us_privacy: Some("1YNN".to_owned()), + gpp: Some("DBACNY~CPXxRfA".to_owned()), gpp_sid: Some(vec![2, 6]), }; let regs = Regs { @@ -322,12 +318,12 @@ mod tests { #[test] fn user_serializes_dual_placement_consent() { let user = User { - id: Some("user-1".to_string()), - consent: Some("CPXxGfAPXxGfA".to_string()), + id: Some("user-1".to_owned()), + consent: Some("CPXxGfAPXxGfA".to_owned()), ext: UserExt { - consent: Some("CPXxGfAPXxGfA".to_string()), + consent: Some("CPXxGfAPXxGfA".to_owned()), consented_providers_settings: Some(ConsentedProvidersSettings { - consented_providers: Some("2~2628.2316~dv.".to_string()), + consented_providers: Some("2~2628.2316~dv.".to_owned()), }), eids: None, } @@ -354,7 +350,7 @@ mod tests { #[test] fn user_omits_consent_when_none() { let user = User { - id: Some("user-1".to_string()), + id: Some("user-1".to_owned()), consent: None, ext: None, ..Default::default() @@ -370,9 +366,9 @@ mod tests { #[test] fn eid_serializes_correctly() { let eid = Eid { - source: "id5-sync.com".to_string(), + source: "id5-sync.com".to_owned(), uids: vec![Uid { - id: "ID5-abc123".to_string(), + id: "ID5-abc123".to_owned(), atype: Some(1), ext: None, }], diff --git a/crates/trusted-server-core/src/platform/mod.rs b/crates/trusted-server-core/src/platform/mod.rs index a2caf67d0..805fc55dc 100644 --- a/crates/trusted-server-core/src/platform/mod.rs +++ b/crates/trusted-server-core/src/platform/mod.rs @@ -76,7 +76,7 @@ mod tests { impl PlatformKvStore for MarkerKvStore { async fn get_bytes(&self, key: &str) -> Result, KvError> { if key == "marker" { - Ok(Some(Bytes::from(self.0.to_string()))) + Ok(Some(Bytes::from(self.0.to_owned()))) } else { Ok(None) } @@ -199,13 +199,13 @@ mod tests { #[test] fn geo_info_coordinates_string_formats_correctly() { let geo = GeoInfo { - city: "New York".to_string(), - country: "US".to_string(), - continent: "NorthAmerica".to_string(), + city: "New York".to_owned(), + country: "US".to_owned(), + continent: "NorthAmerica".to_owned(), latitude: 40.7128, longitude: -74.0060, metro_code: 501, - region: Some("NY".to_string()), + region: Some("NY".to_owned()), asn: None, }; diff --git a/crates/trusted-server-core/src/platform/test_support.rs b/crates/trusted-server-core/src/platform/test_support.rs index 36182fea5..e3a217575 100644 --- a/crates/trusted-server-core/src/platform/test_support.rs +++ b/crates/trusted-server-core/src/platform/test_support.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use base64::{engine::general_purpose, Engine as _}; use ed25519_dalek::SigningKey; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use rand::rngs::OsRng; use super::{ @@ -179,11 +179,11 @@ pub(crate) struct StubBackend; impl PlatformBackend for StubBackend { fn predict_name(&self, _spec: &PlatformBackendSpec) -> Result> { - Ok("stub-backend".to_string()) + Ok("stub-backend".to_owned()) } fn ensure(&self, _spec: &PlatformBackendSpec) -> Result> { - Ok("stub-backend".to_string()) + Ok("stub-backend".to_owned()) } } @@ -365,7 +365,7 @@ impl PlatformHttpClient for StubHttpClient { value .to_str() .ok() - .map(|v| (name.as_str().to_string(), v.to_string())) + .map(|v| (name.as_str().to_owned(), v.to_owned())) }) .collect(); self.request_headers @@ -540,17 +540,15 @@ pub(crate) fn build_request_signing_services() -> RuntimeServices { let signing_key = SigningKey::generate(&mut OsRng); let key_b64 = general_purpose::STANDARD.encode(signing_key.as_bytes()); let x_b64 = general_purpose::URL_SAFE_NO_PAD.encode(signing_key.verifying_key().as_bytes()); - let jwk_json = format!( - r#"{{"kty":"OKP","crv":"Ed25519","x":"{}","kid":"test-kid","alg":"EdDSA"}}"#, - x_b64 - ); + let jwk_json = + format!(r#"{{"kty":"OKP","crv":"Ed25519","x":"{x_b64}","kid":"test-kid","alg":"EdDSA"}}"#); let mut config_data = HashMap::new(); - config_data.insert("current-kid".to_string(), "test-kid".to_string()); - config_data.insert("test-kid".to_string(), jwk_json); + config_data.insert("current-kid".to_owned(), "test-kid".to_owned()); + config_data.insert("test-kid".to_owned(), jwk_json); let mut secret_data = HashMap::new(); - secret_data.insert("test-kid".to_string(), key_b64.into_bytes()); + secret_data.insert("test-kid".to_owned(), key_b64.into_bytes()); build_services_with_config_and_secret( HashMapConfigStore::new(config_data), @@ -804,8 +802,8 @@ mod tests { fn stub_backend_returns_fixed_name() { let stub = StubBackend; let spec = PlatformBackendSpec { - scheme: "https".to_string(), - host: "example.com".to_string(), + scheme: "https".to_owned(), + host: "example.com".to_owned(), port: None, certificate_check: true, first_byte_timeout: DEFAULT_FIRST_BYTE_TIMEOUT, @@ -838,10 +836,10 @@ mod tests { #[test] fn hash_map_stores_return_preset_values() { let mut config = HashMap::new(); - config.insert("current-kid".to_string(), "test-kid".to_string()); + config.insert("current-kid".to_owned(), "test-kid".to_owned()); let mut secrets = HashMap::new(); - secrets.insert("test-kid".to_string(), b"secret-material".to_vec()); + secrets.insert("test-kid".to_owned(), b"secret-material".to_vec()); let services = build_services_with_config_and_secret( HashMapConfigStore::new(config), diff --git a/crates/trusted-server-core/src/platform/types.rs b/crates/trusted-server-core/src/platform/types.rs index 33250d1d1..0b0cec862 100644 --- a/crates/trusted-server-core/src/platform/types.rs +++ b/crates/trusted-server-core/src/platform/types.rs @@ -84,7 +84,7 @@ impl From for StoreName { impl From<&str> for StoreName { fn from(s: &str) -> Self { - Self(s.to_string()) + Self(s.to_owned()) } } @@ -111,7 +111,7 @@ impl From for StoreId { impl From<&str> for StoreId { fn from(s: &str) -> Self { - Self(s.to_string()) + Self(s.to_owned()) } } diff --git a/crates/trusted-server-core/src/proxy.rs b/crates/trusted-server-core/src/proxy.rs index bcd70e18c..3d18ecead 100644 --- a/crates/trusted-server-core/src/proxy.rs +++ b/crates/trusted-server-core/src/proxy.rs @@ -38,7 +38,7 @@ const SIGN_MAX_BODY_BYTES: usize = 65536; const REBUILD_MAX_BODY_BYTES: usize = 65536; fn body_as_reader(body: EdgeBody) -> Cursor { - Cursor::new(body.into_bytes()) + Cursor::new(body.into_bytes().unwrap_or_default()) } /// Headers copied from the original client request to the upstream proxy request @@ -1521,7 +1521,7 @@ pub async fn handle_first_party_proxy_sign( let req_url = req.uri().to_string(); let payload = if method == Method::POST { - let body_bytes = req.into_body().into_bytes(); + let body_bytes = req.into_body().into_bytes().unwrap_or_default(); enforce_max_body_size(&body_bytes, SIGN_MAX_BODY_BYTES, "first-party sign")?; let body = std::str::from_utf8(&body_bytes).change_context(TrustedServerError::InvalidUtf8 { @@ -1636,7 +1636,7 @@ pub async fn handle_first_party_proxy_rebuild( let method = req.method().clone(); let req_url = req.uri().to_string(); let payload = if method == Method::POST { - let body_bytes = req.into_body().into_bytes(); + let body_bytes = req.into_body().into_bytes().unwrap_or_default(); enforce_max_body_size(&body_bytes, REBUILD_MAX_BODY_BYTES, "first-party rebuild")?; let body = std::str::from_utf8(&body_bytes).change_context(TrustedServerError::InvalidUtf8 { @@ -2006,8 +2006,14 @@ mod tests { } fn response_body_string(response: http::Response) -> String { - String::from_utf8(response.into_body().into_bytes().to_vec()) - .expect("response body should be valid UTF-8") + String::from_utf8( + response + .into_body() + .into_bytes() + .unwrap_or_default() + .to_vec(), + ) + .expect("response body should be valid UTF-8") } fn build_http_response(status: StatusCode, body: EdgeBody) -> Response { @@ -2708,7 +2714,7 @@ mod tests { assert_eq!(ct, "text/html; charset=utf-8"); // Decompress output to verify content was rewritten - let compressed_output = out.into_body().into_bytes(); + let compressed_output = out.into_body().into_bytes().unwrap_or_default(); let mut decoder = GzDecoder::new(&compressed_output[..]); let mut decompressed = String::new(); decoder @@ -2764,7 +2770,7 @@ mod tests { assert_eq!(ct, "text/css; charset=utf-8"); // Decompress output to verify content was rewritten - let compressed_output = out.into_body().into_bytes(); + let compressed_output = out.into_body().into_bytes().unwrap_or_default(); let mut decoder = Decompressor::new(&compressed_output[..], 4096); let mut decompressed = String::new(); decoder diff --git a/crates/trusted-server-core/src/publisher.rs b/crates/trusted-server-core/src/publisher.rs index db8a17780..76c3406ab 100644 --- a/crates/trusted-server-core/src/publisher.rs +++ b/crates/trusted-server-core/src/publisher.rs @@ -43,7 +43,7 @@ const SUPPORTED_ENCODING_VALUES: [&str; 3] = ["gzip", "deflate", "br"]; const DEFAULT_PUBLISHER_FIRST_BYTE_TIMEOUT: Duration = Duration::from_secs(15); fn body_as_reader(body: EdgeBody) -> std::io::Cursor { - std::io::Cursor::new(body.into_bytes()) + std::io::Cursor::new(body.into_bytes().unwrap_or_default()) } fn not_found_response() -> Response { @@ -692,8 +692,14 @@ mod tests { } fn response_body_string(response: http::Response) -> String { - String::from_utf8(response.into_body().into_bytes().to_vec()) - .expect("response body should be valid UTF-8") + String::from_utf8( + response + .into_body() + .into_bytes() + .unwrap_or_default() + .to_vec(), + ) + .expect("response body should be valid UTF-8") } #[test] @@ -1021,7 +1027,7 @@ mod tests { // Reattach and verify body content *response.body_mut() = body; let (_, final_body) = response.into_parts(); - let output = final_body.into_bytes(); + let output = final_body.into_bytes().unwrap_or_default(); assert_eq!( output, image_bytes, "pass-through should preserve body byte-for-byte" @@ -1467,7 +1473,7 @@ mod tests { "2048" ); let (_, final_body) = response.into_parts(); - let round_trip = final_body.into_bytes(); + let round_trip = final_body.into_bytes().unwrap_or_default(); assert_eq!( round_trip, image_bytes, "pass-through reattach must preserve bytes exactly" diff --git a/crates/trusted-server-core/src/redacted.rs b/crates/trusted-server-core/src/redacted.rs index 8b1d05bbd..3389c4c81 100644 --- a/crates/trusted-server-core/src/redacted.rs +++ b/crates/trusted-server-core/src/redacted.rs @@ -31,7 +31,10 @@ pub struct Redacted(T); impl Redacted { /// Creates a new [`Redacted`] value. - #[allow(dead_code)] // Used by the library crate but not by build.rs which includes this file via #[path] + #[allow( + dead_code, + reason = "used by the library crate but not by build.rs path inclusion" + )] pub fn new(value: T) -> Self { Self(value) } @@ -74,9 +77,9 @@ mod tests { #[test] fn debug_output_is_redacted() { - let secret = Redacted::new("super-secret".to_string()); + let secret = Redacted::new("super-secret".to_owned()); assert_eq!( - format!("{:?}", secret), + format!("{secret:?}"), "[REDACTED]", "should print [REDACTED] in debug output" ); @@ -84,9 +87,9 @@ mod tests { #[test] fn display_output_is_redacted() { - let secret = Redacted::new("super-secret".to_string()); + let secret = Redacted::new("super-secret".to_owned()); assert_eq!( - format!("{}", secret), + format!("{secret}"), "[REDACTED]", "should print [REDACTED] in display output" ); @@ -94,7 +97,7 @@ mod tests { #[test] fn expose_returns_inner_value() { - let secret = Redacted::new("super-secret".to_string()); + let secret = Redacted::new("super-secret".to_owned()); assert_eq!( secret.expose(), "super-secret", @@ -110,13 +113,13 @@ mod tests { #[test] fn from_string_creates_redacted() { - let secret = Redacted::from("my-key".to_string()); + let secret = Redacted::from("my-key".to_owned()); assert_eq!(secret.expose(), "my-key", "should create from String"); } #[test] fn clone_preserves_inner_value() { - let secret = Redacted::new("cloneable".to_string()); + let secret = Redacted::new("cloneable".to_owned()); let cloned = secret.clone(); assert_eq!( cloned.expose(), @@ -127,7 +130,7 @@ mod tests { #[test] fn serde_roundtrip() { - let secret = Redacted::new("serialize-me".to_string()); + let secret = Redacted::new("serialize-me".to_owned()); let json = serde_json::to_string(&secret).expect("should serialize"); assert_eq!(json, "\"serialize-me\"", "should serialize transparently"); @@ -143,18 +146,21 @@ mod tests { #[test] fn struct_with_redacted_field_debug() { #[derive(Debug)] - #[allow(dead_code)] + #[allow( + dead_code, + reason = "test fixture fields are read only through derived Debug output" + )] struct Config { name: String, api_key: Redacted, } let config = Config { - name: "test".to_string(), - api_key: Redacted::new("secret-key-123".to_string()), + name: "test".to_owned(), + api_key: Redacted::new("secret-key-123".to_owned()), }; - let debug = format!("{:?}", config); + let debug = format!("{config:?}"); assert!( debug.contains("[REDACTED]"), "should contain [REDACTED] for the api_key field" diff --git a/crates/trusted-server-core/src/request_signing/discovery.rs b/crates/trusted-server-core/src/request_signing/discovery.rs index 4dffa339b..b628ecccb 100644 --- a/crates/trusted-server-core/src/request_signing/discovery.rs +++ b/crates/trusted-server-core/src/request_signing/discovery.rs @@ -21,7 +21,7 @@ impl TrustedServerDiscovery { #[must_use] pub fn new(jwks_value: serde_json::Value) -> Self { Self { - version: "1.0".to_string(), + version: "1.0".to_owned(), jwks: jwks_value, } } diff --git a/crates/trusted-server-core/src/request_signing/endpoints.rs b/crates/trusted-server-core/src/request_signing/endpoints.rs index b4750578f..5e3b1d050 100644 --- a/crates/trusted-server-core/src/request_signing/endpoints.rs +++ b/crates/trusted-server-core/src/request_signing/endpoints.rs @@ -100,7 +100,7 @@ pub fn handle_verify_signature( services: &RuntimeServices, req: Request, ) -> Result, Report> { - let body = req.into_body().into_bytes(); + let body = req.into_body().into_bytes().unwrap_or_default(); enforce_max_body_size(&body, VERIFY_MAX_BODY_BYTES, "verify-signature")?; let verify_req: VerifySignatureRequest = serde_json::from_slice(&body).change_context(TrustedServerError::Configuration { @@ -243,7 +243,7 @@ pub fn handle_rotate_key( secret_store_id, } = signing_store_ids(settings)?; - let body = req.into_body().into_bytes(); + let body = req.into_body().into_bytes().unwrap_or_default(); enforce_max_body_size(&body, ADMIN_MAX_BODY_BYTES, "rotate-key")?; let rotate_req: RotateKeyRequest = if body.is_empty() { RotateKeyRequest { kid: None } @@ -362,7 +362,7 @@ pub fn handle_deactivate_key( secret_store_id, } = signing_store_ids(settings)?; - let body = req.into_body().into_bytes(); + let body = req.into_body().into_bytes().unwrap_or_default(); enforce_max_body_size(&body, ADMIN_MAX_BODY_BYTES, "deactivate-key")?; let deactivate_req: DeactivateKeyRequest = serde_json::from_slice(&body).change_context(TrustedServerError::Configuration { @@ -461,8 +461,14 @@ mod tests { } fn response_body_string(response: http::Response) -> String { - String::from_utf8(response.into_body().into_bytes().to_vec()) - .expect("should decode response body") + String::from_utf8( + response + .into_body() + .into_bytes() + .unwrap_or_default() + .to_vec(), + ) + .expect("should decode response body") } fn assert_json_content_type(response: &http::Response) { diff --git a/crates/trusted-server-core/src/request_signing/jwks.rs b/crates/trusted-server-core/src/request_signing/jwks.rs index 8d2066390..7c81332eb 100644 --- a/crates/trusted-server-core/src/request_signing/jwks.rs +++ b/crates/trusted-server-core/src/request_signing/jwks.rs @@ -4,7 +4,7 @@ //! Ed25519 keypairs in JWK format for request signing. use ed25519_dalek::{SigningKey, VerifyingKey}; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use jose_jwk::{ jose_jwa::{Algorithm, Signing}, Jwk, Key, Okp, OkpCurves, Parameters, @@ -77,18 +77,18 @@ pub fn get_active_jwks(services: &RuntimeServices) -> Result = fn parse_active_kids(active_kids: &str) -> Vec { active_kids .split(',') - .map(|kid| kid.trim().to_string()) + .map(|kid| kid.trim().to_owned()) .filter(|kid| !kid.is_empty()) .collect() } diff --git a/crates/trusted-server-core/src/request_signing/rotation.rs b/crates/trusted-server-core/src/request_signing/rotation.rs index f78ac882f..acc9a0a52 100644 --- a/crates/trusted-server-core/src/request_signing/rotation.rs +++ b/crates/trusted-server-core/src/request_signing/rotation.rs @@ -4,10 +4,10 @@ //! lifecycle, and storing keys via platform store primitives through //! [`RuntimeServices`]. -use base64::{engine::general_purpose, Engine}; +use base64::{engine::general_purpose, Engine as _}; use chrono::Utc; use ed25519_dalek::SigningKey; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use jose_jwk::Jwk; use uuid::Uuid; @@ -74,7 +74,7 @@ impl KeyRotationManager { Some(kid) => { if self.key_exists(services, &kid, &active_kids) { return Err(Report::new(TrustedServerError::Configuration { - message: format!("kid '{}' already exists; choose a unique kid", kid), + message: format!("kid '{kid}' already exists; choose a unique kid"), })); } kid @@ -96,9 +96,7 @@ impl KeyRotationManager { .delete(&self.secret_store_id, &new_kid) { log::warn!( - "rotate_key: rollback of private key '{}' failed after JWK write error: {}", - new_kid, - rollback_err + "rotate_key: rollback of private key '{new_kid}' failed after JWK write error: {rollback_err}" ); } return Err(err); @@ -118,9 +116,7 @@ impl KeyRotationManager { .delete(&self.config_store_id, &new_kid) { log::warn!( - "rotate_key: rollback of JWK '{}' failed after active-kids write error: {}", - new_kid, - rollback_err + "rotate_key: rollback of JWK '{new_kid}' failed after active-kids write error: {rollback_err}" ); } if let Err(rollback_err) = services @@ -128,9 +124,7 @@ impl KeyRotationManager { .delete(&self.secret_store_id, &new_kid) { log::warn!( - "rotate_key: rollback of private key '{}' failed after active-kids write error: {}", - new_kid, - rollback_err + "rotate_key: rollback of private key '{new_kid}' failed after active-kids write error: {rollback_err}" ); } return Err(err); @@ -181,7 +175,7 @@ impl KeyRotationManager { .secret_store() .create(&self.secret_store_id, kid, &key_b64) .change_context(TrustedServerError::Configuration { - message: format!("failed to store private key '{}'", kid), + message: format!("failed to store private key '{kid}'"), }) } @@ -193,7 +187,7 @@ impl KeyRotationManager { ) -> Result<(), Report> { let jwk_json = serde_json::to_string(jwk).map_err(|e| { Report::new(TrustedServerError::Configuration { - message: format!("failed to serialize JWK: {}", e), + message: format!("failed to serialize JWK: {e}"), }) })?; @@ -201,7 +195,7 @@ impl KeyRotationManager { .config_store() .put(&self.config_store_id, kid, &jwk_json) .change_context(TrustedServerError::Configuration { - message: format!("failed to store public JWK '{}'", kid), + message: format!("failed to store public JWK '{kid}'"), }) } @@ -428,14 +422,14 @@ mod tests { } self.inner.puts.lock().expect("should lock puts").push(( store_id.to_string(), - key.to_string(), - value.to_string(), + key.to_owned(), + value.to_owned(), )); self.inner .data .lock() .expect("should lock data") - .insert(key.to_string(), value.to_string()); + .insert(key.to_owned(), value.to_owned()); Ok(()) } @@ -444,7 +438,7 @@ mod tests { .deletes .lock() .expect("should lock deletes") - .push((store_id.to_string(), key.to_string())); + .push((store_id.to_string(), key.to_owned())); self.inner .data .lock() @@ -530,7 +524,7 @@ mod tests { .creates .lock() .expect("should lock creates") - .push((store_id.to_string(), name.to_string(), value.to_string())); + .push((store_id.to_string(), name.to_owned(), value.to_owned())); Ok(()) } @@ -539,7 +533,7 @@ mod tests { .deletes .lock() .expect("should lock deletes") - .push((store_id.to_string(), name.to_string())); + .push((store_id.to_string(), name.to_owned())); Ok(()) } } @@ -580,13 +574,13 @@ mod tests { let services = build_services_with_config_and_secret(config_store, secret_store); let manager = KeyRotationManager::new("cfg-id", "sec-id"); - let result = manager.rotate_key(&services, Some("new-kid".to_string())); + let result = manager.rotate_key(&services, Some("new-kid".to_owned())); assert!(result.is_ok(), "should succeed when stores accept writes"); let rotation = result.expect("should produce rotation result"); assert_eq!(rotation.new_kid, "new-kid", "should use the provided kid"); assert!( - rotation.active_kids.contains(&"new-kid".to_string()), + rotation.active_kids.contains(&"new-kid".to_owned()), "should include new kid in active kids" ); } @@ -594,8 +588,8 @@ mod tests { #[test] fn rotate_key_preserves_existing_active_kids() { let mut data = HashMap::new(); - data.insert("current-kid".to_string(), "kid-b".to_string()); - data.insert("active-kids".to_string(), "kid-a, kid-b".to_string()); + data.insert("current-kid".to_owned(), "kid-b".to_owned()); + data.insert("active-kids".to_owned(), "kid-a, kid-b".to_owned()); let config_store = SpyConfigStore::new(data); let secret_store = SpySecretStore::new(); @@ -603,16 +597,12 @@ mod tests { let manager = KeyRotationManager::new("cfg-id", "sec-id"); let rotation = manager - .rotate_key(&services, Some("kid-c".to_string())) + .rotate_key(&services, Some("kid-c".to_owned())) .expect("should rotate key successfully"); assert_eq!( rotation.active_kids, - vec![ - "kid-a".to_string(), - "kid-b".to_string(), - "kid-c".to_string() - ], + vec!["kid-a".to_owned(), "kid-b".to_owned(), "kid-c".to_owned()], "should preserve previously active keys and append the new kid" ); @@ -621,11 +611,7 @@ mod tests { .expect("should read back updated active kids"); assert_eq!( active_kids, - vec![ - "kid-a".to_string(), - "kid-b".to_string(), - "kid-c".to_string() - ], + vec!["kid-a".to_owned(), "kid-b".to_owned(), "kid-c".to_owned()], "should store the full active kid list after rotation" ); } @@ -633,8 +619,8 @@ mod tests { #[test] fn rotate_key_does_not_reactivate_deactivated_previous_kid() { let mut data = HashMap::new(); - data.insert("current-kid".to_string(), "kid-a".to_string()); - data.insert("active-kids".to_string(), "kid-b".to_string()); + data.insert("current-kid".to_owned(), "kid-a".to_owned()); + data.insert("active-kids".to_owned(), "kid-b".to_owned()); let config_store = SpyConfigStore::new(data); let secret_store = SpySecretStore::new(); @@ -642,12 +628,12 @@ mod tests { let manager = KeyRotationManager::new("cfg-id", "sec-id"); let rotation = manager - .rotate_key(&services, Some("kid-c".to_string())) + .rotate_key(&services, Some("kid-c".to_owned())) .expect("should rotate key successfully"); assert_eq!( rotation.active_kids, - vec!["kid-b".to_string(), "kid-c".to_string()], + vec!["kid-b".to_owned(), "kid-c".to_owned()], "should not resurrect a previous kid that is no longer active" ); } @@ -655,8 +641,8 @@ mod tests { #[test] fn rotate_key_rejects_explicit_kid_that_is_already_active() { let mut data = HashMap::new(); - data.insert("current-kid".to_string(), "kid-b".to_string()); - data.insert("active-kids".to_string(), "kid-a,kid-b".to_string()); + data.insert("current-kid".to_owned(), "kid-b".to_owned()); + data.insert("active-kids".to_owned(), "kid-a,kid-b".to_owned()); let config_store = SpyConfigStore::new(data); let secret_store = SpySecretStore::new(); @@ -664,7 +650,7 @@ mod tests { build_services_with_config_and_secret(config_store.clone(), secret_store.clone()); let manager = KeyRotationManager::new("cfg-id", "sec-id"); - let result = manager.rotate_key(&services, Some("kid-a".to_string())); + let result = manager.rotate_key(&services, Some("kid-a".to_owned())); assert!( result.is_err(), @@ -684,8 +670,8 @@ mod tests { fn rotate_key_uniquifies_generated_kid_when_date_based_kid_is_active() { let base_kid = generate_date_based_kid(); let mut data = HashMap::new(); - data.insert("current-kid".to_string(), base_kid.clone()); - data.insert("active-kids".to_string(), base_kid.clone()); + data.insert("current-kid".to_owned(), base_kid.clone()); + data.insert("active-kids".to_owned(), base_kid.clone()); let config_store = SpyConfigStore::new(data); let secret_store = SpySecretStore::new(); @@ -717,7 +703,7 @@ mod tests { #[test] fn deactivate_key_fails_when_only_one_key_remains() { let mut data = HashMap::new(); - data.insert("active-kids".to_string(), "only-key".to_string()); + data.insert("active-kids".to_owned(), "only-key".to_owned()); let config_store = SpyConfigStore::new(data); let secret_store = SpySecretStore::new(); let services = build_services_with_config_and_secret(config_store, secret_store); @@ -733,18 +719,18 @@ mod tests { #[test] fn key_rotation_result_structure_is_valid() { - let jwk = Keypair::generate().get_jwk("test-key".to_string()); + let jwk = Keypair::generate().get_jwk("test-key".to_owned()); let result = KeyRotationResult { - new_kid: "ts-2024-01-01".to_string(), - previous_kid: Some("ts-2023-12-31".to_string()), - active_kids: vec!["ts-2023-12-31".to_string(), "ts-2024-01-01".to_string()], + new_kid: "ts-2024-01-01".to_owned(), + previous_kid: Some("ts-2023-12-31".to_owned()), + active_kids: vec!["ts-2023-12-31".to_owned(), "ts-2024-01-01".to_owned()], jwk: jwk.clone(), }; assert_eq!(result.new_kid, "ts-2024-01-01"); - assert_eq!(result.previous_kid, Some("ts-2023-12-31".to_string())); + assert_eq!(result.previous_kid, Some("ts-2023-12-31".to_owned())); assert_eq!(result.active_kids.len(), 2); - assert_eq!(result.jwk.prm.kid, Some("test-key".to_string())); + assert_eq!(result.jwk.prm.kid, Some("test-key".to_owned())); } #[test] @@ -754,7 +740,7 @@ mod tests { let services = build_services_with_config_and_secret(config_store, secret_store); let manager = KeyRotationManager::new("cfg-id", "sec-id"); - let result = manager.rotate_key(&services, Some("new-kid".to_string())); + let result = manager.rotate_key(&services, Some("new-kid".to_owned())); assert!( result.is_err(), @@ -770,12 +756,12 @@ mod tests { build_services_with_config_and_secret(config_store.clone(), secret_store.clone()); let manager = KeyRotationManager::new("cfg-id", "sec-id"); - let result = manager.rotate_key(&services, Some("rollback-kid".to_string())); + let result = manager.rotate_key(&services, Some("rollback-kid".to_owned())); assert!(result.is_err(), "should fail when JWK write fails"); assert_eq!( secret_store.deletes(), - vec![("sec-id".to_string(), "rollback-kid".to_string())], + vec![("sec-id".to_owned(), "rollback-kid".to_owned())], "should roll back private key material after JWK write failure" ); assert!( @@ -792,17 +778,17 @@ mod tests { build_services_with_config_and_secret(config_store.clone(), secret_store.clone()); let manager = KeyRotationManager::new("cfg-id", "sec-id"); - let result = manager.rotate_key(&services, Some("rollback-kid".to_string())); + let result = manager.rotate_key(&services, Some("rollback-kid".to_owned())); assert!(result.is_err(), "should fail when active-kids write fails"); assert_eq!( config_store.deletes(), - vec![("cfg-id".to_string(), "rollback-kid".to_string())], + vec![("cfg-id".to_owned(), "rollback-kid".to_owned())], "should roll back the stored JWK after active-kids write failure" ); assert_eq!( secret_store.deletes(), - vec![("sec-id".to_string(), "rollback-kid".to_string())], + vec![("sec-id".to_owned(), "rollback-kid".to_owned())], "should roll back private key material after active-kids write failure" ); } @@ -810,8 +796,8 @@ mod tests { #[test] fn deactivate_key_rejects_current_kid() { let mut data = HashMap::new(); - data.insert("current-kid".to_string(), "kid-a".to_string()); - data.insert("active-kids".to_string(), "kid-a,kid-b".to_string()); + data.insert("current-kid".to_owned(), "kid-a".to_owned()); + data.insert("active-kids".to_owned(), "kid-a,kid-b".to_owned()); let config_store = SpyConfigStore::new(data); let secret_store = SpySecretStore::new(); @@ -835,8 +821,8 @@ mod tests { #[test] fn delete_key_rejects_current_kid_before_deleting_storage() { let mut data = HashMap::new(); - data.insert("current-kid".to_string(), "kid-a".to_string()); - data.insert("active-kids".to_string(), "kid-a,kid-b".to_string()); + data.insert("current-kid".to_owned(), "kid-a".to_owned()); + data.insert("active-kids".to_owned(), "kid-a,kid-b".to_owned()); let config_store = SpyConfigStore::new(data); let secret_store = SpySecretStore::new(); @@ -860,10 +846,10 @@ mod tests { #[test] fn delete_key_removes_secret_before_jwk() { let mut data = HashMap::new(); - data.insert("active-kids".to_string(), "kid-a, kid-b".to_string()); + data.insert("active-kids".to_owned(), "kid-a, kid-b".to_owned()); data.insert( - "kid-a".to_string(), - r#"{"kty":"OKP","crv":"Ed25519"}"#.to_string(), + "kid-a".to_owned(), + r#"{"kty":"OKP","crv":"Ed25519"}"#.to_owned(), ); let config_store = SpyConfigStore::new(data); diff --git a/crates/trusted-server-core/src/request_signing/signing.rs b/crates/trusted-server-core/src/request_signing/signing.rs index 176f75e11..de7cf68fe 100644 --- a/crates/trusted-server-core/src/request_signing/signing.rs +++ b/crates/trusted-server-core/src/request_signing/signing.rs @@ -3,9 +3,9 @@ //! This module provides Ed25519-based signing and verification of HTTP requests //! using keys stored via platform store primitives. -use base64::{engine::general_purpose, Engine}; -use ed25519_dalek::{Signature, Signer as Ed25519Signer, SigningKey, Verifier, VerifyingKey}; -use error_stack::{Report, ResultExt}; +use base64::{engine::general_purpose, Engine as _}; +use ed25519_dalek::{Signature, Signer as _, SigningKey, Verifier as _, VerifyingKey}; +use error_stack::{Report, ResultExt as _}; use serde::Serialize; use crate::error::TrustedServerError; @@ -37,8 +37,9 @@ pub fn get_current_key_id( fn parse_ed25519_signing_key(key_bytes: &[u8]) -> Result> { let bytes = general_purpose::STANDARD.decode(key_bytes).map_err(|_| { Report::new(TrustedServerError::Configuration { - message: "signing key is not valid base64 — corrupt key material in secret store" - .into(), + message: + "signing key is not valid base64 \u{2014} corrupt key material in secret store" + .into(), }) })?; @@ -98,8 +99,7 @@ impl SigningParams { request_scheme, timestamp: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis() as u64) - .unwrap_or(0), + .map_or(0, |duration| duration.as_millis() as u64), } } @@ -122,7 +122,7 @@ impl SigningParams { }; serde_json::to_string(&payload).map_err(|e| { Report::new(TrustedServerError::Configuration { - message: format!("Failed to serialize signing payload: {}", e), + message: format!("Failed to serialize signing payload: {e}"), }) }) } @@ -144,7 +144,7 @@ impl RequestSigner { .secret_store() .get_bytes(&SIGNING_STORE_NAME, &key_id) .change_context(TrustedServerError::Configuration { - message: format!("failed to get signing key for kid: {}", key_id), + message: format!("failed to get signing key for kid: {key_id}"), })?; let signing_key = parse_ed25519_signing_key(&key_bytes)?; @@ -198,12 +198,12 @@ pub fn verify_signature( .config_store() .get(&JWKS_STORE_NAME, kid) .change_context(TrustedServerError::Configuration { - message: format!("failed to get JWK for kid: {}", kid), + message: format!("failed to get JWK for kid: {kid}"), })?; let jwk: serde_json::Value = serde_json::from_str(&jwk_json).map_err(|e| { Report::new(TrustedServerError::Configuration { - message: format!("Failed to parse JWK: {}", e), + message: format!("Failed to parse JWK: {e}"), }) })?; @@ -217,7 +217,7 @@ pub fn verify_signature( .decode(x_b64) .map_err(|e| { Report::new(TrustedServerError::Configuration { - message: format!("Failed to decode public key: {}", e), + message: format!("Failed to decode public key: {e}"), }) })?; @@ -229,7 +229,7 @@ pub fn verify_signature( let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes).map_err(|e| { Report::new(TrustedServerError::Configuration { - message: format!("Failed to create verifying key: {}", e), + message: format!("Failed to create verifying key: {e}"), }) })?; @@ -238,7 +238,7 @@ pub fn verify_signature( .or_else(|_| general_purpose::STANDARD.decode(signature_b64)) .map_err(|e| { Report::new(TrustedServerError::Configuration { - message: format!("Failed to decode signature: {}", e), + message: format!("Failed to decode signature: {e}"), }) })?; @@ -338,10 +338,10 @@ mod tests { #[test] fn signing_params_build_payload_serializes_all_fields() { let params = SigningParams { - request_id: "req-123".to_string(), - request_host: "example.com".to_string(), - request_scheme: "https".to_string(), - timestamp: 1706900000, + request_id: "req-123".to_owned(), + request_host: "example.com".to_owned(), + request_scheme: "https".to_owned(), + timestamp: 1_706_900_000, }; let payload = params @@ -355,15 +355,15 @@ mod tests { assert_eq!(parsed["host"], "example.com"); assert_eq!(parsed["scheme"], "https"); assert_eq!(parsed["id"], "req-123"); - assert_eq!(parsed["ts"], 1706900000); + assert_eq!(parsed["ts"], 1_706_900_000); } #[test] fn signing_params_new_creates_recent_timestamp() { let params = SigningParams::new( - "req-123".to_string(), - "example.com".to_string(), - "https".to_string(), + "req-123".to_owned(), + "example.com".to_owned(), + "https".to_owned(), ); let now_ms = std::time::SystemTime::now() @@ -387,9 +387,9 @@ mod tests { let signer = RequestSigner::from_services(&services).expect("should create signer from services"); let params = SigningParams::new( - "auction-123".to_string(), - "publisher.com".to_string(), - "https".to_string(), + "auction-123".to_owned(), + "publisher.com".to_owned(), + "https".to_owned(), ); let signature = signer.sign_request(¶ms).expect("should sign request"); @@ -410,16 +410,16 @@ mod tests { RequestSigner::from_services(&services).expect("should create signer from services"); let params1 = SigningParams { - request_id: "req-1".to_string(), - request_host: "host1.com".to_string(), - request_scheme: "https".to_string(), - timestamp: 1706900000, + request_id: "req-1".to_owned(), + request_host: "host1.com".to_owned(), + request_scheme: "https".to_owned(), + timestamp: 1_706_900_000, }; let params2 = SigningParams { - request_id: "req-1".to_string(), - request_host: "host2.com".to_string(), - request_scheme: "https".to_string(), - timestamp: 1706900000, + request_id: "req-1".to_owned(), + request_host: "host2.com".to_owned(), + request_scheme: "https".to_owned(), + timestamp: 1_706_900_000, }; let sig1 = signer.sign_request(¶ms1).expect("should sign params1"); diff --git a/crates/trusted-server-core/src/rsc_flight.rs b/crates/trusted-server-core/src/rsc_flight.rs index 309e95056..a9e3a40ce 100644 --- a/crates/trusted-server-core/src/rsc_flight.rs +++ b/crates/trusted-server-core/src/rsc_flight.rs @@ -65,12 +65,12 @@ impl RscFlightUrlRewriter { .map(|rest| format!("http://{rest}")); Self { - origin_url: origin_url.to_string(), + origin_url: origin_url.to_owned(), origin_http_url, - origin_host: origin_host.to_string(), + origin_host: origin_host.to_owned(), origin_protocol_relative, request_url, - request_host: request_host.to_string(), + request_host: request_host.to_owned(), request_protocol_relative, state: RowState::Id, row_id: Vec::new(), @@ -360,7 +360,7 @@ mod tests { let output_str = String::from_utf8(output).expect("should be valid UTF-8"); let rewritten_t_content = r#"{"url":"https://proxy.example.com/page"}"#; - let expected = format!("1:T{:x},{}", rewritten_t_content.len(), rewritten_t_content,); + let expected = format!("1:T{:x},{}", rewritten_t_content.len(), rewritten_t_content); assert_eq!( output_str, expected, diff --git a/crates/trusted-server-core/src/s3_sigv4.rs b/crates/trusted-server-core/src/s3_sigv4.rs index 52d9ede98..512b56dc1 100644 --- a/crates/trusted-server-core/src/s3_sigv4.rs +++ b/crates/trusted-server-core/src/s3_sigv4.rs @@ -14,7 +14,7 @@ use std::time::SystemTime; use chrono::{DateTime, Utc}; use error_stack::Report; -use hmac::{Hmac, Mac}; +use hmac::{Hmac, Mac as _}; use http::{header, HeaderMap, HeaderValue, Method}; use sha2::{Digest as _, Sha256}; use url::Url; diff --git a/crates/trusted-server-core/src/settings.rs b/crates/trusted-server-core/src/settings.rs index cf3a8f299..ac0732316 100644 --- a/crates/trusted-server-core/src/settings.rs +++ b/crates/trusted-server-core/src/settings.rs @@ -1,5 +1,5 @@ use config::{Config, Environment, File, FileFormat}; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use regex::Regex; use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; use serde_json::Value as JsonValue; @@ -81,15 +81,18 @@ impl Publisher { /// }; /// assert_eq!(publisher.origin_host(), "origin.example.com:8080"); /// ``` - #[allow(dead_code)] + #[allow( + dead_code, + reason = "used by doctests and callers outside build.rs path inclusion" + )] #[must_use] pub fn origin_host(&self) -> String { Url::parse(&self.origin_url) .ok() .and_then(|url| { url.host_str().map(|host| match url.port() { - Some(port) => format!("{}:{}", host, port), - None => host.to_string(), + Some(port) => format!("{host}:{port}"), + None => host.to_owned(), }) }) .unwrap_or_else(|| self.origin_url.clone()) @@ -120,7 +123,13 @@ impl IntegrationSettings { /// # Errors /// /// Returns an error if the configuration cannot be serialized to JSON. - #[cfg_attr(not(test), allow(dead_code))] + #[cfg_attr( + not(test), + allow( + dead_code, + reason = "used by tests and available for runtime configuration assembly" + ) + )] pub fn insert_config( &mut self, integration_id: impl Into, @@ -131,7 +140,7 @@ impl IntegrationSettings { { let json = serde_json::to_value(value).change_context(TrustedServerError::Configuration { - message: "Failed to serialize integration configuration".to_string(), + message: "Failed to serialize integration configuration".to_owned(), })?; self.entries.insert(integration_id.into(), json); Ok(()) @@ -187,9 +196,8 @@ impl IntegrationSettings { where T: IntegrationConfig, { - let raw = match self.entries.get(integration_id) { - Some(value) => value, - None => return Ok(None), + let Some(raw) = self.entries.get(integration_id) else { + return Ok(None); }; if Self::is_explicitly_disabled(raw) { @@ -494,7 +502,10 @@ pub struct Rewrite { impl Rewrite { /// Checks if a URL should be excluded from rewriting based on domain matching - #[allow(dead_code)] + #[allow( + dead_code, + reason = "used by URL rewriting tests and optional integration configuration" + )] #[must_use] pub fn is_excluded(&self, url: &str) -> bool { // Parse URL to extract host @@ -508,7 +519,7 @@ impl Rewrite { for domain in &self.exclude_domains { if let Some(suffix) = domain.strip_prefix("*.") { // Wildcard: *.example.com matches both example.com and sub.example.com - if host == suffix || host.ends_with(&format!(".{}", suffix)) { + if host == suffix || host.ends_with(&format!(".{suffix}")) { return true; } } else if host == domain { @@ -1695,7 +1706,7 @@ impl Settings { pub fn from_toml(toml_str: &str) -> Result> { let mut settings: Self = toml::from_str(toml_str).change_context(TrustedServerError::Configuration { - message: "Failed to deserialize TOML configuration".to_string(), + message: "Failed to deserialize TOML configuration".to_owned(), })?; settings.proxy.normalize(); @@ -1735,13 +1746,13 @@ impl Settings { .add_source(environment) .build() .change_context(TrustedServerError::Configuration { - message: "Failed to build configuration".to_string(), + message: "Failed to build configuration".to_owned(), })?; let mut settings: Self = config .try_deserialize() .change_context(TrustedServerError::Configuration { - message: "Failed to deserialize configuration".to_string(), + message: "Failed to deserialize configuration".to_owned(), })?; settings.integrations.normalize(); @@ -2204,9 +2215,9 @@ where .collect(), JsonValue::Object(map) => { let mut items: Vec<(usize, T)> = Vec::with_capacity(map.len()); - for (k, val) in map.into_iter() { + for (k, val) in map { let idx = k.parse::().map_err(|_| { - serde::de::Error::custom(format!("Invalid index '{}' in map for Vec field", k)) + serde::de::Error::custom(format!("Invalid index '{k}' in map for Vec field")) })?; let parsed: T = serde_json::from_value(val).map_err(serde::de::Error::custom)?; items.push((idx, parsed)); @@ -2255,8 +2266,7 @@ where } } other => Err(serde::de::Error::custom(format!( - "expected array, map of indices, or parseable string, got {}", - other + "expected array, map of indices, or parseable string, got {other}" ))), } } @@ -2487,7 +2497,7 @@ origin_host_header_overide = "www.example.com""#, #[test] fn test_settings_missing_required_fields() { - let re = Regex::new(r"origin_url = .*").expect("regex should compile"); + let re = Regex::new("origin_url = .*").expect("regex should compile"); let toml_str = crate_test_settings_str(); let toml_str = re.replace(&toml_str, ""); @@ -2633,19 +2643,12 @@ origin_host_header_overide = "www.example.com""#, fn test_prebid_bidders_override_with_json_env() { let toml_str = crate_test_settings_str(); let env_key = format!( - "{}{}INTEGRATIONS{}PREBID{}BIDDERS", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}INTEGRATIONS{ENVIRONMENT_VARIABLE_SEPARATOR}PREBID{ENVIRONMENT_VARIABLE_SEPARATOR}BIDDERS" ); // Ensure no external override interferes let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); temp_env::with_var( origin_key, @@ -2663,7 +2666,7 @@ origin_host_header_overide = "www.example.com""#, .expect("Prebid config should exist with env override"); assert_eq!( cfg.bidders, - vec!["smartadserver".to_string(), "rubicon".to_string()] + vec!["smartadserver".to_owned(), "rubicon".to_owned()] ); }); }, @@ -2675,28 +2678,15 @@ origin_host_header_overide = "www.example.com""#, let toml_str = crate_test_settings_str(); let env_key0 = format!( - "{}{}INTEGRATIONS{}PREBID{}BIDDERS{}0", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}INTEGRATIONS{ENVIRONMENT_VARIABLE_SEPARATOR}PREBID{ENVIRONMENT_VARIABLE_SEPARATOR}BIDDERS{ENVIRONMENT_VARIABLE_SEPARATOR}0" ); let env_key1 = format!( - "{}{}INTEGRATIONS{}PREBID{}BIDDERS{}1", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}INTEGRATIONS{ENVIRONMENT_VARIABLE_SEPARATOR}PREBID{ENVIRONMENT_VARIABLE_SEPARATOR}BIDDERS{ENVIRONMENT_VARIABLE_SEPARATOR}1" ); // Also ensure origin_url env is a plain string (avoid any external env interference) let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); temp_env::with_var( origin_key, @@ -2716,7 +2706,7 @@ origin_host_header_overide = "www.example.com""#, .expect("Prebid config should exist with indexed env override"); assert_eq!( cfg.bidders, - vec!["smartadserver".to_string(), "openx".to_string()] + vec!["smartadserver".to_owned(), "openx".to_owned()] ); }); }); @@ -2728,18 +2718,11 @@ origin_host_header_overide = "www.example.com""#, fn test_prebid_bid_param_overrides_override_with_json_env() { let toml_str = crate_test_settings_str(); let env_key = format!( - "{}{}INTEGRATIONS{}PREBID{}BID_PARAM_OVERRIDES", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}INTEGRATIONS{ENVIRONMENT_VARIABLE_SEPARATOR}PREBID{ENVIRONMENT_VARIABLE_SEPARATOR}BID_PARAM_OVERRIDES" ); let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); temp_env::with_var( origin_key, @@ -2778,18 +2761,11 @@ origin_host_header_overide = "www.example.com""#, fn test_prebid_bid_param_override_rules_override_with_json_env() { let toml_str = crate_test_settings_str(); let env_key = format!( - "{}{}INTEGRATIONS{}PREBID{}BID_PARAM_OVERRIDE_RULES", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}INTEGRATIONS{ENVIRONMENT_VARIABLE_SEPARATOR}PREBID{ENVIRONMENT_VARIABLE_SEPARATOR}BID_PARAM_OVERRIDE_RULES" ); let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); temp_env::with_var( origin_key, @@ -3051,54 +3027,27 @@ origin_host_header_overide = "www.example.com""#, let toml_str = crate_test_settings_str(); let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); // Override handler 0 via env vars let path_key_0 = format!( - "{}{}HANDLERS{}0{}PATH", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}HANDLERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}PATH" ); let username_key_0 = format!( - "{}{}HANDLERS{}0{}USERNAME", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}HANDLERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}USERNAME" ); let password_key_0 = format!( - "{}{}HANDLERS{}0{}PASSWORD", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}HANDLERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}PASSWORD" ); // Admin handler at index 1 (required for admin endpoint coverage) let path_key_1 = format!( - "{}{}HANDLERS{}1{}PATH", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}HANDLERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}PATH" ); let username_key_1 = format!( - "{}{}HANDLERS{}1{}USERNAME", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}HANDLERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}USERNAME" ); let password_key_1 = format!( - "{}{}HANDLERS{}1{}PASSWORD", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}HANDLERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}PASSWORD" ); temp_env::with_vars( @@ -3128,90 +3077,37 @@ origin_host_header_overide = "www.example.com""#, let toml_str = crate_test_settings_str(); let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); let partner_0_name_key = format!( - "{}{}EC{}PARTNERS{}0{}NAME", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}NAME" ); let partner_0_source_domain_key = format!( - "{}{}EC{}PARTNERS{}0{}SOURCE_DOMAIN", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}SOURCE_DOMAIN" ); let partner_0_openrtb_atype_key = format!( - "{}{}EC{}PARTNERS{}0{}OPENRTB_ATYPE", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}OPENRTB_ATYPE" ); let partner_0_bidstream_enabled_key = format!( - "{}{}EC{}PARTNERS{}0{}BIDSTREAM_ENABLED", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}BIDSTREAM_ENABLED" ); let partner_0_api_token_key = format!( - "{}{}EC{}PARTNERS{}0{}API_TOKEN", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}API_TOKEN" ); let partner_1_name_key = format!( - "{}{}EC{}PARTNERS{}1{}NAME", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}NAME" ); let partner_1_source_domain_key = format!( - "{}{}EC{}PARTNERS{}1{}SOURCE_DOMAIN", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}SOURCE_DOMAIN" ); let partner_1_openrtb_atype_key = format!( - "{}{}EC{}PARTNERS{}1{}OPENRTB_ATYPE", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}OPENRTB_ATYPE" ); let partner_1_bidstream_enabled_key = format!( - "{}{}EC{}PARTNERS{}1{}BIDSTREAM_ENABLED", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}BIDSTREAM_ENABLED" ); let partner_1_api_token_key = format!( - "{}{}EC{}PARTNERS{}1{}API_TOKEN", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}EC{ENVIRONMENT_VARIABLE_SEPARATOR}PARTNERS{ENVIRONMENT_VARIABLE_SEPARATOR}1{ENVIRONMENT_VARIABLE_SEPARATOR}API_TOKEN" ); temp_env::with_vars( @@ -3258,17 +3154,10 @@ origin_host_header_overide = "www.example.com""#, let toml_str = crate_test_settings_str(); let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); let path_key = format!( - "{}{}HANDLERS{}0{}PATH", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}HANDLERS{ENVIRONMENT_VARIABLE_SEPARATOR}0{ENVIRONMENT_VARIABLE_SEPARATOR}PATH" ); temp_env::with_var( @@ -3287,8 +3176,7 @@ origin_host_header_overide = "www.example.com""#, fn test_response_headers_override_with_json_env() { let toml_str = crate_test_settings_str(); let env_key = format!( - "{}{}RESPONSE_HEADERS", - ENVIRONMENT_VARIABLE_PREFIX, ENVIRONMENT_VARIABLE_SEPARATOR, + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}RESPONSE_HEADERS", ); temp_env::with_var( @@ -3300,11 +3188,11 @@ origin_host_header_overide = "www.example.com""#, assert_eq!(settings.response_headers.len(), 2); assert_eq!( settings.response_headers.get("X-Robots-Tag"), - Some(&"noindex".to_string()) + Some(&"noindex".to_owned()) ); assert_eq!( settings.response_headers.get("X-Custom-Header"), - Some(&"custom value".to_string()) + Some(&"custom value".to_owned()) ); }, ); @@ -3322,10 +3210,7 @@ origin_host_header_overide = "www.example.com""#, fn test_set_env() { temp_env::with_var( format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ), Some("https://change-publisher.com"), || { @@ -3346,10 +3231,7 @@ origin_host_header_overide = "www.example.com""#, temp_env::with_var( format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ), Some("https://change-publisher.com"), || { @@ -3367,10 +3249,7 @@ origin_host_header_overide = "www.example.com""#, #[test] fn test_origin_host_header_override_env() { let env_key = format!( - "{}{}PUBLISHER{}ORIGIN_HOST_HEADER_OVERRIDE", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_HOST_HEADER_OVERRIDE" ); temp_env::with_var(env_key, Some("www.example.com"), || { @@ -3388,10 +3267,7 @@ origin_host_header_overide = "www.example.com""#, #[test] fn test_origin_host_header_override_env_typo_fails_closed() { let env_key = format!( - "{}{}PUBLISHER{}ORIGIN_HOST_HEADER_OVERIDE", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_HOST_HEADER_OVERIDE" ); temp_env::with_var(env_key, Some("www.example.com"), || { @@ -3408,61 +3284,61 @@ origin_host_header_overide = "www.example.com""#, fn test_publisher_origin_host() { // Test with full URL including port let publisher = Publisher { - domain: "example.com".to_string(), - cookie_domain: ".example.com".to_string(), - origin_url: "https://origin.example.com:8080".to_string(), + domain: "example.com".to_owned(), + cookie_domain: ".example.com".to_owned(), + origin_url: "https://origin.example.com:8080".to_owned(), origin_host_header_override: None, - proxy_secret: Redacted::new("test-secret".to_string()), + proxy_secret: Redacted::new("test-secret".to_owned()), }; assert_eq!(publisher.origin_host(), "origin.example.com:8080"); // Test with URL without port (default HTTPS port) let publisher = Publisher { - domain: "example.com".to_string(), - cookie_domain: ".example.com".to_string(), - origin_url: "https://origin.example.com".to_string(), + domain: "example.com".to_owned(), + cookie_domain: ".example.com".to_owned(), + origin_url: "https://origin.example.com".to_owned(), origin_host_header_override: None, - proxy_secret: Redacted::new("test-secret".to_string()), + proxy_secret: Redacted::new("test-secret".to_owned()), }; assert_eq!(publisher.origin_host(), "origin.example.com"); // Test with HTTP URL with explicit port let publisher = Publisher { - domain: "example.com".to_string(), - cookie_domain: ".example.com".to_string(), - origin_url: "http://localhost:9090".to_string(), + domain: "example.com".to_owned(), + cookie_domain: ".example.com".to_owned(), + origin_url: "http://localhost:9090".to_owned(), origin_host_header_override: None, - proxy_secret: Redacted::new("test-secret".to_string()), + proxy_secret: Redacted::new("test-secret".to_owned()), }; assert_eq!(publisher.origin_host(), "localhost:9090"); // Test with URL without protocol (fallback to original) let publisher = Publisher { - domain: "example.com".to_string(), - cookie_domain: ".example.com".to_string(), - origin_url: "localhost:9090".to_string(), + domain: "example.com".to_owned(), + cookie_domain: ".example.com".to_owned(), + origin_url: "localhost:9090".to_owned(), origin_host_header_override: None, - proxy_secret: Redacted::new("test-secret".to_string()), + proxy_secret: Redacted::new("test-secret".to_owned()), }; assert_eq!(publisher.origin_host(), "localhost:9090"); // Test with IPv4 address let publisher = Publisher { - domain: "example.com".to_string(), - cookie_domain: ".example.com".to_string(), - origin_url: "http://192.168.1.1:8080".to_string(), + domain: "example.com".to_owned(), + cookie_domain: ".example.com".to_owned(), + origin_url: "http://192.168.1.1:8080".to_owned(), origin_host_header_override: None, - proxy_secret: Redacted::new("test-secret".to_string()), + proxy_secret: Redacted::new("test-secret".to_owned()), }; assert_eq!(publisher.origin_host(), "192.168.1.1:8080"); // Test with IPv6 address let publisher = Publisher { - domain: "example.com".to_string(), - cookie_domain: ".example.com".to_string(), - origin_url: "http://[::1]:8080".to_string(), + domain: "example.com".to_owned(), + cookie_domain: ".example.com".to_owned(), + origin_url: "http://[::1]:8080".to_owned(), origin_host_header_override: None, - proxy_secret: Redacted::new("test-secret".to_string()), + proxy_secret: Redacted::new("test-secret".to_owned()), }; assert_eq!(publisher.origin_host(), "[::1]:8080"); } @@ -3500,24 +3376,17 @@ origin_host_header_overide = "www.example.com""#, let toml_str = crate_test_settings_str(); let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); let integration_prefix = format!( - "{}{}INTEGRATIONS{}TESTLIGHT{}", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}INTEGRATIONS{ENVIRONMENT_VARIABLE_SEPARATOR}TESTLIGHT{ENVIRONMENT_VARIABLE_SEPARATOR}" ); - let endpoint_key = format!("{}ENDPOINT", integration_prefix); - let timeout_key = format!("{}TIMEOUT_MS", integration_prefix); - let rewrite_key = format!("{}REWRITE_SCRIPTS", integration_prefix); - let enabled_key = format!("{}ENABLED", integration_prefix); + let endpoint_key = format!("{integration_prefix}ENDPOINT"); + let timeout_key = format!("{integration_prefix}TIMEOUT_MS"); + let rewrite_key = format!("{integration_prefix}REWRITE_SCRIPTS"); + let enabled_key = format!("{integration_prefix}ENABLED"); temp_env::with_var( origin_key, @@ -3639,9 +3508,8 @@ origin_host_header_overide = "www.example.com""#, ) .expect("should insert GPT config"); - let err = match IntegrationRegistry::new(&settings) { - Ok(_) => panic!("enabled invalid integration should fail registry startup"), - Err(err) => err, + let Err(err) = IntegrationRegistry::new(&settings) else { + panic!("enabled invalid integration should fail registry startup"); }; assert!( err.to_string().contains("Integration 'gpt'"), @@ -3680,9 +3548,8 @@ origin_host_header_overide = "www.example.com""#, ) .expect("should insert adserver mock config"); - let err = match build_orchestrator(&settings) { - Ok(_) => panic!("enabled invalid provider config should fail startup"), - Err(err) => err, + let Err(err) = build_orchestrator(&settings) else { + panic!("enabled invalid provider config should fail startup"); }; assert!( err.to_string().contains("Integration 'adserver_mock'"), @@ -3704,9 +3571,8 @@ origin_host_header_overide = "www.example.com""#, ) .expect("should insert prebid config"); - let err = match build_orchestrator(&settings) { - Ok(_) => panic!("empty prebid server_url should fail startup"), - Err(err) => err, + let Err(err) = build_orchestrator(&settings) else { + panic!("empty prebid server_url should fail startup"); }; assert!( err.to_string() @@ -3725,14 +3591,10 @@ origin_host_header_overide = "www.example.com""#, let toml_str = crate_test_settings_str(); let integration_prefix = format!( - "{}{}INTEGRATIONS{}TESTLIGHT{}", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}INTEGRATIONS{ENVIRONMENT_VARIABLE_SEPARATOR}TESTLIGHT{ENVIRONMENT_VARIABLE_SEPARATOR}", ); - let enabled_key = format!("{}ENABLED", integration_prefix); - let endpoint_key = format!("{}ENDPOINT", integration_prefix); + let enabled_key = format!("{integration_prefix}ENABLED"); + let endpoint_key = format!("{integration_prefix}ENDPOINT"); temp_env::with_var(enabled_key, Some("true"), || { temp_env::with_var( @@ -3779,10 +3641,7 @@ origin_host_header_overide = "www.example.com""#, temp_env::with_var( format!( - "{}{}PUBLISHER{}DOMAIN", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR, + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}DOMAIN", ), Some("env-override.com"), || { @@ -3798,7 +3657,7 @@ origin_host_header_overide = "www.example.com""#, #[test] fn test_rewrite_is_excluded() { let rewrite = Rewrite { - exclude_domains: vec!["cdn.example.com".to_string(), "*.example2.com".to_string()], + exclude_domains: vec!["cdn.example.com".to_owned(), "*.example2.com".to_owned()], }; // Exact domain match @@ -3842,19 +3701,19 @@ origin_host_header_overide = "www.example.com""#, let settings = Settings::from_toml(&toml_str).expect("should parse valid TOML"); assert_eq!( settings.auction.allowed_context_keys, - HashSet::from(["permutive_segments".to_string(), "lockr_ids".to_string()]) + HashSet::from(["permutive_segments".to_owned(), "lockr_ids".to_owned()]) ); } #[test] fn test_auction_empty_allowed_context_keys_blocks_all() { let toml_str = crate_test_settings_str() - + r#" + + " [auction] enabled = true providers = [] allowed_context_keys = [] - "#; + "; let settings = Settings::from_toml(&toml_str).expect("should parse valid TOML"); assert!( settings.auction.allowed_context_keys.is_empty(), @@ -3868,16 +3727,13 @@ origin_host_header_overide = "www.example.com""#, fn proxy_normalize_trims_and_lowercases() { let mut proxy = Proxy { certificate_check: true, - allowed_domains: vec![ - " AD.EXAMPLE.COM ".to_string(), - "*.Example.Org".to_string(), - ], + allowed_domains: vec![" AD.EXAMPLE.COM ".to_owned(), "*.Example.Org".to_owned()], asset_routes: vec![], }; proxy.normalize(); assert_eq!( proxy.allowed_domains, - vec!["ad.example.com".to_string(), "*.example.org".to_string()], + vec!["ad.example.com".to_owned(), "*.example.org".to_owned()], "should trim and lowercase each entry" ); } @@ -3887,17 +3743,17 @@ origin_host_header_overide = "www.example.com""#, let mut proxy = Proxy { certificate_check: true, allowed_domains: vec![ - "example.com".to_string(), - " ".to_string(), - "".to_string(), - "cdn.example.com".to_string(), + "example.com".to_owned(), + " ".to_owned(), + String::new(), + "cdn.example.com".to_owned(), ], asset_routes: vec![], }; proxy.normalize(); assert_eq!( proxy.allowed_domains, - vec!["example.com".to_string(), "cdn.example.com".to_string()], + vec!["example.com".to_owned(), "cdn.example.com".to_owned()], "should drop blank and whitespace-only entries" ); } @@ -3906,13 +3762,13 @@ origin_host_header_overide = "www.example.com""#, fn proxy_normalize_removes_bare_wildcard() { let mut proxy = Proxy { certificate_check: true, - allowed_domains: vec!["*".to_string(), "tracker.com".to_string()], + allowed_domains: vec!["*".to_owned(), "tracker.com".to_owned()], asset_routes: vec![], }; proxy.normalize(); assert_eq!( proxy.allowed_domains, - vec!["tracker.com".to_string()], + vec!["tracker.com".to_owned()], "should remove bare \"*\" (invalid pattern that blocks all traffic)" ); } @@ -3921,7 +3777,7 @@ origin_host_header_overide = "www.example.com""#, fn proxy_normalize_bare_wildcard_alone_yields_open_mode() { let mut proxy = Proxy { certificate_check: true, - allowed_domains: vec!["*".to_string()], + allowed_domains: vec!["*".to_owned()], asset_routes: vec![], }; proxy.normalize(); @@ -3935,7 +3791,7 @@ origin_host_header_overide = "www.example.com""#, fn proxy_normalize_all_blank_yields_empty_list() { let mut proxy = Proxy { certificate_check: true, - allowed_domains: vec![" ".to_string(), "\t".to_string()], + allowed_domains: vec![" ".to_owned(), "\t".to_owned()], asset_routes: vec![], }; proxy.normalize(); @@ -4475,10 +4331,7 @@ origin_host_header_overide = "www.example.com""#, let settings = Settings::from_toml(&toml_str).expect("should parse TOML"); assert_eq!( settings.proxy.allowed_domains, - vec![ - "ad.example.com".to_string(), - "*.cdn.example.com".to_string() - ], + vec!["ad.example.com".to_owned(), "*.cdn.example.com".to_owned()], "from_toml should normalize allowed_domains" ); } @@ -4617,10 +4470,7 @@ origin_host_header_overide = "www.example.com""#, allowed_domains = [" AD.EXAMPLE.COM ", " ", "*.CDN.Example.Com"] "#; let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); temp_env::with_var( origin_key, @@ -4630,10 +4480,7 @@ origin_host_header_overide = "www.example.com""#, Settings::from_toml_and_env(&toml_str).expect("should parse TOML with env"); assert_eq!( settings.proxy.allowed_domains, - vec![ - "ad.example.com".to_string(), - "*.cdn.example.com".to_string() - ], + vec!["ad.example.com".to_owned(), "*.cdn.example.com".to_owned()], "from_toml_and_env should normalize allowed_domains" ); }, @@ -4650,7 +4497,7 @@ origin_host_header_overide = "www.example.com""#, "evil.com;path=/", ] { let mut settings = create_test_settings(); - settings.publisher.cookie_domain = bad_domain.to_string(); + settings.publisher.cookie_domain = bad_domain.to_owned(); assert!( settings.validate().is_err(), "should reject cookie_domain containing metacharacters: {bad_domain:?}" @@ -4661,7 +4508,7 @@ origin_host_header_overide = "www.example.com""#, #[test] fn test_publisher_accepts_valid_cookie_domain() { let mut settings = create_test_settings(); - settings.publisher.cookie_domain = ".example.com".to_string(); + settings.publisher.cookie_domain = ".example.com".to_owned(); assert!( settings.validate().is_ok(), "should accept a valid cookie_domain" @@ -4690,7 +4537,7 @@ origin_host_header_overide = "www.example.com""#, config_store_id = "test-config-store-id" secret_store_id = "test-secret-store-id" "# - .to_string() + .to_owned() } #[test] @@ -4746,10 +4593,7 @@ origin_host_header_overide = "www.example.com""#, #[test] fn from_toml_and_env_rejects_config_without_admin_handler() { let origin_key = format!( - "{}{}PUBLISHER{}ORIGIN_URL", - ENVIRONMENT_VARIABLE_PREFIX, - ENVIRONMENT_VARIABLE_SEPARATOR, - ENVIRONMENT_VARIABLE_SEPARATOR + "{ENVIRONMENT_VARIABLE_PREFIX}{ENVIRONMENT_VARIABLE_SEPARATOR}PUBLISHER{ENVIRONMENT_VARIABLE_SEPARATOR}ORIGIN_URL" ); temp_env::with_var( origin_key, @@ -4820,7 +4664,7 @@ origin_host_header_overide = "www.example.com""#, assert!( router_source.contains(endpoint), "ADMIN_ENDPOINTS lists \"{endpoint}\" but it was not found in \ - crates/trusted-server-adapter-fastly/src/main.rs — remove it from ADMIN_ENDPOINTS or \ + crates/trusted-server-adapter-fastly/src/main.rs \u{2014} remove it from ADMIN_ENDPOINTS or \ add the route back to the router" ); } @@ -4849,7 +4693,7 @@ origin_host_header_overide = "www.example.com""#, assert!( Settings::ADMIN_ENDPOINTS.contains(route), "Router has admin route \"{route}\" that is missing from \ - Settings::ADMIN_ENDPOINTS — add it to ensure auth coverage" + Settings::ADMIN_ENDPOINTS \u{2014} add it to ensure auth coverage" ); } } diff --git a/crates/trusted-server-core/src/settings_data.rs b/crates/trusted-server-core/src/settings_data.rs index 5207a7e66..d4d4573d0 100644 --- a/crates/trusted-server-core/src/settings_data.rs +++ b/crates/trusted-server-core/src/settings_data.rs @@ -1,6 +1,6 @@ use core::str; -use error_stack::{Report, ResultExt}; -use validator::Validate; +use error_stack::{Report, ResultExt as _}; +use validator::Validate as _; use crate::error::TrustedServerError; use crate::settings::Settings; @@ -22,7 +22,7 @@ const SETTINGS_DATA: &[u8] = include_bytes!("../../../target/trusted-server-out. pub fn get_settings() -> Result> { let toml_bytes = SETTINGS_DATA; let toml_str = str::from_utf8(toml_bytes).change_context(TrustedServerError::InvalidUtf8 { - message: "embedded trusted-server.toml file".to_string(), + message: "embedded trusted-server.toml file".to_owned(), })?; let settings = Settings::from_toml(toml_str)?; @@ -31,12 +31,12 @@ pub fn get_settings() -> Result> { settings .validate() .change_context(TrustedServerError::Configuration { - message: "Failed to validate configuration".to_string(), + message: "Failed to validate configuration".to_owned(), })?; if !settings.proxy.certificate_check { log::warn!( - "INSECURE: proxy.certificate_check is disabled — TLS certificates will NOT be verified" + "INSECURE: proxy.certificate_check is disabled \u{2014} TLS certificates will NOT be verified" ); } diff --git a/crates/trusted-server-core/src/storage/secret_store.rs b/crates/trusted-server-core/src/storage/secret_store.rs index f2dd7b91e..2a474dcd5 100644 --- a/crates/trusted-server-core/src/storage/secret_store.rs +++ b/crates/trusted-server-core/src/storage/secret_store.rs @@ -7,7 +7,7 @@ use core::fmt::Display; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use fastly::SecretStore; use crate::error::TrustedServerError; @@ -120,7 +120,7 @@ impl FastlySecretStore { pub fn get_string(&self, key: &str) -> Result> { let bytes = self.get(key)?; String::from_utf8(bytes).change_context(TrustedServerError::Configuration { - message: "failed to decode secret as UTF-8".to_string(), + message: "failed to decode secret as UTF-8".to_owned(), }) } } diff --git a/crates/trusted-server-core/src/streaming_processor.rs b/crates/trusted-server-core/src/streaming_processor.rs index 54b2524e5..e37ef5498 100644 --- a/crates/trusted-server-core/src/streaming_processor.rs +++ b/crates/trusted-server-core/src/streaming_processor.rs @@ -13,7 +13,7 @@ use std::rc::Rc; use brotli::enc::writer::CompressorWriter; use brotli::enc::BrotliEncoderParams; use brotli::Decompressor; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ResultExt as _}; use flate2::read::{GzDecoder, ZlibDecoder}; use flate2::write::{GzEncoder, ZlibEncoder}; @@ -135,7 +135,7 @@ impl StreamingPipeline

{ let mut encoder = GzEncoder::new(output, flate2::Compression::default()); self.process_chunks(decoder, &mut encoder)?; encoder.finish().change_context(TrustedServerError::Proxy { - message: "Failed to finalize gzip encoder".to_string(), + message: "Failed to finalize gzip encoder".to_owned(), })?; Ok(()) } @@ -147,7 +147,7 @@ impl StreamingPipeline

{ let mut encoder = ZlibEncoder::new(output, flate2::Compression::default()); self.process_chunks(decoder, &mut encoder)?; encoder.finish().change_context(TrustedServerError::Proxy { - message: "Failed to finalize deflate encoder".to_string(), + message: "Failed to finalize deflate encoder".to_owned(), })?; Ok(()) } @@ -175,7 +175,7 @@ impl StreamingPipeline

{ self.process_chunks(Decompressor::new(input, 4096), output) } _ => Err(Report::new(TrustedServerError::Proxy { - message: "Unsupported compression transformation".to_string(), + message: "Unsupported compression transformation".to_owned(), })), } } @@ -199,20 +199,20 @@ impl StreamingPipeline

{ mut reader: R, mut writer: W, ) -> Result<(), Report> { - let mut buffer = vec![0u8; self.config.chunk_size]; + let mut buffer = vec![0_u8; self.config.chunk_size]; loop { match reader.read(&mut buffer) { Ok(0) => { let final_chunk = self.processor.process_chunk(&[], true).change_context( TrustedServerError::Proxy { - message: "Failed to process final chunk".to_string(), + message: "Failed to process final chunk".to_owned(), }, )?; if !final_chunk.is_empty() { writer.write_all(&final_chunk).change_context( TrustedServerError::Proxy { - message: "Failed to write final chunk".to_string(), + message: "Failed to write final chunk".to_owned(), }, )?; } @@ -223,13 +223,13 @@ impl StreamingPipeline

{ .processor .process_chunk(&buffer[..n], false) .change_context(TrustedServerError::Proxy { - message: "Failed to process chunk".to_string(), + message: "Failed to process chunk".to_owned(), })?; if !processed.is_empty() { writer .write_all(&processed) .change_context(TrustedServerError::Proxy { - message: "Failed to write processed chunk".to_string(), + message: "Failed to write processed chunk".to_owned(), })?; } } @@ -242,7 +242,7 @@ impl StreamingPipeline

{ } writer.flush().change_context(TrustedServerError::Proxy { - message: "Failed to flush output".to_string(), + message: "Failed to flush output".to_owned(), })?; Ok(()) @@ -291,22 +291,20 @@ impl HtmlRewriterAdapter { impl StreamProcessor for HtmlRewriterAdapter { fn process_chunk(&mut self, chunk: &[u8], is_last: bool) -> Result, io::Error> { - match &mut self.rewriter { - Some(rewriter) => { - if !chunk.is_empty() { - rewriter.write(chunk).map_err(|e| { - log::error!("Failed to process HTML chunk: {e}"); - io::Error::other(format!("HTML processing failed: {e}")) - })?; - } + match (&mut self.rewriter, chunk.is_empty()) { + (Some(rewriter), false) => { + rewriter.write(chunk).map_err(|e| { + log::error!("Failed to process HTML chunk: {e}"); + io::Error::other(format!("HTML processing failed: {e}")) + })?; } - None if !chunk.is_empty() => { + (None, false) => { log::warn!( "HtmlRewriterAdapter: {} bytes received after finalization, data will be lost", chunk.len() ); } - None => {} + _ => {} } if is_last { @@ -359,7 +357,7 @@ mod tests { element_content_handlers: vec![lol_html::text!("script", move |text| { fragments_clone .borrow_mut() - .push((text.as_str().to_string(), text.last_in_text_node())); + .push((text.as_str().to_owned(), text.last_in_text_node())); Ok(()) })], ..lol_html::Settings::default() @@ -397,8 +395,8 @@ mod tests { #[test] fn test_uncompressed_pipeline() { let replacer = StreamingReplacer::new(vec![Replacement { - find: "hello".to_string(), - replace_with: "hi".to_string(), + find: "hello".to_owned(), + replace_with: "hi".to_owned(), }]); let config = PipelineConfig::default(); @@ -503,7 +501,7 @@ mod tests { // Create a large HTML document let mut large_html = String::from(""); for i in 0..1000 { - large_html.push_str(&format!("

Paragraph {}

", i)); + large_html.push_str(&format!("

Paragraph {i}

")); } large_html.push_str(""); @@ -581,8 +579,8 @@ mod tests { } let replacer = StreamingReplacer::new(vec![Replacement { - find: "hello".to_string(), - replace_with: "hi".to_string(), + find: "hello".to_owned(), + replace_with: "hi".to_owned(), }]); let config = PipelineConfig { @@ -595,14 +593,14 @@ mod tests { let mut output = Vec::new(); pipeline - .process(&compressed_input[..], &mut output) + .process(&*compressed_input, &mut output) .expect("should process deflate-to-deflate"); // Decompress output and verify correctness let mut decompressed = Vec::new(); - ZlibDecoder::new(&output[..]) + ZlibDecoder::new(&*output) .read_to_end(&mut decompressed) - .expect("should decompress output — implies encoder was finalized correctly"); + .expect("should decompress output \u{2014} implies encoder was finalized correctly"); assert_eq!( String::from_utf8(decompressed).expect("should be valid UTF-8"), @@ -629,8 +627,8 @@ mod tests { } let replacer = StreamingReplacer::new(vec![Replacement { - find: "hello".to_string(), - replace_with: "hi".to_string(), + find: "hello".to_owned(), + replace_with: "hi".to_owned(), }]); let config = PipelineConfig { @@ -644,14 +642,14 @@ mod tests { // Act pipeline - .process(&compressed_input[..], &mut output) + .process(&*compressed_input, &mut output) .expect("should process gzip-to-gzip"); // Assert let mut decompressed = Vec::new(); - GzDecoder::new(&output[..]) + GzDecoder::new(&*output) .read_to_end(&mut decompressed) - .expect("should decompress output — implies encoder was finalized correctly"); + .expect("should decompress output \u{2014} implies encoder was finalized correctly"); assert_eq!( String::from_utf8(decompressed).expect("should be valid UTF-8"), @@ -677,8 +675,8 @@ mod tests { } let replacer = StreamingReplacer::new(vec![Replacement { - find: "hello".to_string(), - replace_with: "hi".to_string(), + find: "hello".to_owned(), + replace_with: "hi".to_owned(), }]); let config = PipelineConfig { @@ -692,7 +690,7 @@ mod tests { // Act pipeline - .process(&compressed_input[..], &mut output) + .process(&*compressed_input, &mut output) .expect("should process gzip-to-none"); // Assert @@ -721,8 +719,8 @@ mod tests { } let replacer = StreamingReplacer::new(vec![Replacement { - find: "hello".to_string(), - replace_with: "hi".to_string(), + find: "hello".to_owned(), + replace_with: "hi".to_owned(), }]); let config = PipelineConfig { @@ -735,14 +733,14 @@ mod tests { let mut output = Vec::new(); pipeline - .process(&compressed_input[..], &mut output) + .process(&*compressed_input, &mut output) .expect("should process brotli-to-brotli"); // Decompress output and verify correctness let mut decompressed = Vec::new(); - Decompressor::new(&output[..], 4096) + Decompressor::new(&*output, 4096) .read_to_end(&mut decompressed) - .expect("should decompress output — implies encoder was finalized correctly"); + .expect("should decompress output \u{2014} implies encoder was finalized correctly"); assert_eq!( String::from_utf8(decompressed).expect("should be valid UTF-8"), @@ -874,11 +872,11 @@ mod tests { let mut output = Vec::new(); pipeline - .process(&compressed_input[..], &mut output) + .process(&*compressed_input, &mut output) .expect("pipeline should process gzip HTML"); let mut decompressed = Vec::new(); - GzDecoder::new(&output[..]) + GzDecoder::new(&*output) .read_to_end(&mut decompressed) .expect("should decompress output"); diff --git a/crates/trusted-server-core/src/streaming_replacer.rs b/crates/trusted-server-core/src/streaming_replacer.rs index 9c438858c..841da5d37 100644 --- a/crates/trusted-server-core/src/streaming_replacer.rs +++ b/crates/trusted-server-core/src/streaming_replacer.rs @@ -52,8 +52,8 @@ impl StreamingReplacer { #[must_use] pub fn new_single(find: &str, replace_with: &str) -> Self { Self::new(vec![Replacement { - find: find.to_string(), - replace_with: replace_with.to_string(), + find: find.to_owned(), + replace_with: replace_with.to_owned(), }]) } @@ -117,7 +117,7 @@ impl StreamingReplacer { // Check if this is a valid UTF-8 boundary if let Ok(s) = std::str::from_utf8(&combined[..adjusted_end_bytes]) { // Valid UTF-8 up to this point, process it - let mut processed = s.to_string(); + let mut processed = s.to_owned(); // Apply all replacements for replacement in &self.replacements { @@ -125,10 +125,10 @@ impl StreamingReplacer { } // Save the overlap for the next chunk - if !is_last_chunk { - self.overlap_buffer = combined[adjusted_end_bytes..].to_vec(); - } else { + if is_last_chunk { self.overlap_buffer.clear(); + } else { + self.overlap_buffer = combined[adjusted_end_bytes..].to_vec(); } return processed.into_bytes(); @@ -159,12 +159,12 @@ pub fn create_url_replacer( request_host: &str, request_scheme: &str, ) -> StreamingReplacer { - let request_url = format!("{}://{}", request_scheme, request_host); + let request_url = format!("{request_scheme}://{request_host}"); let mut replacements = vec![ // Replace full URLs first (more specific) Replacement { - find: origin_url.to_string(), + find: origin_url.to_owned(), replace_with: request_url.clone(), }, ]; @@ -180,14 +180,14 @@ pub fn create_url_replacer( // Replace protocol-relative URLs replacements.push(Replacement { - find: format!("//{}", origin_host), - replace_with: format!("//{}", request_host), + find: format!("//{origin_host}"), + replace_with: format!("//{request_host}"), }); // Replace host in various contexts replacements.push(Replacement { - find: origin_host.to_string(), - replace_with: request_host.to_string(), + find: origin_host.to_owned(), + replace_with: request_host.to_owned(), }); StreamingReplacer::new(replacements) @@ -219,7 +219,7 @@ mod tests { split_points: &[usize], ) -> String { let mut result = Vec::new(); - let mut start = 0usize; + let mut start = 0_usize; for (index, end) in split_points.iter().copied().enumerate() { let is_last_chunk = index == split_points.len() - 1; @@ -266,12 +266,12 @@ mod tests { fn test_streaming_replacer_multiple_patterns() { let replacements = vec![ Replacement { - find: "https://origin.example.com".to_string(), - replace_with: "https://test.example.com".to_string(), + find: "https://origin.example.com".to_owned(), + replace_with: "https://test.example.com".to_owned(), }, Replacement { - find: "//origin.example.com".to_string(), - replace_with: "//test.example.com".to_string(), + find: "//origin.example.com".to_owned(), + replace_with: "//test.example.com".to_owned(), }, ]; @@ -420,20 +420,20 @@ mod tests { "test.com", "https", ), - content: "https://origin.com/test 思怙ᕏ测试 https://origin.com/more", + content: "https://origin.com/test \u{601d}\u{6019}\u{154f}\u{6d4b}\u{8bd5} https://origin.com/more", chunk_size: 20, expected_fragments: &[ "https://test.com/test", "https://test.com/more", - "思怙ᕏ测试", + "\u{601d}\u{6019}\u{154f}\u{6d4b}\u{8bd5}", ], }, Utf8BoundaryCase { name: "small chunks preserve utf8 without replacements", replacer: create_url_replacer("test.com", "https://test.com", "new.com", "https"), - content: "Some text 思怙ᕏ测试 more text with 🎉 emoji", + content: "Some text \u{601d}\u{6019}\u{154f}\u{6d4b}\u{8bd5} more text with \u{1f389} emoji", chunk_size: 8, - expected_fragments: &["Some text", "思怙ᕏ测试", "🎉 emoji"], + expected_fragments: &["Some text", "\u{601d}\u{6019}\u{154f}\u{6d4b}\u{8bd5}", "\u{1f389} emoji"], }, ]; @@ -458,7 +458,7 @@ mod tests { #[test] fn test_process_chunk_boundary_in_multibyte_char() { - let content = "https://example.com/før/bår/test".as_bytes(); + let content = "https://example.com/f\u{f8}r/b\u{e5}r/test".as_bytes(); let result = process_with_explicit_splits( create_url_replacer("example.com", "https://example.com", "new.com", "https"), @@ -466,12 +466,12 @@ mod tests { &[22, content.len()], ); - assert!(result.contains("https://new.com/før/bår/test")); + assert!(result.contains("https://new.com/f\u{f8}r/b\u{e5}r/test")); } #[test] fn test_process_chunk_boundary_in_emoji() { - let content = "🎉🎊🎋 https://emoji.com/more".as_bytes(); + let content = "\u{1f389}\u{1f38a}\u{1f38b} https://emoji.com/more".as_bytes(); let result = process_with_explicit_splits( create_url_replacer("emoji.com", "https://emoji.com", "test.com", "https"), @@ -479,7 +479,7 @@ mod tests { &[2, content.len()], ); - assert!(result.contains("🎉🎊🎋")); + assert!(result.contains("\u{1f389}\u{1f38a}\u{1f38b}")); assert!(result.contains("https://test.com/more")); } @@ -512,12 +512,12 @@ mod tests { // Test replacing arbitrary strings let replacements = vec![ Replacement { - find: "color".to_string(), - replace_with: "colour".to_string(), + find: "color".to_owned(), + replace_with: "colour".to_owned(), }, Replacement { - find: "gray".to_string(), - replace_with: "grey".to_string(), + find: "gray".to_owned(), + replace_with: "grey".to_owned(), }, ]; @@ -535,12 +535,12 @@ mod tests { // Test that longer patterns are replaced first (order matters) let replacements = vec![ Replacement { - find: "hello world".to_string(), - replace_with: "greetings universe".to_string(), + find: "hello world".to_owned(), + replace_with: "greetings universe".to_owned(), }, Replacement { - find: "hello".to_string(), - replace_with: "hi".to_string(), + find: "hello".to_owned(), + replace_with: "hi".to_owned(), }, ]; @@ -559,12 +559,12 @@ mod tests { // Test handling of overlapping patterns let replacements = vec![ Replacement { - find: "abc".to_string(), - replace_with: "xyz".to_string(), + find: "abc".to_owned(), + replace_with: "xyz".to_owned(), }, Replacement { - find: "bcd".to_string(), - replace_with: "123".to_string(), + find: "bcd".to_owned(), + replace_with: "123".to_owned(), }, ]; @@ -607,12 +607,12 @@ mod tests { // Test replacing patterns with special regex characters let replacements = vec![ Replacement { - find: "cost: $10.99".to_string(), - replace_with: "price: €9.99".to_string(), + find: "cost: $10.99".to_owned(), + replace_with: "price: \u{20ac}9.99".to_owned(), }, Replacement { - find: "[TAG]".to_string(), - replace_with: "