From d24a6b527cb0b40949977418feeae6fa783a2dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 9 Jul 2026 21:46:30 +0200 Subject: [PATCH 01/13] ci: add optional praxis main testing and known-issue label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add CI support for testing against praxis core main branch via workflow_dispatch input, and a praxis-known-issue PR label that allows jobs to pass despite failures from transient core issues. Signed-off-by: Sébastien Han --- .github/actions/clone-praxis/action.yml | 20 ++++++++++++++ .github/actions/patch-praxis/action.yml | 20 ++++++++++++++ .github/workflows/integration.yaml | 15 +++++++++++ .github/workflows/tests.yaml | 24 +++++++++++++++++ Makefile | 36 ++++++++++++++++++++++++- 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 .github/actions/clone-praxis/action.yml create mode 100644 .github/actions/patch-praxis/action.yml diff --git a/.github/actions/clone-praxis/action.yml b/.github/actions/clone-praxis/action.yml new file mode 100644 index 00000000..12f7f3a1 --- /dev/null +++ b/.github/actions/clone-praxis/action.yml @@ -0,0 +1,20 @@ +name: Clone Praxis core +description: >- + Clone praxis core repo as a sibling directory so + path-based [patch.crates-io] overrides resolve. + +inputs: + ref: + description: "Git ref to checkout (branch, tag, or SHA)" + required: false + default: "main" + +runs: + using: composite + steps: + - name: Clone praxis core + run: >- + git clone --depth 1 --branch "${{ inputs.ref }}" + https://github.com/praxis-proxy/praxis.git + "$GITHUB_WORKSPACE/../praxis" + shell: bash diff --git a/.github/actions/patch-praxis/action.yml b/.github/actions/patch-praxis/action.yml new file mode 100644 index 00000000..63982a18 --- /dev/null +++ b/.github/actions/patch-praxis/action.yml @@ -0,0 +1,20 @@ +name: Patch Praxis dependencies +description: >- + Override crates.io praxis dependencies with local + path dependencies from the cloned praxis repo. + +runs: + using: composite + steps: + - name: Append [patch.crates-io] to Cargo.toml + run: | + cat >> Cargo.toml << 'PATCH' + + [patch.crates-io] + praxis-proxy-core = { path = "../praxis/core" } + praxis-proxy-filter = { path = "../praxis/filter" } + praxis-proxy-protocol = { path = "../praxis/protocol" } + praxis-proxy-tls = { path = "../praxis/tls" } + praxis-proxy = { path = "../praxis/server" } + PATCH + shell: bash diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index b5881fc1..3a1db336 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -9,6 +9,7 @@ on: branches: [main] pull_request: branches: [main] + types: [opened, synchronize, reopened, labeled] workflow_dispatch: inputs: debug: @@ -16,6 +17,11 @@ on: required: false type: boolean default: false + praxis_main: + description: "Test against praxis core main branch instead of crates.io release" + required: false + type: boolean + default: false concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -34,11 +40,20 @@ jobs: test: runs-on: ubuntu-24.04 + continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'praxis-known-issue') }} permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Clone Praxis core (path dependency) + if: inputs.praxis_main + uses: ./.github/actions/clone-praxis + + - name: Patch Praxis dependencies + if: inputs.praxis_main + uses: ./.github/actions/patch-praxis + - name: Install stable Rust uses: dtolnay/rust-toolchain@6190aa5fb88a88ee71c12769924bbe63a9ab152e # 1.96.0 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f00c65f5..60738c03 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,6 +9,7 @@ on: branches: [main] pull_request: branches: [main] + types: [opened, synchronize, reopened, labeled] merge_group: branches: [main] workflow_dispatch: @@ -18,6 +19,11 @@ on: required: false type: boolean default: false + praxis_main: + description: "Test against praxis core main branch instead of crates.io release" + required: false + type: boolean + default: false concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -36,11 +42,20 @@ jobs: lint: runs-on: ubuntu-24.04 + continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'praxis-known-issue') }} permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Clone Praxis core (path dependency) + if: inputs.praxis_main + uses: ./.github/actions/clone-praxis + + - name: Patch Praxis dependencies + if: inputs.praxis_main + uses: ./.github/actions/patch-praxis + - name: Install nightly Rust (for rustfmt) uses: ./.github/actions/install-nightly-rust with: @@ -76,11 +91,20 @@ jobs: test: runs-on: ubuntu-24.04 + continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'praxis-known-issue') }} permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Clone Praxis core (path dependency) + if: inputs.praxis_main + uses: ./.github/actions/clone-praxis + + - name: Patch Praxis dependencies + if: inputs.praxis_main + uses: ./.github/actions/patch-praxis + - name: Install stable Rust uses: dtolnay/rust-toolchain@6190aa5fb88a88ee71c12769924bbe63a9ab152e # 1.96.0 diff --git a/Makefile b/Makefile index df46addc..08348cbf 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ endif lint fmt doc audit coverage-check \ require-container-engine \ container container-run \ - setup-hooks help + setup-hooks help \ + patch-praxis unpatch-praxis # ------------------------------------------------------------------- # All @@ -105,6 +106,35 @@ coverage-check: exit 1; \ fi +# ------------------------------------------------------------------- +# Praxis path override (test against local ../praxis) +# ------------------------------------------------------------------- + +patch-praxis: + @if [ ! -d "../praxis" ]; then \ + echo "ERROR: ../praxis not found — clone praxis core as a sibling directory first"; \ + exit 1; \ + fi + @if grep -q '\[patch\.crates-io\]' Cargo.toml; then \ + echo "Already patched — run 'make unpatch-praxis' first"; \ + exit 1; \ + fi + @printf '\n[patch.crates-io]\n\ + praxis-proxy-core = { path = "../praxis/core" }\n\ + praxis-proxy-filter = { path = "../praxis/filter" }\n\ + praxis-proxy-protocol = { path = "../praxis/protocol" }\n\ + praxis-proxy-tls = { path = "../praxis/tls" }\n\ + praxis-proxy = { path = "../praxis/server" }\n' >> Cargo.toml + @echo "Patched Cargo.toml to use ../praxis path dependencies" + +unpatch-praxis: + @if ! grep -q '\[patch\.crates-io\]' Cargo.toml; then \ + echo "Nothing to unpatch"; \ + exit 0; \ + fi + @sed -i.bak '/^\[patch\.crates-io\]/,$$d' Cargo.toml && rm -f Cargo.toml.bak + @echo "Removed [patch.crates-io] from Cargo.toml" + # ------------------------------------------------------------------- # Dev Setup # ------------------------------------------------------------------- @@ -145,3 +175,7 @@ help: @echo "Container:" @echo " container build praxis-ai container image" @echo " container-run run container in foreground (host network)" + @echo "" + @echo "Praxis override:" + @echo " patch-praxis use ../praxis path deps instead of crates.io" + @echo " unpatch-praxis revert to crates.io praxis deps" From 559d06e1b55839158d409d6d8353543b1e812490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Wed, 15 Jul 2026 17:24:03 +0200 Subject: [PATCH 02/13] ci: always test against praxis core main on every PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the conditional gating so clone-praxis and patch-praxis steps run unconditionally. Rename the workflow_dispatch input from praxis_main (boolean) to praxis_ref (string) so manual runs can target a specific branch or tag. Signed-off-by: Sébastien Han --- .github/workflows/integration.yaml | 12 ++++++------ .github/workflows/tests.yaml | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 3a1db336..4c07f1ad 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -17,11 +17,11 @@ on: required: false type: boolean default: false - praxis_main: - description: "Test against praxis core main branch instead of crates.io release" + praxis_ref: + description: "Praxis core git ref to test against (default: main)" required: false - type: boolean - default: false + type: string + default: "main" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -47,11 +47,11 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Clone Praxis core (path dependency) - if: inputs.praxis_main uses: ./.github/actions/clone-praxis + with: + ref: ${{ inputs.praxis_ref || 'main' }} - name: Patch Praxis dependencies - if: inputs.praxis_main uses: ./.github/actions/patch-praxis - name: Install stable Rust diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 60738c03..61966814 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,11 +19,11 @@ on: required: false type: boolean default: false - praxis_main: - description: "Test against praxis core main branch instead of crates.io release" + praxis_ref: + description: "Praxis core git ref to test against (default: main)" required: false - type: boolean - default: false + type: string + default: "main" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -49,11 +49,11 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Clone Praxis core (path dependency) - if: inputs.praxis_main uses: ./.github/actions/clone-praxis + with: + ref: ${{ inputs.praxis_ref || 'main' }} - name: Patch Praxis dependencies - if: inputs.praxis_main uses: ./.github/actions/patch-praxis - name: Install nightly Rust (for rustfmt) @@ -98,11 +98,11 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Clone Praxis core (path dependency) - if: inputs.praxis_main uses: ./.github/actions/clone-praxis + with: + ref: ${{ inputs.praxis_ref || 'main' }} - name: Patch Praxis dependencies - if: inputs.praxis_main uses: ./.github/actions/patch-praxis - name: Install stable Rust From 4a014b98e424932aceaca21eac1086946f657937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Wed, 15 Jul 2026 17:37:58 +0200 Subject: [PATCH 03/13] ci: add independent praxis-main compatibility test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move praxis-main testing into a dedicated workflow (praxis-compat.yaml) so regular CI stays on crates.io and the praxis-main job is visually distinct. The new test-praxis-main check is excluded from ci-status so it does not block PRs. Also make Config construction forward-compatible by using serde round-trip instead of struct literals, so new #[serde(default)] fields in praxis-core (like metrics) are handled automatically. Signed-off-by: Sébastien Han --- .github/workflows/ci-status.yaml | 1 + .github/workflows/integration.yaml | 17 +------ .github/workflows/praxis-compat.yaml | 73 +++++++++++++++++++++++++++ .github/workflows/tests.yaml | 26 +--------- tests/utils/src/net/backend/simple.rs | 57 +++++++++++---------- xtask/src/echo.rs | 33 ++++++------ 6 files changed, 122 insertions(+), 85 deletions(-) create mode 100644 .github/workflows/praxis-compat.yaml diff --git a/.github/workflows/ci-status.yaml b/.github/workflows/ci-status.yaml index 91d0085d..25b728bf 100644 --- a/.github/workflows/ci-status.yaml +++ b/.github/workflows/ci-status.yaml @@ -42,6 +42,7 @@ jobs: const excludedChecks = new Set([ 'ci-status', + 'test-praxis-main', ]); const terminalStatuses = new Set(['completed']); diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 4c07f1ad..b7f58e18 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -9,7 +9,7 @@ on: branches: [main] pull_request: branches: [main] - types: [opened, synchronize, reopened, labeled] + types: [opened, synchronize, reopened] workflow_dispatch: inputs: debug: @@ -17,12 +17,6 @@ on: required: false type: boolean default: false - praxis_ref: - description: "Praxis core git ref to test against (default: main)" - required: false - type: string - default: "main" - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} @@ -40,20 +34,11 @@ jobs: test: runs-on: ubuntu-24.04 - continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'praxis-known-issue') }} permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Clone Praxis core (path dependency) - uses: ./.github/actions/clone-praxis - with: - ref: ${{ inputs.praxis_ref || 'main' }} - - - name: Patch Praxis dependencies - uses: ./.github/actions/patch-praxis - - name: Install stable Rust uses: dtolnay/rust-toolchain@6190aa5fb88a88ee71c12769924bbe63a9ab152e # 1.96.0 diff --git a/.github/workflows/praxis-compat.yaml b/.github/workflows/praxis-compat.yaml new file mode 100644 index 00000000..f1d138e9 --- /dev/null +++ b/.github/workflows/praxis-compat.yaml @@ -0,0 +1,73 @@ +name: Tests (praxis main) + +# ------------------------------------------------------------------------------ +# Workflow Settings +# ------------------------------------------------------------------------------ + +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, synchronize, reopened, labeled] + workflow_dispatch: + inputs: + praxis_ref: + description: "Praxis core git ref to test against" + required: false + type: string + default: "main" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +permissions: {} + +env: + CARGO_TERM_COLOR: always + +jobs: + # ---------------------------------------------------------------------------- + # Build + test against praxis core main + # ---------------------------------------------------------------------------- + + test-praxis-main: + runs-on: ubuntu-24.04 + continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'praxis-known-issue') }} + permissions: + contents: read + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Clone Praxis core (path dependency) + uses: ./.github/actions/clone-praxis + with: + ref: ${{ inputs.praxis_ref || 'main' }} + + - name: Patch Praxis dependencies + uses: ./.github/actions/patch-praxis + + - name: Install stable Rust + uses: dtolnay/rust-toolchain@6190aa5fb88a88ee71c12769924bbe63a9ab152e # 1.96.0 + + - name: Cache Cargo registry and build artifacts + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + target + key: ${{ runner.os }}-rust-1.96.0-praxis-main-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-rust-1.96.0-praxis-main- + + - name: Unit tests + run: make test-unit + + - name: Schema tests + run: make test-schema + + - name: Integration tests + run: make test-integration diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 61966814..24b7b8a1 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,7 +9,7 @@ on: branches: [main] pull_request: branches: [main] - types: [opened, synchronize, reopened, labeled] + types: [opened, synchronize, reopened] merge_group: branches: [main] workflow_dispatch: @@ -19,12 +19,6 @@ on: required: false type: boolean default: false - praxis_ref: - description: "Praxis core git ref to test against (default: main)" - required: false - type: string - default: "main" - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} @@ -42,20 +36,11 @@ jobs: lint: runs-on: ubuntu-24.04 - continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'praxis-known-issue') }} permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Clone Praxis core (path dependency) - uses: ./.github/actions/clone-praxis - with: - ref: ${{ inputs.praxis_ref || 'main' }} - - - name: Patch Praxis dependencies - uses: ./.github/actions/patch-praxis - - name: Install nightly Rust (for rustfmt) uses: ./.github/actions/install-nightly-rust with: @@ -91,20 +76,11 @@ jobs: test: runs-on: ubuntu-24.04 - continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'praxis-known-issue') }} permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Clone Praxis core (path dependency) - uses: ./.github/actions/clone-praxis - with: - ref: ${{ inputs.praxis_ref || 'main' }} - - - name: Patch Praxis dependencies - uses: ./.github/actions/patch-praxis - - name: Install stable Rust uses: dtolnay/rust-toolchain@6190aa5fb88a88ee71c12769924bbe63a9ab152e # 1.96.0 diff --git a/tests/utils/src/net/backend/simple.rs b/tests/utils/src/net/backend/simple.rs index e73d2339..06de82a2 100644 --- a/tests/utils/src/net/backend/simple.rs +++ b/tests/utils/src/net/backend/simple.rs @@ -10,8 +10,7 @@ use std::{ }; use praxis_core::config::{ - AdminConfig, BodyLimitsConfig, Cluster, Config, Endpoint, FailureMode, FilterChainConfig, FilterEntry, - InsecureOptions, Listener, ProtocolKind, RuntimeConfig, + Cluster, Config, Endpoint, FailureMode, FilterChainConfig, FilterEntry, Listener, ProtocolKind, }; use super::specialized::{BackendGuard, read_until_headers_complete, spawn_tcp_server, spawn_tcp_server_with_shutdown}; @@ -401,32 +400,36 @@ fn build_static_response_filter(entry: &RoutedEntry) -> FilterEntry { } /// Assemble a [`Config`] from parts. +/// +/// Uses serde round-trip so new `#[serde(default)]` fields in +/// upstream praxis-core (e.g. `metrics`) are filled automatically, +/// keeping this compatible with both crates.io and path deps. fn build_config(address: &str, clusters: Vec, filters: Vec) -> Config { - Config { - admin: AdminConfig::default(), - body_limits: BodyLimitsConfig::default(), - clusters, - filter_chains: vec![FilterChainConfig { - name: "backend".to_owned(), - filters, - }], - insecure_options: InsecureOptions::default(), - listeners: vec![Listener { - address: address.to_owned(), - cluster: None, - downstream_read_timeout_ms: None, - filter_chains: vec!["backend".to_owned()], - max_connections: None, - name: "backend".to_owned(), - protocol: ProtocolKind::default(), - tcp_session_timeout_ms: None, - tcp_max_duration_secs: None, - tls: None, - upstream: None, - }], - runtime: RuntimeConfig::default(), - shutdown_timeout_secs: 5, - } + let listener = Listener { + address: address.to_owned(), + cluster: None, + downstream_read_timeout_ms: None, + filter_chains: vec!["backend".to_owned()], + max_connections: None, + name: "backend".to_owned(), + protocol: ProtocolKind::default(), + tcp_session_timeout_ms: None, + tcp_max_duration_secs: None, + tls: None, + upstream: None, + }; + + let chain = FilterChainConfig { + name: "backend".to_owned(), + filters, + }; + + let mut map = serde_yaml::Mapping::new(); + map.insert("clusters".into(), serde_yaml::to_value(clusters).unwrap()); + map.insert("filter_chains".into(), serde_yaml::to_value(vec![chain]).unwrap()); + map.insert("listeners".into(), serde_yaml::to_value(vec![listener]).unwrap()); + map.insert("shutdown_timeout_secs".into(), serde_yaml::to_value(5_u64).unwrap()); + serde_yaml::from_value(serde_yaml::Value::Mapping(map)).unwrap() } /// Build a YAML header mapping with `name` and `value` keys. diff --git a/xtask/src/echo.rs b/xtask/src/echo.rs index 450bc289..a5524f37 100644 --- a/xtask/src/echo.rs +++ b/xtask/src/echo.rs @@ -4,10 +4,7 @@ //! `cargo xtask echo` — quick HTTP test server. use clap::Parser; -use praxis_core::config::{ - AdminConfig, BodyLimitsConfig, Config, FailureMode, FilterChainConfig, FilterEntry, InsecureOptions, Listener, - ProtocolKind, RuntimeConfig, -}; +use praxis_core::config::{Config, FailureMode, FilterChainConfig, FilterEntry, Listener, ProtocolKind}; // ----------------------------------------------------------------------------- // CLI Arguments @@ -60,19 +57,21 @@ pub(crate) fn run(mut args: Args) { /// [`Config`]: praxis_core::config::Config fn build_config(args: &Args) -> Config { let entry = build_static_response_entry(args); - Config { - admin: AdminConfig::default(), - body_limits: BodyLimitsConfig::default(), - clusters: vec![], - filter_chains: vec![FilterChainConfig { - name: "echo".into(), - filters: vec![entry], - }], - insecure_options: InsecureOptions::default(), - listeners: vec![echo_listener(&args.address)], - runtime: RuntimeConfig::default(), - shutdown_timeout_secs: 30, - } + + let chain = FilterChainConfig { + name: "echo".into(), + filters: vec![entry], + }; + + let mut map = serde_yaml::Mapping::new(); + map.insert("clusters".into(), serde_yaml::to_value(Vec::<()>::new()).unwrap()); + map.insert("filter_chains".into(), serde_yaml::to_value(vec![chain]).unwrap()); + map.insert( + "listeners".into(), + serde_yaml::to_value(vec![echo_listener(&args.address)]).unwrap(), + ); + map.insert("shutdown_timeout_secs".into(), serde_yaml::to_value(30_u64).unwrap()); + serde_yaml::from_value(serde_yaml::Value::Mapping(map)).unwrap() } /// Build the echo listener bound to `address`. From 7eb7cdf9c80d788300eb645d14510bb42c7a0bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Wed, 15 Jul 2026 17:49:37 +0200 Subject: [PATCH 04/13] fix: add praxis-main feature for HttpFilterContext compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Praxis core main added peer_identity, pre_read_mutations, and structured_metadata fields to HttpFilterContext. Use a praxis-main feature flag to conditionally include these fields in the test context constructors. The praxis-compat CI workflow passes --features praxis-main when running unit tests for apis and filters crates. Signed-off-by: Sébastien Han --- .github/workflows/praxis-compat.yaml | 5 ++++- apis/Cargo.toml | 1 + apis/src/lib.rs | 6 ++++++ filters/Cargo.toml | 1 + filters/src/lib.rs | 6 ++++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/praxis-compat.yaml b/.github/workflows/praxis-compat.yaml index f1d138e9..6207d15a 100644 --- a/.github/workflows/praxis-compat.yaml +++ b/.github/workflows/praxis-compat.yaml @@ -64,7 +64,10 @@ jobs: ${{ runner.os }}-rust-1.96.0-praxis-main- - name: Unit tests - run: make test-unit + run: | + cargo test -p praxis-ai-apis --features praxis-main + cargo test -p praxis-ai-filters --features praxis-main + cargo test -p praxis-ai-proxy - name: Schema tests run: make test-schema diff --git a/apis/Cargo.toml b/apis/Cargo.toml index 2838ebf9..1d5ec575 100644 --- a/apis/Cargo.toml +++ b/apis/Cargo.toml @@ -14,6 +14,7 @@ name = "praxis_ai_apis" [features] default = ["store"] store = ["dep:sqlx", "dep:dashmap"] +praxis-main = [] [lints] workspace = true diff --git a/apis/src/lib.rs b/apis/src/lib.rs index b46eb835..8aa30aa9 100644 --- a/apis/src/lib.rs +++ b/apis/src/lib.rs @@ -91,6 +91,12 @@ pub(crate) mod test_utils { selected_endpoint_index: None, time_source: &praxis_core::time::SystemTimeSource, upstream: None, + #[cfg(feature = "praxis-main")] + peer_identity: None, + #[cfg(feature = "praxis-main")] + pre_read_mutations: Vec::new(), + #[cfg(feature = "praxis-main")] + structured_metadata: std::collections::HashMap::new(), } } diff --git a/filters/Cargo.toml b/filters/Cargo.toml index 035b7bd7..b3252f1a 100644 --- a/filters/Cargo.toml +++ b/filters/Cargo.toml @@ -14,6 +14,7 @@ name = "praxis_ai_filters" [features] default = ["apis"] apis = ["praxis-ai-apis/default"] +praxis-main = [] [lints] workspace = true diff --git a/filters/src/lib.rs b/filters/src/lib.rs index db9a7dff..763987bc 100644 --- a/filters/src/lib.rs +++ b/filters/src/lib.rs @@ -86,6 +86,12 @@ pub(crate) mod test_utils { selected_endpoint_index: None, time_source: &praxis_core::time::SystemTimeSource, upstream: None, + #[cfg(feature = "praxis-main")] + peer_identity: None, + #[cfg(feature = "praxis-main")] + pre_read_mutations: Vec::new(), + #[cfg(feature = "praxis-main")] + structured_metadata: std::collections::HashMap::new(), } } From 6db7487e5736eb917c1069ea869c35bfd46584b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 12:17:43 +0200 Subject: [PATCH 05/13] fix(ci): update lockfile after patching praxis deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Praxis core main bumped regex to ^1.13.1, which conflicts with the locked 1.13.0 in Cargo.lock. Running cargo update --workspace after patching lets Cargo resolve the new dependency requirements. Signed-off-by: Sébastien Han --- .github/workflows/praxis-compat.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/praxis-compat.yaml b/.github/workflows/praxis-compat.yaml index 6207d15a..6e6df659 100644 --- a/.github/workflows/praxis-compat.yaml +++ b/.github/workflows/praxis-compat.yaml @@ -48,6 +48,9 @@ jobs: - name: Patch Praxis dependencies uses: ./.github/actions/patch-praxis + - name: Update lockfile for patched dependencies + run: cargo update --workspace + - name: Install stable Rust uses: dtolnay/rust-toolchain@6190aa5fb88a88ee71c12769924bbe63a9ab152e # 1.96.0 From e19b88e4d6b663ab09911412118c044597b676a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 12:20:46 +0200 Subject: [PATCH 06/13] fix(ci): use cargo update without --workspace flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --workspace flag only updates workspace members, not transitive dependencies like regex. Plain cargo update resolves all deps including those bumped by praxis core main. Signed-off-by: Sébastien Han --- .github/workflows/praxis-compat.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/praxis-compat.yaml b/.github/workflows/praxis-compat.yaml index 6e6df659..4f6cecc2 100644 --- a/.github/workflows/praxis-compat.yaml +++ b/.github/workflows/praxis-compat.yaml @@ -49,7 +49,7 @@ jobs: uses: ./.github/actions/patch-praxis - name: Update lockfile for patched dependencies - run: cargo update --workspace + run: cargo update - name: Install stable Rust uses: dtolnay/rust-toolchain@6190aa5fb88a88ee71c12769924bbe63a9ab152e # 1.96.0 From 6e891af06e3b2b58474d9cdc8236c1bbfc917bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 12:31:19 +0200 Subject: [PATCH 07/13] fix(ci): add max_batch_size to JsonRpcConfig constructors for praxis main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Praxis core main added a max_batch_size field to JsonRpcConfig. Gate it behind #[cfg(feature = "praxis-main")] like the other forward-compat fields. Signed-off-by: Sébastien Han --- filters/src/agentic/a2a/mod.rs | 2 ++ filters/src/agentic/mcp/broker/mod.rs | 2 ++ filters/src/agentic/mcp/mod.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/filters/src/agentic/a2a/mod.rs b/filters/src/agentic/a2a/mod.rs index c0f7320b..64f5fe43 100644 --- a/filters/src/agentic/a2a/mod.rs +++ b/filters/src/agentic/a2a/mod.rs @@ -681,6 +681,8 @@ fn build_json_rpc_config(max_body_bytes: usize) -> JsonRpcConfig { kind: None, method: None, }, + #[cfg(feature = "praxis-main")] + max_batch_size: 100, max_body_bytes, on_invalid: OnInvalidBehavior::Continue, } diff --git a/filters/src/agentic/mcp/broker/mod.rs b/filters/src/agentic/mcp/broker/mod.rs index 787de95e..67de03e9 100644 --- a/filters/src/agentic/mcp/broker/mod.rs +++ b/filters/src/agentic/mcp/broker/mod.rs @@ -926,6 +926,8 @@ fn build_json_rpc_config(max_body_bytes: usize) -> JsonRpcConfig { kind: None, method: None, }, + #[cfg(feature = "praxis-main")] + max_batch_size: 100, max_body_bytes, on_invalid: OnInvalidBehavior::Continue, } diff --git a/filters/src/agentic/mcp/mod.rs b/filters/src/agentic/mcp/mod.rs index 679a19a7..aed9708f 100644 --- a/filters/src/agentic/mcp/mod.rs +++ b/filters/src/agentic/mcp/mod.rs @@ -201,6 +201,8 @@ fn build_json_rpc_config(max_body_bytes: usize) -> JsonRpcConfig { kind: None, method: None, }, + #[cfg(feature = "praxis-main")] + max_batch_size: 100, max_body_bytes, on_invalid: OnInvalidBehavior::Continue, } From 24ed7c10e929eda66b59fab7407e9dabbbf47ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 12:46:42 +0200 Subject: [PATCH 08/13] fix(ci): use cfg blocks for max_batch_size instead of field-level cfg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Field-level #[cfg] on struct expression initializers does not work when the struct definition does not gate the field. Use cfg on block expressions to conditionally compile the entire struct construction. Signed-off-by: Sébastien Han --- filters/src/agentic/a2a/mod.rs | 35 ++++++++++++++++++--------- filters/src/agentic/mcp/broker/mod.rs | 35 ++++++++++++++++++--------- filters/src/agentic/mcp/mod.rs | 35 ++++++++++++++++++--------- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/filters/src/agentic/a2a/mod.rs b/filters/src/agentic/a2a/mod.rs index 64f5fe43..c420a103 100644 --- a/filters/src/agentic/a2a/mod.rs +++ b/filters/src/agentic/a2a/mod.rs @@ -674,17 +674,30 @@ fn clear_sse_capture_metadata(ctx: &mut HttpFilterContext<'_>) { fn build_json_rpc_config(max_body_bytes: usize) -> JsonRpcConfig { use praxis_filter::builtins::http::payload_processing::json_rpc::config::{BatchPolicy, JsonRpcHeaders}; - JsonRpcConfig { - batch_policy: BatchPolicy::Reject, - headers: JsonRpcHeaders { - id: None, - kind: None, - method: None, - }, - #[cfg(feature = "praxis-main")] - max_batch_size: 100, - max_body_bytes, - on_invalid: OnInvalidBehavior::Continue, + let headers = JsonRpcHeaders { + id: None, + kind: None, + method: None, + }; + + #[cfg(feature = "praxis-main")] + { + JsonRpcConfig { + batch_policy: BatchPolicy::Reject, + headers, + max_batch_size: 100, + max_body_bytes, + on_invalid: OnInvalidBehavior::Continue, + } + } + #[cfg(not(feature = "praxis-main"))] + { + JsonRpcConfig { + batch_policy: BatchPolicy::Reject, + headers, + max_body_bytes, + on_invalid: OnInvalidBehavior::Continue, + } } } diff --git a/filters/src/agentic/mcp/broker/mod.rs b/filters/src/agentic/mcp/broker/mod.rs index 67de03e9..881b64c5 100644 --- a/filters/src/agentic/mcp/broker/mod.rs +++ b/filters/src/agentic/mcp/broker/mod.rs @@ -919,16 +919,29 @@ fn build_json_rpc_config(max_body_bytes: usize) -> JsonRpcConfig { json_rpc::config::{BatchPolicy, JsonRpcHeaders}, }; - JsonRpcConfig { - batch_policy: BatchPolicy::Reject, - headers: JsonRpcHeaders { - id: None, - kind: None, - method: None, - }, - #[cfg(feature = "praxis-main")] - max_batch_size: 100, - max_body_bytes, - on_invalid: OnInvalidBehavior::Continue, + let headers = JsonRpcHeaders { + id: None, + kind: None, + method: None, + }; + + #[cfg(feature = "praxis-main")] + { + JsonRpcConfig { + batch_policy: BatchPolicy::Reject, + headers, + max_batch_size: 100, + max_body_bytes, + on_invalid: OnInvalidBehavior::Continue, + } + } + #[cfg(not(feature = "praxis-main"))] + { + JsonRpcConfig { + batch_policy: BatchPolicy::Reject, + headers, + max_body_bytes, + on_invalid: OnInvalidBehavior::Continue, + } } } diff --git a/filters/src/agentic/mcp/mod.rs b/filters/src/agentic/mcp/mod.rs index aed9708f..758f4ab0 100644 --- a/filters/src/agentic/mcp/mod.rs +++ b/filters/src/agentic/mcp/mod.rs @@ -194,17 +194,30 @@ impl HttpFilter for McpFilter { fn build_json_rpc_config(max_body_bytes: usize) -> JsonRpcConfig { use praxis_filter::builtins::http::payload_processing::json_rpc::config::{BatchPolicy, JsonRpcHeaders}; - JsonRpcConfig { - batch_policy: BatchPolicy::Reject, - headers: JsonRpcHeaders { - id: None, - kind: None, - method: None, - }, - #[cfg(feature = "praxis-main")] - max_batch_size: 100, - max_body_bytes, - on_invalid: OnInvalidBehavior::Continue, + let headers = JsonRpcHeaders { + id: None, + kind: None, + method: None, + }; + + #[cfg(feature = "praxis-main")] + { + JsonRpcConfig { + batch_policy: BatchPolicy::Reject, + headers, + max_batch_size: 100, + max_body_bytes, + on_invalid: OnInvalidBehavior::Continue, + } + } + #[cfg(not(feature = "praxis-main"))] + { + JsonRpcConfig { + batch_policy: BatchPolicy::Reject, + headers, + max_body_bytes, + on_invalid: OnInvalidBehavior::Continue, + } } } From 148ca41aef97e0a5877c518091c3baec862a68b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 13:01:28 +0200 Subject: [PATCH 09/13] fix(ci): forward praxis-main feature through proxy crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo test -p praxis-ai-proxy builds praxis-ai-filters without the praxis-main feature, causing compile errors when deps are patched to praxis core main. Add a praxis-main feature to the server crate that forwards to its deps. Signed-off-by: Sébastien Han --- .github/workflows/praxis-compat.yaml | 2 +- server/Cargo.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/praxis-compat.yaml b/.github/workflows/praxis-compat.yaml index 4f6cecc2..8ba19c3e 100644 --- a/.github/workflows/praxis-compat.yaml +++ b/.github/workflows/praxis-compat.yaml @@ -70,7 +70,7 @@ jobs: run: | cargo test -p praxis-ai-apis --features praxis-main cargo test -p praxis-ai-filters --features praxis-main - cargo test -p praxis-ai-proxy + cargo test -p praxis-ai-proxy --features praxis-main - name: Schema tests run: make test-schema diff --git a/server/Cargo.toml b/server/Cargo.toml index 413de663..e2ab058e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -17,6 +17,9 @@ path = "src/lib.rs" name = "praxis-ai" path = "src/main.rs" +[features] +praxis-main = ["praxis-ai-filters/praxis-main", "praxis-ai-apis/praxis-main"] + [lints] workspace = true From 08c7e6e42d124a50702f8cdd5ec1dfa74d9d9045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 13:16:45 +0200 Subject: [PATCH 10/13] fix(ci): add SkipPipelineChecks arg for praxis main compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Praxis core main added a third argument to ordering_errors. Gate with cfg(feature = praxis-main) to keep crates.io compat. Signed-off-by: Sébastien Han --- server/src/pipelines.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/pipelines.rs b/server/src/pipelines.rs index a956a574..2516d428 100644 --- a/server/src/pipelines.rs +++ b/server/src/pipelines.rs @@ -97,6 +97,13 @@ fn validate_pipeline( skip: bool, allow_open_security: bool, ) -> Result<(), Box> { + #[cfg(feature = "praxis-main")] + let errors = pipeline.ordering_errors( + entries, + allow_open_security, + &praxis_core::config::SkipPipelineChecks::default(), + ); + #[cfg(not(feature = "praxis-main"))] let errors = pipeline.ordering_errors(entries, allow_open_security); if skip { From 6bffd4d018b7772aa0a0bed2757c1dd62c6404b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 13:33:55 +0200 Subject: [PATCH 11/13] fix(ci): forward praxis-main feature through test crates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schema and integration tests build praxis-ai-filters transitively via praxis-test-utils. Add praxis-main feature forwarding so all test targets compile against praxis core main. Signed-off-by: Sébastien Han --- .github/workflows/praxis-compat.yaml | 4 ++-- tests/integration/Cargo.toml | 1 + tests/schema/Cargo.toml | 3 +++ tests/utils/Cargo.toml | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/praxis-compat.yaml b/.github/workflows/praxis-compat.yaml index 8ba19c3e..c1548ff7 100644 --- a/.github/workflows/praxis-compat.yaml +++ b/.github/workflows/praxis-compat.yaml @@ -73,7 +73,7 @@ jobs: cargo test -p praxis-ai-proxy --features praxis-main - name: Schema tests - run: make test-schema + run: cargo test -p praxis-tests-schema --features praxis-main - name: Integration tests - run: make test-integration + run: cargo test -p praxis-tests-integration --features praxis-main diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 5ddc67f9..a401863c 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -10,6 +10,7 @@ publish = false [features] cpex-policy-engine = ["praxis-filter/cpex-policy-engine"] no-mac-cert-rotation-tests = [] # We use this to disable testing cert rotation on macOS +praxis-main = ["praxis-test-utils/praxis-main"] [lints] workspace = true diff --git a/tests/schema/Cargo.toml b/tests/schema/Cargo.toml index 8089584c..41d41c80 100644 --- a/tests/schema/Cargo.toml +++ b/tests/schema/Cargo.toml @@ -7,6 +7,9 @@ license.workspace = true readme = "../../README.md" publish = false +[features] +praxis-main = ["praxis-test-utils/praxis-main"] + [lints] workspace = true diff --git a/tests/utils/Cargo.toml b/tests/utils/Cargo.toml index 8f69db63..efc91e3c 100644 --- a/tests/utils/Cargo.toml +++ b/tests/utils/Cargo.toml @@ -7,6 +7,9 @@ license.workspace = true readme = "../../README.md" publish = false +[features] +praxis-main = ["praxis-ai-proxy/praxis-main"] + [lints] workspace = true From 3f3fd43bdf741e9d226f20211d6b60acdd5492b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 13:51:13 +0200 Subject: [PATCH 12/13] fix(ci): handle changed SNI validation messages for praxis main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Praxis core main replaced per-case SNI error messages with a generic 'sni must be a valid DNS hostname' message. Use cfg to match the right assertion per version. Signed-off-by: Sébastien Han --- tests/schema/tests/suite/cluster.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/schema/tests/suite/cluster.rs b/tests/schema/tests/suite/cluster.rs index fff4379c..7f8c0ea0 100644 --- a/tests/schema/tests/suite/cluster.rs +++ b/tests/schema/tests/suite/cluster.rs @@ -103,6 +103,9 @@ clusters: sni: "" "#; let err = Config::from_yaml(yaml).unwrap_err(); + #[cfg(feature = "praxis-main")] + assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("sni is empty"), "got: {err}"); } @@ -129,6 +132,9 @@ clusters: "# ); let err = Config::from_yaml(&yaml).unwrap_err(); + #[cfg(feature = "praxis-main")] + assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("exceeds 253 characters"), "got: {err}"); } @@ -155,6 +161,9 @@ clusters: "# ); let err = Config::from_yaml(&yaml).unwrap_err(); + #[cfg(feature = "praxis-main")] + assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("invalid label length"), "got: {err}"); } @@ -177,6 +186,9 @@ clusters: sni: "api..example.com" "#; let err = Config::from_yaml(yaml).unwrap_err(); + #[cfg(feature = "praxis-main")] + assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("invalid label length"), "got: {err}"); } @@ -199,6 +211,9 @@ clusters: sni: "api.exam ple.com" "#; let err = Config::from_yaml(yaml).unwrap_err(); + #[cfg(feature = "praxis-main")] + assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("invalid characters"), "got: {err}"); } From c8f63bcc9041e8a7e6c3159b2d65059c6dd9b251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 16 Jul 2026 14:01:50 +0200 Subject: [PATCH 13/13] style: format cluster schema tests with nightly rustfmt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sébastien Han --- tests/schema/tests/suite/cluster.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/schema/tests/suite/cluster.rs b/tests/schema/tests/suite/cluster.rs index 7f8c0ea0..c7b90b8f 100644 --- a/tests/schema/tests/suite/cluster.rs +++ b/tests/schema/tests/suite/cluster.rs @@ -104,7 +104,10 @@ clusters: "#; let err = Config::from_yaml(yaml).unwrap_err(); #[cfg(feature = "praxis-main")] - assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + assert!( + err.to_string().contains("sni must be a valid DNS hostname"), + "got: {err}" + ); #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("sni is empty"), "got: {err}"); } @@ -133,7 +136,10 @@ clusters: ); let err = Config::from_yaml(&yaml).unwrap_err(); #[cfg(feature = "praxis-main")] - assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + assert!( + err.to_string().contains("sni must be a valid DNS hostname"), + "got: {err}" + ); #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("exceeds 253 characters"), "got: {err}"); } @@ -162,7 +168,10 @@ clusters: ); let err = Config::from_yaml(&yaml).unwrap_err(); #[cfg(feature = "praxis-main")] - assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + assert!( + err.to_string().contains("sni must be a valid DNS hostname"), + "got: {err}" + ); #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("invalid label length"), "got: {err}"); } @@ -187,7 +196,10 @@ clusters: "#; let err = Config::from_yaml(yaml).unwrap_err(); #[cfg(feature = "praxis-main")] - assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + assert!( + err.to_string().contains("sni must be a valid DNS hostname"), + "got: {err}" + ); #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("invalid label length"), "got: {err}"); } @@ -212,7 +224,10 @@ clusters: "#; let err = Config::from_yaml(yaml).unwrap_err(); #[cfg(feature = "praxis-main")] - assert!(err.to_string().contains("sni must be a valid DNS hostname"), "got: {err}"); + assert!( + err.to_string().contains("sni must be a valid DNS hostname"), + "got: {err}" + ); #[cfg(not(feature = "praxis-main"))] assert!(err.to_string().contains("invalid characters"), "got: {err}"); }