meilibridge 0.1.6

High-performance PostgreSQL to Meilisearch connector
Documentation
name: CI

on:
  push:
    branches: [ main ]
  schedule:
    # Run at 2 AM UTC every Monday to catch dependency issues
    - cron: '0 2 * * 1'
  workflow_call:
    # Allow this workflow to be called by other workflows
  workflow_dispatch:
    # Allow manual workflow runs

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  RUSTFLAGS: "-D warnings"
  CARGO_INCREMENTAL: 0
  RUST_VERSION: "1.82"  # Minimum version for dependencies

jobs:
  # Security audit for dependencies
  security-audit:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Cache cargo-audit
      uses: actions/cache@v3
      with:
        path: ~/.cargo/bin/cargo-audit
        key: cargo-audit-${{ runner.os }}
    
    - name: Install cargo-audit
      run: |
        if ! command -v cargo-audit &> /dev/null; then
          cargo install cargo-audit
        fi
    
    - name: Run security audit
      run: |
        # Run audit but ignore the known protobuf issue in prometheus
        # This is documented in deny.toml
        cargo audit || (echo "::warning::Known security issue in protobuf 2.28.0 via prometheus dependency. Waiting for upstream fix." && exit 0)

  # License check for dependencies
  # TODO: Re-enable once cargo-deny or cargo-license support current Rust edition
  license-check:
    name: License Check (Skipped)
    runs-on: ubuntu-latest
    steps:
    - name: Skip license check
      run: |
        echo "::warning::License check temporarily disabled"
        echo "Waiting for cargo-deny/cargo-license to support current Rust edition"
        echo "TODO: Re-enable once tooling is updated"

  # Quick checks that should pass before running expensive tests
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable
      with:
        toolchain: "1.82"
        components: rustfmt, clippy
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Check formatting
      run: cargo fmt -- --check
    
    - name: Run clippy
      run: cargo clippy --all-targets --all-features -- -D warnings
    
    - name: Check documentation
      run: cargo doc --no-deps --all-features
      env:
        RUSTDOCFLAGS: "-D warnings"

  # Build job with different feature combinations
  build:
    name: Build
    needs: [lint, security-audit, license-check]
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        rust: ["1.82", "stable", "beta"]
        exclude:
          # Skip beta on Windows to save CI time
          - os: windows-latest
            rust: beta
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@master
      with:
        toolchain: ${{ matrix.rust }}
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Build (no features)
      run: cargo build --verbose --no-default-features
    
    - name: Build (default features)
      run: cargo build --verbose
    
    - name: Build (all features)
      run: cargo build --verbose --all-features
    
    - name: Build release mode
      run: cargo build --verbose --release

  # Minimum supported Rust version check
  msrv:
    name: Minimum Supported Rust Version
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Read MSRV from Cargo.toml
      id: msrv
      run: |
        MSRV=$(grep -oP 'rust-version = "\K[^"]+' Cargo.toml || echo "1.82")
        echo "msrv=$MSRV" >> $GITHUB_OUTPUT
    
    - name: Install Rust ${{ steps.msrv.outputs.msrv }}
      uses: dtolnay/rust-toolchain@master
      with:
        toolchain: ${{ steps.msrv.outputs.msrv }}
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Check MSRV
      run: cargo check --all-features

  # Unit tests - fast, no external dependencies
  unit-tests:
    name: Unit Tests
    needs: [lint, security-audit]
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        rust: ["1.82"]
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@master
      with:
        toolchain: ${{ matrix.rust }}
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Run unit tests
      run: cargo test --lib --verbose
    
    - name: Run unit tests (no default features)
      run: cargo test --lib --verbose --no-default-features
    
    - name: Run unit tests (all features)
      run: cargo test --lib --verbose --all-features

  # Integration tests - use testcontainers
  integration-tests:
    name: Integration Tests
    needs: [lint, security-audit]
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable
      with:
        toolchain: "1.82"
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Run integration tests
      env:
        RUST_LOG: debug
      run: cargo test --test integration --verbose

  # Doc tests
  doc-tests:
    name: Documentation Tests
    needs: lint
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable
      with:
        toolchain: "1.82"
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Run doc tests
      run: cargo test --doc --verbose --all-features

  # Benchmark tests to catch performance regressions
  benchmarks:
    name: Benchmarks
    needs: lint
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable
      with:
        toolchain: "1.82"
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Run benchmarks
      run: cargo bench --no-run

  # Coverage job - runs unit tests with coverage
  coverage:
    name: Code Coverage
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable
      with:
        toolchain: "1.82"
    
    - name: Cache dependencies
      uses: Swatinem/rust-cache@v2
    
    - name: Install tarpaulin
      run: |
        if ! command -v cargo-tarpaulin &> /dev/null; then
          cargo install cargo-tarpaulin
        fi
    
    - name: Generate coverage
      env:
        RUST_LOG: error
      run: |
        # Run only library and unit tests with coverage
        # Skip integration tests to avoid timeout issues
        cargo tarpaulin --lib --test unit --verbose --all-features \
          --timeout 300 --out xml \
          --exclude-files "*/tests/*" \
          --exclude-files "*/benches/*" \
          --exclude-files "*/examples/*" \
          --exclude-files "*/main.rs"
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        token: ${{ secrets.CODECOV_TOKEN }}
        fail_ci_if_error: false
        verbose: true

  # Summary job to ensure all tests pass
  test-summary:
    name: CI Summary
    if: always()
    needs: [
      security-audit,
      license-check,
      lint,
      build,
      msrv,
      unit-tests,
      integration-tests,
      doc-tests,
      benchmarks
    ]
    runs-on: ubuntu-latest
    steps:
    - name: Check results
      run: |
        results=(
          "${{ needs.security-audit.result }}"
          "${{ needs.license-check.result }}"
          "${{ needs.lint.result }}"
          "${{ needs.build.result }}"
          "${{ needs.msrv.result }}"
          "${{ needs.unit-tests.result }}"
          "${{ needs.integration-tests.result }}"
          "${{ needs.doc-tests.result }}"
          "${{ needs.benchmarks.result }}"
        )
        
        for result in "${results[@]}"; do
          if [[ "$result" != "success" && "$result" != "skipped" ]]; then
            echo "One or more CI checks failed"
            exit 1
          fi
        done
        
        echo "All CI checks passed successfully!"

  # Build and push Docker image after all tests pass
  docker-build:
    name: Docker Build and Push
    if: always()
    needs: [test-summary]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Log in to Docker Hub
      if: github.event_name != 'pull_request' && needs.test-summary.result == 'success'
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    - name: Build Docker image
      if: needs.test-summary.result == 'success'
      uses: docker/build-push-action@v5
      with:
        context: .
        file: ./docker/Dockerfile
        platforms: linux/amd64
        push: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
        tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/meilibridge:latest
        cache-from: type=gha
        cache-to: type=gha,mode=max
        build-args: |
          BUILD_DATE=${{ github.event.head_commit.timestamp }}
          VERSION=${{ github.ref_name }}
          VCS_REF=${{ github.sha }}
    
    - name: Run Trivy vulnerability scanner
      if: needs.test-summary.result == 'success' && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/meilibridge:latest
        format: 'sarif'
        output: 'trivy-results.sarif'
    
    - name: Upload Trivy scan results to GitHub Security tab
      if: needs.test-summary.result == 'success' && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: 'trivy-results.sarif'
    
    - name: Update Docker Hub description
      if: needs.test-summary.result == 'success' && github.event_name == 'push' && github.ref == 'refs/heads/main'
      continue-on-error: true
      uses: peter-evans/dockerhub-description@v4
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
        repository: ${{ secrets.DOCKERHUB_USERNAME }}/meilibridge
        readme-filepath: ./README.md
        short-description: ${{ github.event.repository.description }}