name: CI

on:
  push:
    branches: ["master"]
    tags: ["juniper*"]
  pull_request:
    branches: ["master"]

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

env:
  RUST_BACKTRACE: 1

jobs:

  ################
  # Pull Request #
  ################

  pr:
    if: ${{ github.event_name == 'pull_request' }}
    needs:
      - bench
      - codespell
      - clippy
      - feature
      - msrv
      - release-check
      - rustfmt
      - test
      - test-book
      - wasm
    runs-on: ubuntu-latest
    steps:
      - run: true




  ##########################
  # Linting and formatting #
  ##########################

  clippy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: stable
          components: clippy

      - run: make cargo.lint

  codespell:
    name: codespell (Book)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: codespell-project/actions-codespell@v2
        with:
          path: book/

  rustfmt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: nightly
          components: rustfmt

      - run: make cargo.fmt check=yes




  ###########
  # Testing #
  ###########

  bench:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: stable

      - run: cargo clippy -p juniper_benchmarks --benches -- -D warnings
      - run: cargo bench -p juniper_benchmarks

  feature:
    strategy:
      fail-fast: false
      matrix:
        include:
          - { feature: <none>, crate: juniper }
          - { feature: anyhow, crate: juniper }
          - { feature: "anyhow,backtrace", crate: juniper }
          - { feature: bigdecimal, crate: juniper }
          - { feature: bson, crate: juniper }
          - { feature: chrono, crate: juniper }
          - { feature: chrono-clock, crate: juniper }
          - { feature: chrono-tz, crate: juniper }
          - { feature: expose-test-schema, crate: juniper }
          - { feature: rust_decimal, crate: juniper }
          - { feature: schema-language, crate: juniper }
          - { feature: time, crate: juniper }
          - { feature: url, crate: juniper }
          - { feature: uuid, crate: juniper }
          - { feature: graphql-transport-ws, crate: juniper_graphql_ws }
          - { feature: graphql-ws, crate: juniper_graphql_ws }
          - { feature: <none>, crate: juniper_actix }
          - { feature: subscriptions, crate: juniper_actix }
          - { feature: <none>, crate: juniper_axum }
          - { feature: subscriptions, crate: juniper_axum }
          - { feature: <none>, crate: juniper_warp }
          - { feature: subscriptions, crate: juniper_warp }
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: nightly
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: stable

      - run: cargo +nightly update -Z minimal-versions

      - run: cargo check -p ${{ matrix.crate }} --no-default-features
                   ${{ matrix.feature != '<none>'
                       && format('--features {0}', matrix.feature)
                       || '' }}
        env:
          RUSTFLAGS: -D warnings

  msrv:
    name: MSRV
    strategy:
      fail-fast: false
      matrix:
        msrv: ["1.75.0"]
        crate:
          - juniper_codegen
          - juniper
          - juniper_subscriptions
          - juniper_graphql_ws
          - juniper_actix
          - juniper_axum
          - juniper_hyper
          - juniper_rocket
          - juniper_warp
        os:
          - ubuntu
          - macOS
          - windows
        #include:
        #  - { msrv: "1.75.0", crate: "juniper_actix", os: "ubuntu" }
        #  - { msrv: "1.75.0", crate: "juniper_actix", os: "macOS" }
        #  - { msrv: "1.75.0", crate: "juniper_actix", os: "windows" }
    runs-on: ${{ matrix.os }}-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: nightly
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: ${{ matrix.msrv }}

      - run: cargo +nightly update -Z minimal-versions

      - run: make test.cargo crate=${{ matrix.crate }}

  package:
    name: check (package)
    if: ${{ startsWith(github.ref, 'refs/tags/juniper') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: stable

      - name: Parse crate name and version from Git tag
        id: tag
        uses: actions-ecosystem/action-regex-match@v2
        with:
          text: ${{ github.ref }}
          regex: '^refs/tags/(([a-z_]+)-v([0-9]+\.[0-9]+\.[0-9]+(-.+)?))$'

      - run: cargo package -p ${{ steps.tag.outputs.group2 }} --all-features

  test:
    strategy:
      fail-fast: false
      matrix:
        crate:
          - juniper_codegen
          - juniper
          - juniper_subscriptions
          - juniper_graphql_ws
          - juniper_integration_tests
          - juniper_codegen_tests
          - juniper_actix
          - juniper_axum
          - juniper_hyper
          - juniper_rocket
          - juniper_warp
        os:
          - ubuntu
          - macOS
          - windows
        toolchain:
          - stable
          - beta
          - nightly
        exclude:
          - crate: juniper_codegen_tests
            toolchain: beta
          - crate: juniper_codegen_tests
            toolchain: nightly
          - crate: juniper_codegen_tests
            os: macOS
          - crate: juniper_codegen_tests
            os: windows
    runs-on: ${{ matrix.os }}-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          components: rust-src

      - run: cargo install cargo-careful
        if: ${{ matrix.toolchain == 'nightly' }}

      - run: make test.cargo crate=${{ matrix.crate }}
                  careful=${{ (matrix.toolchain == 'nightly' && 'yes')
                           ||                                   'no' }}

  test-book:
    name: test (Book)
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu
          - macOS
          - windows
        toolchain:
          - stable
          - beta
          - nightly
    runs-on: ${{ matrix.os }}-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
      - uses: peaceiris/actions-mdbook@v2

      - run: make test.book

  wasm:
    strategy:
      fail-fast: false
      matrix:
        crate:
          - juniper
        target:
          - wasm32-unknown-unknown
          - wasm32-wasi
        toolchain:
          - stable
          - beta
          - nightly
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          target: ${{ matrix.target }}

      - run: cargo check --target ${{ matrix.target }} -p ${{ matrix.crate }}
                   ${{ matrix.target == 'wasm32-unknown-unknown'
                       && '--features js'
                       || '' }}




  #############
  # Releasing #
  #############

  publish:
    name: publish (crates.io)
    if: ${{ startsWith(github.ref, 'refs/tags/juniper') }}
    needs: ["release-github"]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: stable

      - name: Parse crate name and version from Git tag
        id: tag
        uses: actions-ecosystem/action-regex-match@v2
        with:
          text: ${{ github.ref }}
          regex: '^refs/tags/(([a-z_]+)-v([0-9]+\.[0-9]+\.[0-9]+(-.+)?))$'

      - run: cargo publish -p ${{ steps.tag.outputs.group2 }} --all-features
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATESIO_TOKEN }}

  release-check:
    name: check (release)
    if: ${{ !startsWith(github.ref, 'refs/tags/juniper') }}
    strategy:
      fail-fast: false
      matrix:
        crate:
          - juniper_codegen
          - juniper
          - juniper_subscriptions
          - juniper_graphql_ws
          - juniper_actix
          - juniper_axum
          - juniper_hyper
          - juniper_rocket
          - juniper_warp
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: stable

      - run: cargo install cargo-release

      - run: make cargo.release crate=${{ matrix.crate }} ver=minor
                                exec=no install=no

  release-github:
    name: release (GitHub)
    if: ${{ startsWith(github.ref, 'refs/tags/juniper') }}
    needs:
      - bench
      - clippy
      - codespell
      - feature
      - msrv
      - package
      - rustfmt
      - test
      - test-book
      - wasm
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Parse crate name and version from Git tag
        id: tag
        uses: actions-ecosystem/action-regex-match@v2
        with:
          text: ${{ github.ref }}
          regex: '^refs/tags/(([a-z_]+)-v([0-9]+\.[0-9]+\.[0-9]+(-.+)?))$'
      - name: Verify release version matches crate's Cargo manifest
        run: |
          test "${{ steps.tag.outputs.group3 }}" \
            == "$(grep -m1 'version = "' \
                       ${{ steps.tag.outputs.group2 }}/Cargo.toml \
                  | cut -d '"' -f2)"

      - name: Parse CHANGELOG link
        id: changelog
        run: echo "link=${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.tag.outputs.group1 }}/${{ steps.tag.outputs.group2 }}/CHANGELOG.md#$(sed -n '/^## \[${{ steps.tag.outputs.group3 }}\]/{s/^## \[\(.*\)\][^0-9]*\([0-9].*\)/\1--\2/;s/[^0-9a-z-]*//g;p;}' ${{ steps.tag.outputs.group2 }}/CHANGELOG.md)"
             >> $GITHUB_OUTPUT

      - name: Create GitHub release
        uses: softprops/action-gh-release@v2
        with:
          name: ${{ steps.tag.outputs.group2 }} ${{ steps.tag.outputs.group3 }}
          body: |
            [API docs](https://docs.rs/${{ steps.tag.outputs.group2 }}/${{ steps.tag.outputs.group3 }})
            [Changelog](${{ steps.changelog.outputs.link }})
          prerelease: ${{ contains(steps.tag.outputs.group3, '-') }}




  ##########
  # Deploy #
  ##########

  deploy-book:
    name: deploy (Book)
    if: ${{ github.ref == 'refs/heads/master'
         || startsWith(github.ref, 'refs/tags/juniper') }}
    needs: ["codespell", "test", "test-book"]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: peaceiris/actions-mdbook@v2

      - run: make book.build out=gh-pages${{ (github.ref == 'refs/heads/master'
                                              && '/master')
                                          ||     '' }}

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          keep_files: true
          publish_dir: book/gh-pages