structured-proxy 8.1.4

Universal gRPC→REST transcoding proxy — config-driven, works with any gRPC service
Documentation
name: CI

permissions:
  contents: read

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: "-Dwarnings"

jobs:
  quality-checks:
    name: Quality Checks (${{ matrix.backend.name }})
    runs-on: ubuntu-latest
    services:
      # Shared store for the rate-limit cross-instance reconciliation test.
      # Pulled through Google's Docker Hub mirror: anonymous pulls from
      # Docker Hub on shared runner addresses hit its rate limit.
      redis:
        image: mirror.gcr.io/library/redis:7
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    strategy:
      fail-fast: false
      matrix:
        backend:
          # One leg per crypto backend, plus the build that links both: Cargo
          # features are additive, so feature unification (and every tool that
          # reaches for `--all-features`, docs.rs and cargo-semver-checks
          # included) can produce that combination.
          - name: rust_crypto
            # Pure-Rust default backend (RustCrypto / rsa), with the binary
            # (`cli` carries `redis`).
            flags: "--features cli"
          - name: aws_lc_rs
            # Opt-in constant-time backend (aws-lc, C FFI); disables the default.
            flags: "--no-default-features --features aws_lc_rs,redis"
          - name: injected_verifier
            # No built-in verifier at all: the build a consumer takes when it
            # injects its own `hooks::TokenVerifier`. Links no JWT or TLS
            # crypto (and so no `rsa`), which is only true as long as nothing
            # outside the backend features reaches for jsonwebtoken or a
            # rustls provider — this leg is what keeps that honest. The binary
            # builds without a verifier too.
            flags: "--no-default-features --features cli"
          - name: all_features
            # Both backends at once, the build cargo-semver-checks and docs.rs
            # take. `aws_lc_rs` wins the tie; this leg keeps that wiring honest.
            flags: "--all-features"
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      # Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
      # Runs each test in its own process, so a hang names the test that hung
      # instead of timing the whole job out. Third-party action: pinned to a
      # commit SHA (dependabot keeps it fresh).
      - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2
        with:
          tool: cargo-nextest

      - name: Format
        if: matrix.backend.name == 'rust_crypto'
        run: cargo fmt --all -- --check

      # The default and injected-verifier builds are pure Rust: no C crypto
      # (ring, aws-lc) on any target. The aws_lc_rs and all-features legs link
      # aws-lc by choice.
      - name: No C crypto
        if: matrix.backend.name == 'rust_crypto' || matrix.backend.name == 'injected_verifier'
        run: |
          tree=$(cargo tree -e normal --target all --prefix none --format '{p}' ${{ matrix.backend.flags }})
          if printf '%s\n' "$tree" | grep -E '^(ring|aws-lc-rs|aws-lc-sys) v'; then
            echo "::error::C crypto is linked into the ${{ matrix.backend.name }} build"
            exit 1
          fi

      # A build without a crypto backend (`default-features = false`, what a
      # transcoding-only consumer takes) must pass an advisory scan without
      # the ignores in deny.toml: none of the crates they cover is linked.
      - name: No ignored advisories without a crypto backend
        if: matrix.backend.name == 'injected_verifier'
        run: |
          tree=$(cargo tree -e normal --target all --prefix none --format '{p}' ${{ matrix.backend.flags }})
          if printf '%s\n' "$tree" | grep -E '^(rsa|rustls-rustcrypto|paste) v|^rustls-webpki v0\.102\.'; then
            echo "::error::the build without a crypto backend links a crate with an ignored advisory"
            exit 1
          fi

      # `cargo add structured-proxy` takes the default features: the library
      # must compile none of the binary's dependencies, which sit behind `cli`.
      - name: No CLI dependencies by default
        if: matrix.backend.name == 'rust_crypto'
        run: |
          tree=$(cargo tree -e normal --target all --prefix none --format '{p}')
          if printf '%s\n' "$tree" | grep -E '^(clap|tracing-subscriber|redis) v'; then
            echo "::error::the default build links a CLI-only dependency"
            exit 1
          fi

      - name: Clippy
        run: cargo clippy --all-targets ${{ matrix.backend.flags }}

      - name: Build
        run: cargo build --release ${{ matrix.backend.flags }}

      - name: Test
        env:
          # Exercises the rate-limit reconciliation against the Redis service.
          SHIELD_REDIS_TEST_URL: redis://127.0.0.1:6379/
        run: cargo nextest run ${{ matrix.backend.flags }}

      # nextest does not run doctests; cargo does.
      - name: Doc tests
        run: cargo test --doc ${{ matrix.backend.flags }}

      # With `cli`, so the packaged crate's binary compiles too: without the
      # feature cargo verifies the library alone.
      - name: Publish dry-run
        if: matrix.backend.name == 'rust_crypto'
        run: cargo publish --dry-run --features cli

  # The release packages ship static musl binaries; build and test that target
  # on every change, so a dependency that breaks it fails here, not at release.
  musl:
    name: musl (${{ matrix.target }})
    runs-on: ${{ matrix.runs-on }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-musl
            runs-on: ubuntu-latest
          - target: aarch64-unknown-linux-musl
            runs-on: ubuntu-24.04-arm
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - name: Install musl tools
        run: |
          sudo apt-get update
          sudo apt-get install -y musl-tools
      # Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          key: ${{ matrix.target }}
      # Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
      - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2
        with:
          tool: cargo-nextest

      # The build the release workflow packages.
      - name: Build
        run: cargo build --release --target ${{ matrix.target }} --features cli --bin structured-proxy

      - name: Test
        run: cargo nextest run --target ${{ matrix.target }} --features cli

  # The `rust-version` in Cargo.toml: every target builds on exactly that
  # toolchain, with and without a built-in verifier (their code differs).
  msrv:
    name: MSRV (1.99)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@1.99
      # Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
      - name: Check (all features)
        run: cargo check --all-targets --all-features
      - name: Check (injected verifier)
        run: cargo check --all-targets --no-default-features --features cli

  security-audit:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      # Scans Cargo.lock for RustSec advisories; honors the ignore list in
      # deny.toml. The released binary rather than the Docker action, whose
      # image build pulls from Docker Hub and hits its anonymous rate limit
      # on shared runners.
      - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2
        with:
          tool: cargo-deny
      - name: Check advisories
        run: cargo deny check advisories