nsip 0.7.2

NSIP Search API client for nsipsearch.nsip.org/api
Documentation
---
name: CI

"on":
  push:
    branches: [develop, main]
  pull_request:
    branches: [develop, main]
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0
  RUSTFLAGS: "-D warnings"

jobs:
  fmt:
    name: Format
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: stable
          components: rustfmt
          cache-key: fmt

      - name: Check formatting
        run: cargo fmt --all -- --check

  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: stable
          components: clippy
          cache-key: clippy

      - name: Run clippy
        run: >-
          cargo clippy --all-targets --all-features
          -- -D warnings

  test:
    name: Test (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: stable
          cache-key: test

      - name: Run tests
        run: cargo test --all-features --verbose

  doc:
    name: Documentation
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      RUSTDOCFLAGS: "-D warnings"
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: stable
          cache-key: doc

      - name: Check documentation
        run: cargo doc --no-deps --all-features

  deny:
    name: Cargo Deny
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Install cargo-deny
        # yamllint disable-line rule:line-length
        uses: ./.github/actions/install-cargo-tool
        with:
          tool: cargo-deny

      - name: Run cargo-deny
        run: cargo deny check

  slug-docs:
    name: Error Slug Docs
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Assert every error slug has a catalog page
        run: |
          set -euo pipefail
          # Source of truth: the default slug literals in crates/problem.rs
          # (slug_path / validation_slug). Each must resolve to a Markdown
          # page under docs/reference/errors/.
          missing=0
          # Match any "domain/slug" literal (not just the current cli/api/mcp
          # domains) so a new error domain is caught. Strip the #[cfg(test)]
          # module first (test fixtures contain non-slug strings like
          # "errors/custom"), and drop RFC media types like
          # application/problem+json that share the shape.
          slugs=$(sed '/#\[cfg(test)\]/,$d' crates/problem.rs \
            | grep -oE '"[a-z][a-z0-9]*/[a-z0-9][a-z0-9-]*"' \
            | tr -d '"' \
            | grep -Ev '^(application|text|image|multipart|audio|video|font|model|message|example)/' \
            | sort -u)
          if [ -z "$slugs" ]; then
            echo "ERROR: no slug literals found in crates/problem.rs" >&2
            exit 1
          fi
          for slug in $slugs; do
            page="docs/reference/errors/${slug}.md"
            if [ -f "$page" ]; then
              echo "ok: $slug -> $page"
            else
              echo "MISSING: $slug has no catalog page at $page" >&2
              missing=1
            fi
          done
          if [ "$missing" -ne 0 ]; then
            echo "Error slug catalog is incomplete." >&2
            exit 1
          fi

  msrv:
    name: MSRV Check
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: "1.92"
          cache-key: msrv

      - name: Check MSRV
        run: cargo check --all-features

  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    timeout-minutes: 30
    environment: copilot
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: stable
          components: llvm-tools-preview
          cache-key: cov

      - name: Install cargo-llvm-cov
        # yamllint disable-line rule:line-length
        uses: ./.github/actions/install-cargo-tool
        with:
          tool: cargo-llvm-cov

      - name: Generate coverage report
        run: >-
          cargo llvm-cov --all-features
          --lcov --output-path lcov.info

      - name: Enforce 90% line coverage
        run: >-
          cargo llvm-cov --all-features
          --fail-under-lines 90

      - name: Upload coverage to Codecov
        # yamllint disable-line rule:line-length
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f  # v7.0.0
        with:
          files: lcov.info
          fail_ci_if_error: false
          verbose: true
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

  pin-check:
    # Central reusable workflow: every `uses:` must be pinned to a 40-char SHA.
    # Required-check context name: "pin-check / pin-check"
    permissions:
      contents: read
    # yamllint disable-line rule:line-length
    uses: zircote/.github/.github/workflows/pin-check.yml@e8f0dbde068cc0701e443e7b8d57ae9917de7da3  # main

  all-checks-pass:
    name: All Checks Pass
    if: always()
    needs: [fmt, clippy, test, doc, deny, msrv, coverage, slug-docs, pin-check]
    runs-on: ubuntu-latest
    steps:
      - name: Check all jobs passed
        env:
          FMT_RESULT: ${{ needs.fmt.result }}
          CLIPPY_RESULT: ${{ needs.clippy.result }}
          TEST_RESULT: ${{ needs.test.result }}
          DOC_RESULT: ${{ needs.doc.result }}
          DENY_RESULT: ${{ needs.deny.result }}
          MSRV_RESULT: ${{ needs.msrv.result }}
          COVERAGE_RESULT: ${{ needs.coverage.result }}
          SLUG_DOCS_RESULT: ${{ needs.slug-docs.result }}
          PIN_CHECK_RESULT: ${{ needs.pin-check.result }}
        run: |
          if [[ "$FMT_RESULT" != "success" ]] || \
             [[ "$CLIPPY_RESULT" != "success" ]] || \
             [[ "$TEST_RESULT" != "success" ]] || \
             [[ "$DOC_RESULT" != "success" ]] || \
             [[ "$DENY_RESULT" != "success" ]] || \
             [[ "$MSRV_RESULT" != "success" ]] || \
             [[ "$COVERAGE_RESULT" != "success" ]] || \
             [[ "$SLUG_DOCS_RESULT" != "success" ]] || \
             [[ "$PIN_CHECK_RESULT" != "success" ]]; then
            echo "One or more jobs failed"
            exit 1
          fi
          echo "All checks passed!"