Full Code of Hexilee/roa for AI

master 979d9783b9ca cached
112 files
340.2 KB
93.0k tokens
611 symbols
1 requests
Download .txt
Showing preview only (368K chars total). Download the full file or copy to clipboard to get everything.
Repository: Hexilee/roa
Branch: master
Commit: 979d9783b9ca
Files: 112
Total size: 340.2 KB

Directory structure:
gitextract_am2piau4/

├── .github/
│   └── workflows/
│       ├── clippy.yml
│       ├── code-coverage.yml
│       ├── nightly-test.yml
│       ├── release.yml
│       ├── security-audit.yml
│       └── stable-test.yml
├── .gitignore
├── .vscode/
│   └── launch.json
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── assets/
│   ├── author.txt
│   ├── cert.pem
│   ├── css/
│   │   └── table.css
│   ├── key.pem
│   └── welcome.html
├── examples/
│   ├── echo.rs
│   ├── hello-world.rs
│   ├── https.rs
│   ├── restful-api.rs
│   ├── serve-file.rs
│   ├── websocket-echo.rs
│   └── welcome.rs
├── integration/
│   ├── diesel-example/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── src/
│   │   │   ├── bin/
│   │   │   │   └── api.rs
│   │   │   ├── data_object.rs
│   │   │   ├── endpoints.rs
│   │   │   ├── lib.rs
│   │   │   ├── models.rs
│   │   │   └── schema.rs
│   │   └── tests/
│   │       └── restful.rs
│   ├── juniper-example/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   └── src/
│   │       ├── main.rs
│   │       ├── models.rs
│   │       └── schema.rs
│   ├── multipart-example/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── assets/
│   │   │   └── index.html
│   │   └── src/
│   │       └── main.rs
│   └── websocket-example/
│       ├── Cargo.toml
│       ├── README.md
│       └── src/
│           └── main.rs
├── roa/
│   ├── Cargo.toml
│   ├── README.md
│   ├── src/
│   │   ├── body/
│   │   │   ├── file/
│   │   │   │   ├── content_disposition.rs
│   │   │   │   └── help.rs
│   │   │   └── file.rs
│   │   ├── body.rs
│   │   ├── compress.rs
│   │   ├── cookie.rs
│   │   ├── cors.rs
│   │   ├── forward.rs
│   │   ├── jsonrpc.rs
│   │   ├── jwt.rs
│   │   ├── lib.rs
│   │   ├── logger.rs
│   │   ├── query.rs
│   │   ├── router/
│   │   │   ├── endpoints/
│   │   │   │   ├── dispatcher.rs
│   │   │   │   └── guard.rs
│   │   │   ├── endpoints.rs
│   │   │   ├── err.rs
│   │   │   └── path.rs
│   │   ├── router.rs
│   │   ├── stream.rs
│   │   ├── tcp/
│   │   │   ├── incoming.rs
│   │   │   └── listener.rs
│   │   ├── tcp.rs
│   │   ├── tls/
│   │   │   ├── incoming.rs
│   │   │   └── listener.rs
│   │   ├── tls.rs
│   │   └── websocket.rs
│   └── templates/
│       └── user.html
├── roa-async-std/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       ├── lib.rs
│       ├── listener.rs
│       ├── net.rs
│       └── runtime.rs
├── roa-core/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       ├── app/
│       │   ├── future.rs
│       │   ├── runtime.rs
│       │   └── stream.rs
│       ├── app.rs
│       ├── body.rs
│       ├── context/
│       │   └── storage.rs
│       ├── context.rs
│       ├── err.rs
│       ├── executor.rs
│       ├── group.rs
│       ├── lib.rs
│       ├── middleware.rs
│       ├── request.rs
│       ├── response.rs
│       └── state.rs
├── roa-diesel/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       ├── async_ext.rs
│       ├── lib.rs
│       └── pool.rs
├── roa-juniper/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       └── lib.rs
├── rustfmt.toml
├── src/
│   └── lib.rs
├── templates/
│   └── directory.html
└── tests/
    ├── logger.rs
    ├── restful.rs
    └── serve-file.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/clippy.yml
================================================
on: [push, pull_request]
name: Clippy
jobs:
  clippy_check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install Toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: nightly
          override: true
          components: clippy
      - uses: actions-rs/clippy-check@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          args: --all-targets --all-features

================================================
FILE: .github/workflows/code-coverage.yml
================================================
name: Code Coverage
on:
  push:
    branches:
    - master
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: nightly
          override: true
      - name: Check all
        uses: actions-rs/cargo@v1
        with:
          command: check
          args: --all --all-features
  cover:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: nightly
          override: true
      - name: Install libsqlite3-dev
        run: |
          sudo apt-get update
          sudo apt-get install -y libsqlite3-dev
      - name: Run cargo-tarpaulin
        uses: actions-rs/tarpaulin@v0.1
        with:
          version: '0.21.0'
          args: --avoid-cfg-tarpaulin --out Xml --all --all-features
      - name: Upload to codecov.io
        uses: codecov/codecov-action@v1.0.2
        with:
          token: ${{secrets.CODECOV_TOKEN}}
      - name: Archive code coverage results
        uses: actions/upload-artifact@v1
        with:
          name: code-coverage-report
          path: cobertura.xml


================================================
FILE: .github/workflows/nightly-test.yml
================================================
name: Nightly Test
on:
  push:
  pull_request:
  schedule:
    - cron: '0 0 * * *'

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: nightly
          override: true
      - name: Check all
        uses: actions-rs/cargo@v1
        with:
          command: check
          args: --all --all-features
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Install libsqlite3-dev
        run: |
          sudo apt-get update
          sudo apt-get -y install libsqlite3-dev
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: nightly
          override: true
      - name: Run all tests
        uses: actions-rs/cargo@v1
        with:
          command: test
          args: --all --all-features --no-fail-fast



================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
  push:
    branches:
      - release
    paths:
      - '**/Cargo.toml'
      - '.github/workflows/release.yml'

jobs:
  publish:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      max-parallel: 1
      matrix:
        package:
          - name: roa-core
            registryName: roa-core
            path: roa-core
            publishPath: /target/package
          - name: roa
            registryName: roa
            path: roa
            publishPath: /target/package
          - name: roa-juniper
            registryName: roa-juniper
            path: roa-juniper
            publishPath: /target/package
          - name: roa-diesel
            registryName: roa-diesel
            path: roa-diesel
            publishPath: /target/package
          - name: roa-async-std
            registryName: roa-async-std
            path: roa-async-std
            publishPath: /target/package

    steps:
      - uses: actions/checkout@v2
      - name: Install Toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          override: true
      - name: install libsqlite3-dev
        run: |
          sudo apt-get update
          sudo apt-get install -y libsqlite3-dev
      - name: get version
        working-directory: ${{ matrix.package.path }}
        run: echo "PACKAGE_VERSION=$(sed -nE 's/^\s*version = \"(.*?)\"/\1/p' Cargo.toml)" >> $GITHUB_ENV
      - name: check published version
        run: echo "PUBLISHED_VERSION=$(cargo search ${{ matrix.package.registryName }} --limit 1 | sed -nE 's/^[^\"]*\"//; s/\".*//1p' -)" >> $GITHUB_ENV
      - name: cargo login
        if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
        run: cargo login ${{ secrets.CRATE_TOKEN }}
      - name: cargo package
        if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
        working-directory: ${{ matrix.package.path }}
        run: |
          echo "package dir:"
          ls
          cargo package
          echo "We will publish:" $PACKAGE_VERSION
          echo "This is current latest:" $PUBLISHED_VERSION
          echo "post package dir:"
          cd ${{ matrix.publishPath }}
          ls
      - name: Publish ${{ matrix.package.name }}
        if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
        working-directory: ${{ matrix.package.path }}
        run: |
          echo "# Cargo Publish" | tee -a ${{runner.workspace }}/notes.md
          echo "\`\`\`" >> ${{runner.workspace }}/notes.md
          cargo publish --no-verify 2>&1 | tee -a ${{runner.workspace }}/notes.md
          echo "\`\`\`" >> ${{runner.workspace }}/notes.md
      - name: Create Release
        id: create_crate_release
        if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
        uses: jbolda/create-release@v1.1.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ matrix.package.name }}-v${{ env.PACKAGE_VERSION }}
          release_name: "Release ${{ matrix.package.name }} v${{ env.PACKAGE_VERSION }} [crates.io]"
          bodyFromFile: ./../notes.md
          draft: false
          prerelease: false
      - name: Upload Release Asset
        id: upload-release-asset
        if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
        uses: actions/upload-release-asset@v1.0.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_crate_release.outputs.upload_url }}
          asset_path: ./${{ matrix.package.publishPath }}/${{ matrix.package.registryName }}-${{ env.PACKAGE_VERSION }}.crate
          asset_name: ${{ matrix.package.registryName }}-${{ env.PACKAGE_VERSION }}.crate
          asset_content_type: application/x-gtar

================================================
FILE: .github/workflows/security-audit.yml
================================================
name: Security Audit
on:
  schedule:
    - cron: '0 0 * * *'
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/audit-check@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .github/workflows/stable-test.yml
================================================
on: [push, pull_request]
name: Stable Test
jobs:
  check:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        rust:
          - stable
          - 1.60.0
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: ${{ matrix.rust }}
          override: true
      - name: Check all
        uses: actions-rs/cargo@v1
        with:
          command: check
          args: --all --features "roa/full"
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        rust:
          - stable
          - 1.60.0
    steps:
      - name: Install libsqlite3-dev
        run: |
          sudo apt-get update
          sudo apt-get -y install libsqlite3-dev
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: ${{ matrix.rust }}
          override: true
      - name: Run all tests
        uses: actions-rs/cargo@v1
        with:
          command: test
          args: --all --features "roa/full" --no-fail-fast
        


================================================
FILE: .gitignore
================================================
/target
**/*.rs.bk
Cargo.lock
**/upload/*
.env
node_modules

================================================
FILE: .vscode/launch.json
================================================
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa"
                ],
                "filter": {
                    "name": "roa",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa-core'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa-core"
                ],
                "filter": {
                    "name": "roa-core",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa-diesel'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa-diesel"
                ],
                "filter": {
                    "name": "roa-diesel",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa-tokio'",
            "cargo": {
                "args": [
                    "+nightly",
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa-tokio",
                    "--all-features",
                ],
                "filter": {
                    "name": "roa-tokio",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa-multipart'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa-multipart"
                ],
                "filter": {
                    "name": "roa-multipart",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa-juniper'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa-juniper"
                ],
                "filter": {
                    "name": "roa-juniper",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa-jsonrpc'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa-jsonrpc"
                ],
                "filter": {
                    "name": "roa-jsonrpc",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'diesel-example'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=diesel-example"
                ],
                "filter": {
                    "name": "diesel-example",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable 'api'",
            "cargo": {
                "args": [
                    "build",
                    "--bin=api",
                    "--package=diesel-example"
                ],
                "filter": {
                    "name": "api",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in executable 'api'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--bin=api",
                    "--package=diesel-example"
                ],
                "filter": {
                    "name": "api",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug integration test 'restful'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--test=restful",
                    "--package=diesel-example"
                ],
                "filter": {
                    "name": "restful",
                    "kind": "test"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable 'multipart-example'",
            "cargo": {
                "args": [
                    "build",
                    "--bin=multipart-example",
                    "--package=multipart-example"
                ],
                "filter": {
                    "name": "multipart-example",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in executable 'multipart-example'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--bin=multipart-example",
                    "--package=multipart-example"
                ],
                "filter": {
                    "name": "multipart-example",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable 'websocket-example'",
            "cargo": {
                "args": [
                    "build",
                    "--bin=websocket-example",
                    "--package=websocket-example"
                ],
                "filter": {
                    "name": "websocket-example",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in executable 'websocket-example'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--bin=websocket-example",
                    "--package=websocket-example"
                ],
                "filter": {
                    "name": "websocket-example",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable 'juniper-example'",
            "cargo": {
                "args": [
                    "build",
                    "--bin=juniper-example",
                    "--package=juniper-example"
                ],
                "filter": {
                    "name": "juniper-example",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in executable 'juniper-example'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--bin=juniper-example",
                    "--package=juniper-example"
                ],
                "filter": {
                    "name": "juniper-example",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in library 'roa-root'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--lib",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "roa-root",
                    "kind": "lib"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug example 'echo'",
            "cargo": {
                "args": [
                    "build",
                    "--example=echo",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "echo",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in example 'echo'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--example=echo",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "echo",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug example 'hello-world'",
            "cargo": {
                "args": [
                    "build",
                    "--example=hello-world",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "hello-world",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in example 'hello-world'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--example=hello-world",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "hello-world",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug example 'https'",
            "cargo": {
                "args": [
                    "build",
                    "--example=https",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "https",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in example 'https'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--example=https",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "https",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug example 'restful-api'",
            "cargo": {
                "args": [
                    "build",
                    "--example=restful-api",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "restful-api",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in example 'restful-api'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--example=restful-api",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "restful-api",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug example 'serve-file'",
            "cargo": {
                "args": [
                    "build",
                    "--example=serve-file",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "serve-file",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in example 'serve-file'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--example=serve-file",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "serve-file",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug example 'websocket-echo'",
            "cargo": {
                "args": [
                    "build",
                    "--example=websocket-echo",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "websocket-echo",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in example 'websocket-echo'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--example=websocket-echo",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "websocket-echo",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug example 'welcome'",
            "cargo": {
                "args": [
                    "build",
                    "--example=welcome",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "welcome",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in example 'welcome'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--example=welcome",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "welcome",
                    "kind": "example"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug integration test 'logger'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--test=logger",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "logger",
                    "kind": "test"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug integration test 'restful'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--test=restful",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "restful",
                    "kind": "test"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug integration test 'serve-file'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--test=serve-file",
                    "--package=roa-root"
                ],
                "filter": {
                    "name": "serve-file",
                    "kind": "test"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        }
    ]
}

================================================
FILE: Cargo.toml
================================================
[package]
name = "roa-root"
version = "0.6.0"
authors = ["Hexilee <hexileee@gmail.com>"]
edition = "2018"
license = "MIT"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]
members = [
    "roa",
    "roa-core",
    "roa-diesel",
    "roa-async-std",
    "roa-juniper",
    "integration/diesel-example",
    "integration/multipart-example",
    "integration/websocket-example",
    "integration/juniper-example"
]

[dev-dependencies]
tokio = { version = "1.15", features = ["full"] }
reqwest = { version = "0.11", features = ["json", "cookies", "gzip"] }
serde = { version = "1", features = ["derive"] }
roa = { path = "./roa", features = ["full"] }
test-case = "1.2"
once_cell = "1.8"
log = "0.4"
slab = "0.4.2"
multimap = "0.8.0"
hyper = "0.14"
chrono = "0.4"
mime = "0.3"
encoding = "0.2"
askama = "0.10"
http = "0.2"
bytesize = "1.1"
serde_json = "1.0"
tracing = "0.1"
futures = "0.3"
doc-comment = "0.3.3"
anyhow = "1.0"
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }


================================================
FILE: LICENSE
================================================
Copyright (c) 2020 Hexilee

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

================================================
FILE: Makefile
================================================
check:
	cargo check --all --features "roa/full"
build: 
	cargo build --all --features "roa/full"
test: 
	cargo test --all --features "roa/full"
fmt:
	cargo +nightly fmt
lint:
	cargo clippy --all-targets -- -D warnings
check-all:
	cargo +nightly check --all --all-features
test-all:
	cargo +nightly test --all --all-features


================================================
FILE: README.md
================================================
<div align="center">
  <h1>Roa</h1>
  <p><strong>Roa is an async web framework inspired by koajs, lightweight but powerful. </strong> </p>
  <p>

[![Stable Test](https://github.com/Hexilee/roa/workflows/Stable%20Test/badge.svg)](https://github.com/Hexilee/roa/actions)
[![codecov](https://codecov.io/gh/Hexilee/roa/branch/master/graph/badge.svg)](https://codecov.io/gh/Hexilee/roa) 
[![wiki](https://img.shields.io/badge/roa-wiki-purple.svg)](https://github.com/Hexilee/roa/wiki)
[![Rust Docs](https://docs.rs/roa/badge.svg)](https://docs.rs/roa)
[![Crate version](https://img.shields.io/crates/v/roa.svg)](https://crates.io/crates/roa)
[![Download](https://img.shields.io/crates/d/roa.svg)](https://crates.io/crates/roa)
[![MSRV-1.54](https://img.shields.io/badge/MSRV-1.54-blue.svg)](https://blog.rust-lang.org/2021/07/29/Rust-1.54.0.html)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Hexilee/roa/blob/master/LICENSE)

  </p>

  <h3>
    <a href="https://github.com/Hexilee/roa/tree/master/examples">Examples</a>
    <span> | </span>
    <a href="https://github.com/Hexilee/roa/wiki/Guide">Guide</a>
    <span> | </span>
    <a href="https://github.com/Hexilee/roa/wiki/Cookbook">Cookbook</a>
  </h3>
</div>
<br>


#### Feature highlights

- A lightweight, solid and well extensible core.
    - Supports HTTP/1.x and HTTP/2.0 protocols.
    - Full streaming.
    - Highly extensible middleware system.
    - Based on [`hyper`](https://github.com/hyperium/hyper), runtime-independent, you can chose async runtime as you like.
- Many useful extensions.
    - Official runtime schemes:
        - (Default) [tokio](https://github.com/tokio-rs/tokio) runtime and TcpStream.
        - [async-std](https://github.com/async-rs/async-std) runtime and TcpStream.
    - Transparent content compression (br, gzip, deflate, zstd).
    - Configurable and nestable router.
    - Named uri parameters(query and router parameter).
    - Cookie and jwt support.
    - HTTPS support.
    - WebSocket support.
    - Asynchronous multipart form support.
    - Other middlewares(logger, CORS .etc).
- Integrations
    - roa-diesel, integration with [diesel](https://github.com/diesel-rs/diesel).
    - roa-juniper, integration with [juniper](https://github.com/graphql-rust/juniper).
- Works on stable Rust.

#### Get start

```toml
# Cargo.toml

[dependencies]
roa = "0.6"
tokio = { version = "1.15", features = ["rt", "macro"] }
```

```rust,no_run
use roa::App;
use roa::preload::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let app = App::new().end("Hello, World");
    app.listen("127.0.0.1:8000", |addr| {
        println!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}
```
Refer to [wiki](https://github.com/Hexilee/roa/wiki) for more details.


================================================
FILE: assets/author.txt
================================================
Hexilee

================================================
FILE: assets/cert.pem
================================================
-----BEGIN CERTIFICATE-----
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
1QU=
-----END CERTIFICATE-----

================================================
FILE: assets/css/table.css
================================================
/* spacing */

table {
    table-layout: fixed;
    width: 80%;
    border-collapse: collapse;
}

thead th {
    text-align: left
}

thead th:nth-child(1) {
    width: 40%;
}

thead th:nth-child(2) {
    width: 20%;
}

thead th:nth-child(3) {
    width: 40%;
}

th, td {
    padding: 10px;
}

================================================
FILE: assets/key.pem
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP
n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M
IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5
4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ
WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk
oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli
JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6
/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD
YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP
wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA
69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA
AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/
9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm
YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR
6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM
ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI
7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab
L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+
vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ
b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz
0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL
OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI
6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC
71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g
9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu
bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb
IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga
/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc
KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2
iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP
tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD
jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY
l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj
gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh
Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q
1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW
t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI
fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9
5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt
+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc
3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf
cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T
qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU
DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K
5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc
fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc
Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ
4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6
I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c=
-----END RSA PRIVATE KEY-----

================================================
FILE: assets/welcome.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Roa Framework</title>
</head>
<body>
<h1>Welcome!</h1>
<h4>Go to <a href="https://github.com/Hexilee/roa">roa</a> for more information...</h4>
</body>
</html>

================================================
FILE: examples/echo.rs
================================================
//! RUST_LOG=info Cargo run --example echo,
//! then request http://127.0.0.1:8000 with some payload.

use std::error::Error as StdError;

use roa::logger::logger;
use roa::preload::*;
use roa::{App, Context};
use tracing::info;
use tracing_subscriber::EnvFilter;

async fn echo(ctx: &mut Context) -> roa::Result {
    let stream = ctx.req.stream();
    ctx.resp.write_stream(stream);
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let app = App::new().gate(logger).end(echo);
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: examples/hello-world.rs
================================================
//! RUST_LOG=info Cargo run --example hello-world,
//! then request http://127.0.0.1:8000.

use log::info;
use roa::logger::logger;
use roa::preload::*;
use roa::App;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;
    let app = App::new().gate(logger).end("Hello, World!");
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: examples/https.rs
================================================
//! RUST_LOG=info Cargo run --example https,
//! then request https://127.0.0.1:8000.

use std::error::Error as StdError;
use std::fs::File;
use std::io::BufReader;

use log::info;
use roa::body::DispositionType;
use roa::logger::logger;
use roa::preload::*;
use roa::tls::pemfile::{certs, rsa_private_keys};
use roa::tls::{Certificate, PrivateKey, ServerConfig, TlsListener};
use roa::{App, Context};
use tracing_subscriber::EnvFilter;

async fn serve_file(ctx: &mut Context) -> roa::Result {
    ctx.write_file("assets/welcome.html", DispositionType::Inline)
        .await
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let mut cert_file = BufReader::new(File::open("assets/cert.pem")?);
    let mut key_file = BufReader::new(File::open("assets/key.pem")?);
    let cert_chain = certs(&mut cert_file)?
        .into_iter()
        .map(Certificate)
        .collect();

    let config = ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_single_cert(
            cert_chain,
            PrivateKey(rsa_private_keys(&mut key_file)?.remove(0)),
        )?;

    let app = App::new().gate(logger).end(serve_file);
    app.listen_tls("127.0.0.1:8000", config, |addr| {
        info!("Server is listening on https://localhost:{}", addr.port())
    })?
    .await?;
    Ok(())
}


================================================
FILE: examples/restful-api.rs
================================================
//! RUST_LOG=info Cargo run --example restful-api,
//! then:
//! - `curl 127.0.0.1:8000/user/0`
//!     query user where id=0
//! - `curl -H "Content-type: application/json" -d '{"name":"Hexilee", "age": 20}' -X POST 127.0.0.1:8000/user`
//!     create a new user
//! - `curl -H "Content-type: application/json" -d '{"name":"Alice", "age": 20}' -X PUT 127.0.0.1:8000/user/0`
//!     update user where id=0, return the old data
//! - `curl 127.0.0.1:8000/user/0 -X DELETE`
//!     delete user where id=0

use std::result::Result as StdResult;
use std::sync::Arc;

use roa::http::StatusCode;
use roa::preload::*;
use roa::router::{get, post, Router};
use roa::{throw, App, Context, Result};
use serde::{Deserialize, Serialize};
use serde_json::json;
use slab::Slab;
use tokio::sync::RwLock;

#[derive(Debug, Serialize, Deserialize, Clone)]
struct User {
    name: String,
    age: u8,
}

#[derive(Clone)]
struct Database {
    table: Arc<RwLock<Slab<User>>>,
}

impl Database {
    fn new() -> Self {
        Self {
            table: Arc::new(RwLock::new(Slab::new())),
        }
    }

    async fn create(&self, user: User) -> usize {
        self.table.write().await.insert(user)
    }

    async fn retrieve(&self, id: usize) -> Result<User> {
        match self.table.read().await.get(id) {
            Some(user) => Ok(user.clone()),
            None => throw!(StatusCode::NOT_FOUND),
        }
    }

    async fn update(&self, id: usize, new_user: &mut User) -> Result {
        match self.table.write().await.get_mut(id) {
            Some(user) => {
                std::mem::swap(new_user, user);
                Ok(())
            }
            None => throw!(StatusCode::NOT_FOUND),
        }
    }

    async fn delete(&self, id: usize) -> Result<User> {
        if !self.table.read().await.contains(id) {
            throw!(StatusCode::NOT_FOUND)
        }
        Ok(self.table.write().await.remove(id))
    }
}

async fn create_user(ctx: &mut Context<Database>) -> Result {
    let user: User = ctx.read_json().await?;
    let id = ctx.create(user).await;
    ctx.write_json(&json!({ "id": id }))?;
    ctx.resp.status = StatusCode::CREATED;
    Ok(())
}

async fn get_user(ctx: &mut Context<Database>) -> Result {
    let id: usize = ctx.must_param("id")?.parse()?;
    let user = ctx.retrieve(id).await?;
    ctx.write_json(&user)
}

async fn update_user(ctx: &mut Context<Database>) -> Result {
    let id: usize = ctx.must_param("id")?.parse()?;
    let mut user: User = ctx.read_json().await?;
    ctx.update(id, &mut user).await?;
    ctx.write_json(&user)
}

async fn delete_user(ctx: &mut Context<Database>) -> Result {
    let id: usize = ctx.must_param("id")?.parse()?;
    let user = ctx.delete(id).await?;
    ctx.write_json(&user)
}

#[tokio::main]
async fn main() -> StdResult<(), Box<dyn std::error::Error>> {
    let router = Router::new()
        .on("/", post(create_user))
        .on("/:id", get(get_user).put(update_user).delete(delete_user));
    let app = App::state(Database::new()).end(router.routes("/user")?);
    app.listen("127.0.0.1:8000", |addr| {
        println!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: examples/serve-file.rs
================================================
//! RUST_LOG=info cargo run --example serve-file,
//! then request http://127.0.0.1:8000.

use std::borrow::Cow;
use std::path::Path;
use std::result::Result as StdResult;
use std::time::SystemTime;

use askama::Template;
use bytesize::ByteSize;
use chrono::offset::Local;
use chrono::DateTime;
use log::info;
use roa::body::DispositionType::*;
use roa::compress::Compress;
use roa::http::StatusCode;
use roa::logger::logger;
use roa::preload::*;
use roa::router::{get, Router};
use roa::{throw, App, Context, Next, Result};
use tokio::fs::{metadata, read_dir};
use tracing_subscriber::EnvFilter;

#[derive(Template)]
#[template(path = "directory.html")]
struct Dir<'a> {
    title: &'a str,
    root: &'a str,
    dirs: Vec<DirInfo>,
    files: Vec<FileInfo>,
}

struct DirInfo {
    link: String,
    name: String,
    modified: String,
}

struct FileInfo {
    link: String,
    name: String,
    modified: String,
    size: String,
}

impl<'a> Dir<'a> {
    fn new(title: &'a str, root: &'a str) -> Self {
        Self {
            title,
            root,
            dirs: Vec::new(),
            files: Vec::new(),
        }
    }
}

async fn path_checker(ctx: &mut Context, next: Next<'_>) -> Result {
    if ctx.must_param("path")?.contains("..") {
        throw!(StatusCode::BAD_REQUEST, "invalid path")
    } else {
        next.await
    }
}

async fn serve_path(ctx: &mut Context) -> Result {
    let path_value = ctx.must_param("path")?;
    let path = path_value.as_ref();
    let file_path = Path::new(".").join(path);
    let meta = metadata(&file_path).await?;
    if meta.is_file() {
        ctx.write_file(file_path, Inline).await
    } else if meta.is_dir() {
        serve_dir(ctx, path).await
    } else {
        throw!(StatusCode::NOT_FOUND, "path not found")
    }
}

async fn serve_root(ctx: &mut Context) -> Result {
    serve_dir(ctx, "").await
}

async fn serve_dir(ctx: &mut Context, path: &str) -> Result {
    let uri_path = Path::new("/").join(path);
    let mut entries = read_dir(Path::new(".").join(path)).await?;
    let title = uri_path
        .file_name()
        .map(|os_str| os_str.to_string_lossy())
        .unwrap_or(Cow::Borrowed("/"));
    let root_str = uri_path.to_string_lossy();
    let mut dir = Dir::new(&title, &root_str);
    while let Some(entry) = entries.next_entry().await? {
        let metadata = entry.metadata().await?;
        if metadata.is_dir() {
            dir.dirs.push(DirInfo {
                link: uri_path
                    .join(entry.file_name())
                    .to_string_lossy()
                    .to_string(),
                name: entry.file_name().to_string_lossy().to_string(),
                modified: format_time(metadata.modified()?),
            })
        }
        if metadata.is_file() {
            dir.files.push(FileInfo {
                link: uri_path
                    .join(entry.file_name())
                    .to_string_lossy()
                    .to_string(),
                name: entry.file_name().to_string_lossy().to_string(),
                modified: format_time(metadata.modified()?),
                size: ByteSize(metadata.len()).to_string(),
            })
        }
    }
    ctx.render(&dir)
}

fn format_time(time: SystemTime) -> String {
    let datetime: DateTime<Local> = time.into();
    datetime.format("%d/%m/%Y %T").to_string()
}

#[tokio::main]
async fn main() -> StdResult<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let wildcard_router = Router::new().gate(path_checker).on("/", get(serve_path));
    let router = Router::new()
        .on("/", serve_root)
        .include("/*{path}", wildcard_router);
    let app = App::new()
        .gate(logger)
        .gate(Compress::default())
        .end(router.routes("/")?);
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await
    .map_err(Into::into)
}


================================================
FILE: examples/websocket-echo.rs
================================================
//! RUST_LOG=info cargo run --example websocket-echo,
//! then request ws://127.0.0.1:8000/chat.

use std::error::Error as StdError;

use futures::StreamExt;
use http::Method;
use log::{error, info};
use roa::cors::Cors;
use roa::logger::logger;
use roa::preload::*;
use roa::router::{allow, Router};
use roa::websocket::Websocket;
use roa::App;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let router = Router::new().on(
        "/chat",
        allow(
            [Method::GET],
            Websocket::new(|_ctx, stream| async move {
                let (write, read) = stream.split();
                if let Err(err) = read.forward(write).await {
                    error!("{}", err);
                }
            }),
        ),
    );
    let app = App::new()
        .gate(logger)
        .gate(Cors::new())
        .end(router.routes("/")?);
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: examples/welcome.rs
================================================
//! RUST_LOG=info Cargo run --example welcome,
//! then request http://127.0.0.1:8000 with some payload.

use std::error::Error as StdError;

use log::info;
use roa::logger::logger;
use roa::preload::*;
use roa::App;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let app = App::new()
        .gate(logger)
        .end(include_str!("../assets/welcome.html"));
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: integration/diesel-example/Cargo.toml
================================================
[package]
name = "diesel-example"
version = "0.1.0"
authors = ["Hexilee <hexileee@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version = "1.15", features = ["full"] }
diesel = { version = "1.4", features = ["extras", "sqlite"] }
roa = { path = "../../roa", features = ["router", "json"] }
roa-diesel = { path = "../../roa-diesel" }
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = "0.1"
serde = { version = "1", features = ["derive"] }
anyhow = "1.0"

[dev-dependencies]
reqwest = { version = "0.11", features = ["json", "cookies", "gzip"] }

================================================
FILE: integration/diesel-example/README.md
================================================
```bash
RUST_LOG=info cargo run --bin api
```

- `curl 127.0.0.1:8000/post/1`
    query post where id=0 and published
- `curl -H "Content-type: application/json" -d '{"title":"Hello", "body": "Hello, world", "published": false}' -X POST 127.0.0.1:8000/post`
    create a new post
- `curl -H "Content-type: application/json" -d '{"title":"Hello", "body": "Hello, world", "published": true}' -X PUT 127.0.0.1:8000/post/1`
    update post where id=0, return the old data
- `curl 127.0.0.1:8000/post/1 -X DELETE`
    delete post where id=0


================================================
FILE: integration/diesel-example/src/bin/api.rs
================================================
use diesel_example::{create_pool, post_router};
use roa::logger::logger;
use roa::preload::*;
use roa::App;
use tracing::info;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;
    let app = App::state(create_pool()?)
        .gate(logger)
        .end(post_router().routes("/post")?);
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: integration/diesel-example/src/data_object.rs
================================================
use serde::Deserialize;

use crate::schema::posts;

// for both transfer and access
#[derive(Debug, Insertable, Deserialize)]
#[table_name = "posts"]
pub struct NewPost {
    pub title: String,
    pub body: String,
    pub published: bool,
}


================================================
FILE: integration/diesel-example/src/endpoints.rs
================================================
use diesel::prelude::*;
use diesel::result::Error;
use roa::http::StatusCode;
use roa::preload::*;
use roa::router::{get, post, Router};
use roa::{throw, Context, Result};
use roa_diesel::preload::*;

use crate::data_object::NewPost;
use crate::models::*;
use crate::schema::posts::dsl::{self, posts};
use crate::State;

pub fn post_router() -> Router<State> {
    Router::new()
        .on("/", post(create_post))
        .on("/:id", get(get_post).put(update_post).delete(delete_post))
}

async fn create_post(ctx: &mut Context<State>) -> Result {
    let data: NewPost = ctx.read_json().await?;
    let conn = ctx.get_conn().await?;
    let post = ctx
        .exec
        .spawn_blocking(move || {
            conn.transaction::<Post, Error, _>(|| {
                diesel::insert_into(crate::schema::posts::table)
                    .values(&data)
                    .execute(&conn)?;
                Ok(posts.order(dsl::id.desc()).first(&conn)?)
            })
        })
        .await?;
    ctx.resp.status = StatusCode::CREATED;
    ctx.write_json(&post)
}

async fn get_post(ctx: &mut Context<State>) -> Result {
    let id: i32 = ctx.must_param("id")?.parse()?;
    match ctx
        .first::<Post, _>(posts.find(id).filter(dsl::published.eq(true)))
        .await?
    {
        None => throw!(StatusCode::NOT_FOUND, &format!("post({}) not found", id)),
        Some(post) => ctx.write_json(&post),
    }
}

async fn update_post(ctx: &mut Context<State>) -> Result {
    let id: i32 = ctx.must_param("id")?.parse()?;
    let NewPost {
        title,
        body,
        published,
    } = ctx.read_json().await?;

    match ctx.first::<Post, _>(posts.find(id)).await? {
        None => throw!(StatusCode::NOT_FOUND, &format!("post({}) not found", id)),
        Some(post) => {
            ctx.execute(diesel::update(posts.find(id)).set((
                dsl::title.eq(title),
                dsl::body.eq(body),
                dsl::published.eq(published),
            )))
            .await?;
            ctx.write_json(&post)
        }
    }
}

async fn delete_post(ctx: &mut Context<State>) -> Result {
    let id: i32 = ctx.must_param("id")?.parse()?;
    match ctx.first::<Post, _>(posts.find(id)).await? {
        None => throw!(StatusCode::NOT_FOUND, &format!("post({}) not found", id)),
        Some(post) => {
            ctx.execute(diesel::delete(posts.find(id))).await?;
            ctx.write_json(&post)
        }
    }
}


================================================
FILE: integration/diesel-example/src/lib.rs
================================================
#[macro_use]
extern crate diesel;

mod data_object;
mod endpoints;
pub mod models;
pub mod schema;

use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use roa_diesel::{make_pool, Pool};

#[derive(Clone)]
pub struct State(pub Pool<SqliteConnection>);

impl AsRef<Pool<SqliteConnection>> for State {
    fn as_ref(&self) -> &Pool<SqliteConnection> {
        &self.0
    }
}

pub fn create_pool() -> anyhow::Result<State> {
    let pool = make_pool(":memory:")?;
    diesel::sql_query(
        r"
        CREATE TABLE posts (
          id INTEGER PRIMARY KEY,
          title VARCHAR NOT NULL,
          body TEXT NOT NULL,
          published BOOLEAN NOT NULL DEFAULT 'f'
        )
    ",
    )
    .execute(&*pool.get()?)?;
    Ok(State(pool))
}

pub use endpoints::post_router;


================================================
FILE: integration/diesel-example/src/models.rs
================================================
use diesel::Queryable;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Queryable, Serialize, Deserialize)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}


================================================
FILE: integration/diesel-example/src/schema.rs
================================================
table! {
    posts (id) {
        id -> Integer,
        title -> Text,
        body -> Text,
        published -> Bool,
    }
}


================================================
FILE: integration/diesel-example/tests/restful.rs
================================================
use diesel_example::models::Post;
use diesel_example::{create_pool, post_router};
use roa::http::StatusCode;
use roa::preload::*;
use roa::App;
use serde::Serialize;
use tracing::{debug, info};
use tracing_subscriber::EnvFilter;

#[derive(Debug, Serialize, Copy, Clone)]
pub struct NewPost<'a> {
    pub title: &'a str,
    pub body: &'a str,
    pub published: bool,
}

impl PartialEq<Post> for NewPost<'_> {
    fn eq(&self, other: &Post) -> bool {
        self.title == other.title && self.body == other.body && self.published == other.published
    }
}

#[tokio::test]
async fn test() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let app = App::state(create_pool()?).end(post_router().routes("/post")?);
    let (addr, server) = app.run()?;
    tokio::task::spawn(server);
    info!("server is running on {}", addr);
    let base_url = format!("http://{}/post", addr);
    let client = reqwest::Client::new();

    // Not Found
    let resp = client.get(&format!("{}/{}", &base_url, 0)).send().await?;
    assert_eq!(StatusCode::NOT_FOUND, resp.status());
    debug!("{}/{} not found", &base_url, 0);

    // Create
    let first_post = NewPost {
        title: "Hello",
        body: "Welcome to roa-diesel",
        published: false,
    };

    let resp = client.post(&base_url).json(&first_post).send().await?;
    assert_eq!(StatusCode::CREATED, resp.status());
    let created_post: Post = resp.json().await?;
    let id = created_post.id;
    assert_eq!(&first_post, &created_post);

    // Post isn't published, get nothing
    let resp = client.get(&format!("{}/{}", &base_url, id)).send().await?;
    assert_eq!(StatusCode::NOT_FOUND, resp.status());

    // Update
    let second_post = NewPost {
        published: true,
        ..first_post
    };
    let resp = client
        .put(&format!("{}/{}", &base_url, id))
        .json(&second_post)
        .send()
        .await?;
    assert_eq!(StatusCode::OK, resp.status());

    // Return old post
    let updated_post: Post = resp.json().await?;
    assert_eq!(&first_post, &updated_post);

    // Get it
    let resp = client.get(&format!("{}/{}", &base_url, id)).send().await?;
    assert_eq!(StatusCode::OK, resp.status());
    let query_post: Post = resp.json().await?;
    assert_eq!(&second_post, &query_post);

    // Delete
    let resp = client
        .delete(&format!("{}/{}", &base_url, id))
        .send()
        .await?;
    assert_eq!(StatusCode::OK, resp.status());
    let deleted_post: Post = resp.json().await?;
    assert_eq!(&second_post, &deleted_post);

    // Post is deleted, get nothing
    let resp = client.get(&format!("{}/{}", &base_url, id)).send().await?;
    assert_eq!(StatusCode::NOT_FOUND, resp.status());
    Ok(())
}


================================================
FILE: integration/juniper-example/Cargo.toml
================================================
[package]
name = "juniper-example"
version = "0.1.0"
authors = ["Hexilee <hexileee@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
diesel = "1.4"
roa = { path = "../../roa", features = ["router"] }
roa-diesel = { path = "../../roa-diesel" }
roa-juniper = { path = "../../roa-juniper" }
diesel-example = { path = "../diesel-example" }
tokio = { version = "1.15", features = ["full"] }
tracing = "0.1"
serde = { version = "1", features = ["derive"] }
futures = "0.3"
juniper = { version = "0.15", default-features = false }
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = "1.0"

================================================
FILE: integration/juniper-example/README.md
================================================
```bash
RUST_LOG=info cargo run
```

Then request http://127.0.0.1:8000, play with the GraphQL playground! 

================================================
FILE: integration/juniper-example/src/main.rs
================================================
#[macro_use]
extern crate diesel;

mod models;
mod schema;
use std::error::Error as StdError;

use diesel::prelude::*;
use diesel::result::Error;
use diesel_example::{create_pool, State};
use juniper::http::playground::playground_source;
use juniper::{
    graphql_value, EmptySubscription, FieldError, FieldResult, GraphQLInputObject, RootNode,
};
use roa::http::Method;
use roa::logger::logger;
use roa::preload::*;
use roa::router::{allow, get, Router};
use roa::App;
use roa_diesel::preload::*;
use roa_juniper::{GraphQL, JuniperContext};
use serde::Serialize;
use tracing::info;
use tracing_subscriber::EnvFilter;

use crate::models::Post;
use crate::schema::posts;

#[derive(Debug, Insertable, Serialize, GraphQLInputObject)]
#[table_name = "posts"]
#[graphql(description = "A new post")]
struct NewPost {
    title: String,
    body: String,
    published: bool,
}

struct Query;

#[juniper::graphql_object(
    Context = JuniperContext<State>,
)]
impl Query {
    async fn post(
        &self,
        ctx: &JuniperContext<State>,
        id: i32,
        published: bool,
    ) -> FieldResult<Post> {
        use crate::schema::posts::dsl::{self, posts};
        match ctx
            .first(posts.find(id).filter(dsl::published.eq(published)))
            .await?
        {
            Some(post) => Ok(post),
            None => Err(FieldError::new(
                "post not found",
                graphql_value!({ "status": 404, "id": id }),
            )),
        }
    }
}

struct Mutation;

#[juniper::graphql_object(
    Context = JuniperContext<State>,
)]
impl Mutation {
    async fn create_post(
        &self,
        ctx: &JuniperContext<State>,
        new_post: NewPost,
    ) -> FieldResult<Post> {
        use crate::schema::posts::dsl::{self, posts};
        let conn = ctx.get_conn().await?;
        let post = ctx
            .exec
            .spawn_blocking(move || {
                conn.transaction::<Post, Error, _>(|| {
                    diesel::insert_into(crate::schema::posts::table)
                        .values(&new_post)
                        .execute(&conn)?;
                    Ok(posts.order(dsl::id.desc()).first(&conn)?)
                })
            })
            .await?;
        Ok(post)
    }

    async fn update_post(
        &self,
        id: i32,
        ctx: &JuniperContext<State>,
        new_post: NewPost,
    ) -> FieldResult<Post> {
        use crate::schema::posts::dsl::{self, posts};
        match ctx.first(posts.find(id)).await? {
            None => Err(FieldError::new(
                "post not found",
                graphql_value!({ "status": 404, "id": id }),
            )),
            Some(old_post) => {
                let NewPost {
                    title,
                    body,
                    published,
                } = new_post;
                ctx.execute(diesel::update(posts.find(id)).set((
                    dsl::title.eq(title),
                    dsl::body.eq(body),
                    dsl::published.eq(published),
                )))
                .await?;
                Ok(old_post)
            }
        }
    }

    async fn delete_post(&self, ctx: &JuniperContext<State>, id: i32) -> FieldResult<Post> {
        use crate::schema::posts::dsl::posts;
        match ctx.first(posts.find(id)).await? {
            None => Err(FieldError::new(
                "post not found",
                graphql_value!({ "status": 404, "id": id }),
            )),
            Some(old_post) => {
                ctx.execute(diesel::delete(posts.find(id))).await?;
                Ok(old_post)
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let router = Router::new()
        .on("/", get(playground_source("/api", None)))
        .on(
            "/api",
            allow(
                [Method::GET, Method::POST],
                GraphQL(RootNode::new(Query, Mutation, EmptySubscription::new())),
            ),
        );
    let app = App::state(create_pool()?)
        .gate(logger)
        .end(router.routes("/")?);
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: integration/juniper-example/src/models.rs
================================================
use diesel::Queryable;
use juniper::GraphQLObject;
use serde::Deserialize;

#[derive(Debug, Clone, Queryable, Deserialize, GraphQLObject)]
#[graphql(description = "A post")]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}


================================================
FILE: integration/juniper-example/src/schema.rs
================================================
table! {
    posts (id) {
        id -> Integer,
        title -> Text,
        body -> Text,
        published -> Bool,
    }
}


================================================
FILE: integration/multipart-example/Cargo.toml
================================================
[package]
name = "multipart-example"
version = "0.1.0"
authors = ["Hexilee <hexileee@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
roa = { path = "../../roa", features = ["router", "file", "multipart"] }
tokio = { version = "1.15", features = ["full"] }
tracing = "0.1"
futures = "0.3"
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = "1.0"


================================================
FILE: integration/multipart-example/README.md
================================================
```bash
RUST_LOG=info cargo run
```

Then visit `http://127.0.0.1:8000`, files will be stored in `./upload`.

================================================
FILE: integration/multipart-example/assets/index.html
================================================
<html lang="en">
<head><title>Upload Test</title></head>
<body>
<form action="/file" method="post" enctype="multipart/form-data">
    <input type="file" multiple name="file"/>
    <input type="submit" value="Submit">
</form>
</body>
</html>

================================================
FILE: integration/multipart-example/src/main.rs
================================================
use std::error::Error as StdError;
use std::path::Path;

use roa::body::{DispositionType, PowerBody};
use roa::logger::logger;
use roa::preload::*;
use roa::router::{get, post, Router};
use roa::{App, Context};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tracing::info;
use tracing_subscriber::EnvFilter;

async fn get_form(ctx: &mut Context) -> roa::Result {
    ctx.write_file("./assets/index.html", DispositionType::Inline)
        .await
}

async fn post_file(ctx: &mut Context) -> roa::Result {
    let mut form = ctx.read_multipart().await?;
    while let Some(mut field) = form.next_field().await? {
        info!("{:?}", field.content_type());
        match field.file_name() {
            None => continue, // ignore non-file field
            Some(filename) => {
                let path = Path::new("./upload");
                let mut file = File::create(path.join(filename)).await?;
                while let Some(c) = field.chunk().await? {
                    file.write_all(&c).await?;
                }
            }
        }
    }
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let router = Router::new()
        .on("/", get(get_form))
        .on("/file", post(post_file));
    let app = App::new().gate(logger).end(router.routes("/")?);
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}


================================================
FILE: integration/websocket-example/Cargo.toml
================================================
[package]
name = "websocket-example"
version = "0.1.0"
authors = ["Hexilee <hexileee@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
roa = { path = "../../roa", features = ["router", "file", "websocket"] }
tokio = { version = "1.15", features = ["full"] }
tracing = "0.1"
futures = "0.3"
http = "0.2"
slab = "0.4"
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = "1.0"

[dev-dependencies]
tokio-tungstenite = { version = "0.15", features = ["connect"] }


================================================
FILE: integration/websocket-example/README.md
================================================
WIP...

================================================
FILE: integration/websocket-example/src/main.rs
================================================
use std::borrow::Cow;
use std::error::Error as StdError;
use std::sync::Arc;

use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt};
use http::Method;
use roa::logger::logger;
use roa::preload::*;
use roa::router::{allow, RouteTable, Router, RouterError};
use roa::websocket::tungstenite::protocol::frame::coding::CloseCode;
use roa::websocket::tungstenite::protocol::frame::CloseFrame;
use roa::websocket::tungstenite::Error as WsError;
use roa::websocket::{Message, SocketStream, Websocket};
use roa::{App, Context};
use slab::Slab;
use tokio::sync::{Mutex, RwLock};
use tracing::{debug, error, info, warn};
use tracing_subscriber::EnvFilter;

type Sender = SplitSink<SocketStream, Message>;
type Channel = Slab<Mutex<Sender>>;
#[derive(Clone)]
struct SyncChannel(Arc<RwLock<Channel>>);

impl SyncChannel {
    fn new() -> Self {
        Self(Arc::new(RwLock::new(Slab::new())))
    }

    async fn broadcast(&self, message: Message) {
        let channel = self.0.read().await;
        for (_, sender) in channel.iter() {
            if let Err(err) = sender.lock().await.send(message.clone()).await {
                error!("broadcast error: {}", err);
            }
        }
    }

    async fn send(&self, index: usize, message: Message) {
        if let Err(err) = self.0.read().await[index].lock().await.send(message).await {
            error!("message send error: {}", err)
        }
    }

    async fn register(&self, sender: Sender) -> usize {
        self.0.write().await.insert(Mutex::new(sender))
    }

    async fn deregister(&self, index: usize) -> Sender {
        self.0.write().await.remove(index).into_inner()
    }
}

async fn handle_message(
    ctx: &Context<SyncChannel>,
    index: usize,
    mut receiver: SplitStream<SocketStream>,
) -> Result<(), WsError> {
    while let Some(message) = receiver.next().await {
        let message = message?;
        match message {
            Message::Close(frame) => {
                debug!("websocket connection close: {:?}", frame);
                break;
            }
            Message::Ping(data) => ctx.send(index, Message::Pong(data)).await,
            Message::Pong(data) => warn!("ignored pong: {:?}", data),
            msg => ctx.broadcast(msg).await,
        }
    }
    Ok(())
}

fn route(prefix: &'static str) -> Result<RouteTable<SyncChannel>, RouterError> {
    Router::new()
        .on(
            "/chat",
            allow(
                [Method::GET],
                Websocket::new(|ctx: Context<SyncChannel>, stream| async move {
                    let (sender, receiver) = stream.split();
                    let index = ctx.register(sender).await;
                    let result = handle_message(&ctx, index, receiver).await;
                    let mut sender = ctx.deregister(index).await;
                    if let Err(err) = result {
                        let result = sender
                            .send(Message::Close(Some(CloseFrame {
                                code: CloseCode::Invalid,
                                reason: Cow::Owned(err.to_string()),
                            })))
                            .await;
                        if let Err(err) = result {
                            warn!("send close message error: {}", err)
                        }
                    }
                }),
            ),
        )
        .routes(prefix)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
        .map_err(|err| anyhow::anyhow!("fail to init tracing subscriber: {}", err))?;

    let app = App::state(SyncChannel::new()).gate(logger).end(route("/")?);
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use roa::preload::*;
    use tokio_tungstenite::connect_async;

    use super::{route, App, Message, SinkExt, StdError, StreamExt, SyncChannel};

    #[tokio::test]
    async fn echo() -> Result<(), Box<dyn StdError>> {
        let channel = SyncChannel::new();
        let app = App::state(channel.clone()).end(route("/")?);
        let (addr, server) = app.run()?;
        tokio::task::spawn(server);
        let (ws_stream, _) = connect_async(format!("ws://{}/chat", addr)).await?;
        let (mut sender, mut recv) = ws_stream.split();
        tokio::time::sleep(Duration::from_secs(1)).await;
        assert_eq!(1, channel.0.read().await.len());

        // ping
        sender
            .send(Message::Ping(b"Hello, World!".to_vec()))
            .await?;
        let msg = recv.next().await.unwrap()?;
        assert!(msg.is_pong());
        assert_eq!(b"Hello, World!".as_ref(), msg.into_data().as_slice());

        // close
        sender.send(Message::Close(None)).await?;
        tokio::time::sleep(Duration::from_secs(1)).await;
        assert_eq!(0, channel.0.read().await.len());
        Ok(())
    }

    #[tokio::test]
    async fn broadcast() -> Result<(), Box<dyn StdError>> {
        let channel = SyncChannel::new();
        let app = App::state(channel.clone()).end(route("/")?);
        let (addr, server) = app.run()?;
        tokio::task::spawn(server);
        let url = format!("ws://{}/chat", addr);
        for _ in 0..100 {
            let url = url.clone();
            tokio::task::spawn(async move {
                if let Ok((ws_stream, _)) = connect_async(url).await {
                    let (mut sender, mut recv) = ws_stream.split();
                    if let Some(Ok(message)) = recv.next().await {
                        assert!(sender.send(message).await.is_ok());
                    }
                    tokio::time::sleep(Duration::from_secs(1)).await;
                    assert!(sender.send(Message::Close(None)).await.is_ok());
                }
            });
        }
        tokio::time::sleep(Duration::from_secs(1)).await;
        assert_eq!(100, channel.0.read().await.len());

        let (ws_stream, _) = connect_async(url).await?;
        let (mut sender, mut recv) = ws_stream.split();
        assert!(sender
            .send(Message::Text("Hello, World!".to_string()))
            .await
            .is_ok());
        tokio::time::sleep(Duration::from_secs(2)).await;
        assert_eq!(1, channel.0.read().await.len());

        let mut counter = 0i32;
        while let Some(item) = recv.next().await {
            if let Ok(Message::Text(message)) = item {
                assert_eq!("Hello, World!", message);
            }
            counter += 1;
            if counter == 101 {
                break;
            }
        }
        Ok(())
    }
}


================================================
FILE: roa/Cargo.toml
================================================
[package]
name = "roa"
version = "0.6.1"
authors = ["Hexilee <i@hexilee.me>"]
edition = "2018"
license = "MIT"
readme = "./README.md"
repository = "https://github.com/Hexilee/roa"
documentation = "https://docs.rs/roa"
homepage = "https://github.com/Hexilee/roa/wiki"
description = """
async web framework inspired by koajs, lightweight but powerful.
"""
keywords = ["http", "web", "framework", "async"]
categories = ["network-programming", "asynchronous",
              "web-programming::http-server"]

[package.metadata.docs.rs]
features = ["docs"]
rustdoc-args = ["--cfg", "feature=\"docs\""]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[badges]
codecov = { repository = "Hexilee/roa" }

[dependencies]
tracing = { version = "0.1", features = ["log"] }
futures = "0.3"
bytesize = "1.0"
async-trait = "0.1.51"
url = "2.2"
percent-encoding = "2.1"
bytes = "1.1"
headers = "0.3"
tokio = "1.15"
tokio-util = { version = "0.6.9", features = ["io"] }
once_cell = "1.8"
hyper = { version = "0.14", default-features = false, features = ["stream", "server", "http1", "http2"] }
roa-core = { path = "../roa-core", version = "0.6" }

cookie = { version = "0.15", features = ["percent-encode"], optional = true }
jsonwebtoken = { version = "7.2", optional = true }
serde = { version = "1", optional = true }
serde_json = { version = "1.0", optional = true }
async-compression = { version = "0.3.8", features = ["all-algorithms", "futures-io"], optional = true }

# router
radix_trie = { version = "0.2.1", optional = true }
regex = { version = "1.5", optional = true }

# body
askama = { version = "0.10", optional = true }
doc-comment = { version = "0.3.3", optional = true }
serde_urlencoded = { version = "0.7", optional = true }
mime_guess = { version = "2.0", optional = true }
multer = { version = "2.0", optional = true }
mime = { version = "0.3", optional = true }

# websocket
tokio-tungstenite = { version = "0.15.0", default-features = false, optional = true }


# tls
rustls = { version = "0.20", optional = true }
tokio-rustls = { version = "0.23", optional = true }
rustls-pemfile = { version = "0.2", optional = true }

# jsonrpc
jsonrpc-v2 = { version = "0.10", default-features = false, features = ["bytes-v10"], optional = true }

[dev-dependencies]
tokio = { version = "1.15", features = ["full"] }
tokio-native-tls = "0.3"
hyper-tls = "0.5"
reqwest = { version = "0.11", features = ["json", "cookies", "gzip", "multipart"] }
pretty_env_logger = "0.4"
serde = { version = "1", features = ["derive"] }
test-case = "1.2"
slab = "0.4.5"
multimap = "0.8"
hyper = "0.14"
mime = "0.3"
encoding = "0.2"
askama = "0.10"
anyhow = "1.0"

[features]
default = ["async_rt"]
full = [
    "default",
    "json",
    "urlencoded",
    "file",
    "multipart",
    "template",
    "tls",
    "router",
    "jwt",
    "cookies",
    "compress",
    "websocket",
    "jsonrpc",
]

docs = ["full", "roa-core/docs"]
runtime = ["roa-core/runtime"]
json = ["serde", "serde_json"]
multipart = ["multer", "mime"]
urlencoded = ["serde", "serde_urlencoded"]
file = ["mime_guess", "tokio/fs"]
template = ["askama"]
tcp = ["tokio/net", "tokio/time"]
tls = ["rustls", "tokio-rustls", "rustls-pemfile"]
cookies = ["cookie"]
jwt = ["jsonwebtoken", "serde", "serde_json"]
router = ["radix_trie", "regex", "doc-comment"]
websocket = ["tokio-tungstenite"]
compress = ["async-compression"]
async_rt = ["runtime", "tcp"]
jsonrpc = ["jsonrpc-v2"]


================================================
FILE: roa/README.md
================================================
[![Build status](https://img.shields.io/travis/Hexilee/roa/master.svg)](https://travis-ci.org/Hexilee/roa)
[![codecov](https://codecov.io/gh/Hexilee/roa/branch/master/graph/badge.svg)](https://codecov.io/gh/Hexilee/roa)
[![Rust Docs](https://docs.rs/roa/badge.svg)](https://docs.rs/roa)
[![Crate version](https://img.shields.io/crates/v/roa.svg)](https://crates.io/crates/roa)
[![Download](https://img.shields.io/crates/d/roa.svg)](https://crates.io/crates/roa)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Hexilee/roa/blob/master/LICENSE)

### Introduction

Roa is an async web framework inspired by koajs, lightweight but powerful.

### Application

A Roa application is a structure composing and executing middlewares and an endpoint in a stack-like manner.

The obligatory hello world application:

```rust,no_run
use roa::App;
use roa::preload::*;
use tracing::info;
use std::error::Error as StdError;

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    let app = App::new().end("Hello, World");
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}
```

#### Endpoint

An endpoint is a request handler.

There are some build-in endpoints in roa.

- Functional endpoint

    A normal functional endpoint is an async function with signature:
    `async fn(&mut Context) -> Result`.
    
    ```rust
    use roa::{App, Context, Result};
    
    async fn endpoint(ctx: &mut Context) -> Result {
        Ok(())
    }
    
    let app = App::new().end(endpoint);
    ```
  
- Ok endpoint

    `()` is an endpoint always return `Ok(())`
    
    ```rust
    let app = roa::App::new().end(());
    ```

- Status endpoint

    `Status` is an endpoint always return `Err(Status)`
    
    ```rust
    use roa::{App, status};
    use roa::http::StatusCode;
    let app = App::new().end(status!(StatusCode::BAD_REQUEST));
    ```

- String endpoint

    Write string to body.
    
    ```rust
    use roa::App;
    
    let app = App::new().end("Hello, world"); // static slice
    let app = App::new().end("Hello, world".to_owned()); // string
    ```

- Redirect endpoint

    Redirect to an uri.
    
    ```rust
    use roa::App;
    use roa::http::Uri;
    
    let app = App::new().end("/target".parse::<Uri>().unwrap());
    ```


#### Cascading
Like koajs, middleware suspends and passes control to "downstream" by invoking `next.await`.
Then control flows back "upstream" when `next.await` returns.

The following example responds with "Hello World",
however first the request flows through the x-response-time and logging middleware to mark
when the request started, then continue to yield control through the endpoint.
When a middleware invokes next the function suspends and passes control to the next middleware or endpoint.
After the endpoint is called,
the stack will unwind and each middleware is resumed to perform
its upstream behaviour.

```rust,no_run
use roa::{App, Context, Next};
use roa::preload::*;
use tracing::info;
use std::error::Error as StdError;
use std::time::Instant;

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    let app = App::new()
        .gate(logger)
        .gate(x_response_time)
        .end("Hello, World");
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}

async fn logger(ctx: &mut Context, next: Next<'_>) -> roa::Result {
    next.await?;
    let rt = ctx.load::<String>("x-response-time").unwrap();
    info!("{} {} - {}", ctx.method(), ctx.uri(), rt.as_str());
    Ok(())
}

async fn x_response_time(ctx: &mut Context, next: Next<'_>) -> roa::Result {
    let start = Instant::now();
    next.await?;
    let ms = start.elapsed().as_millis();
    ctx.store("x-response-time", format!("{}ms", ms));
    Ok(())
}

```

### Status Handling

You can catch or straightly throw a status returned by next.

```rust,no_run
use roa::{App, Context, Next, status};
use roa::preload::*;
use roa::http::StatusCode;
use tokio::task::spawn;
use tracing::info;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = App::new()
        .gate(catch)
        .gate(not_catch)
        .end(status!(StatusCode::IM_A_TEAPOT, "I'm a teapot!"));
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}

async fn catch(_ctx: &mut Context, next: Next<'_>) -> roa::Result {
    // catch
    if let Err(status) = next.await {
        // teapot is ok
        if status.status_code != StatusCode::IM_A_TEAPOT {
            return Err(status);
        }
    }
    Ok(())
}

async fn not_catch(ctx: &mut Context, next: Next<'_>) -> roa::Result {
    next.await?; // just throw
    unreachable!()
}
```

#### status_handler
App has an status_handler to handle status thrown by the top middleware.
This is the status_handler:

```rust,no_run
use roa::{Context, Status};
pub fn status_handler<S>(ctx: &mut Context<S>, status: Status) {
    ctx.resp.status = status.status_code;
    if status.expose {
        ctx.resp.write(status.message);
    } else {
        tracing::error!("{}", status);
    }
}
```

### Router.
Roa provides a configurable and nestable router.

```rust,no_run
use roa::preload::*;
use roa::router::{Router, get};
use roa::{App, Context};
use tokio::task::spawn;
use tracing::info;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let router = Router::new()
        .on("/:id", get(end)); // get dynamic "/:id"
    let app = App::new()
        .end(router.routes("/user")?); // route with prefix "/user"
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    
    Ok(())
}

async fn end(ctx: &mut Context) -> roa::Result {
    // get "/user/1", then id == 1.
    let id: u64 = ctx.must_param("id")?.parse()?;
    // do something
    Ok(())
}
```

### Query

Roa provides a middleware `query_parser`.

```rust,no_run
use roa::preload::*;
use roa::query::query_parser;
use roa::{App, Context};
use tokio::task::spawn;
use tracing::info;

async fn must(ctx: &mut Context) -> roa::Result {
    // request "/?id=1", then id == 1.
    let id: u64 = ctx.must_query("id")?.parse()?;
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = App::new()
        .gate(query_parser)
        .end(must);
    app.listen("127.0.0.1:8080", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;     
    Ok(())
}
```

### Other modules

- body: dealing with body more conveniently.
- compress: supports transparent content compression.
- cookie: cookies getter or setter.
- cors: CORS support.
- forward: "X-Forwarded-*" parser.
- jwt: json web token support.
- logger: a logger middleware.
- tls: https supports.
- websocket: websocket supports.


================================================
FILE: roa/src/body/file/content_disposition.rs
================================================
use std::convert::{TryFrom, TryInto};
use std::fmt::{self, Display, Formatter};

use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};

use super::help::bug_report;
use crate::http::header::HeaderValue;
use crate::Status;

// This encode set is used for HTTP header values and is defined at
// https://tools.ietf.org/html/rfc5987#section-3.2
const HTTP_VALUE: &AsciiSet = &CONTROLS
    .add(b' ')
    .add(b'"')
    .add(b'%')
    .add(b'\'')
    .add(b'(')
    .add(b')')
    .add(b'*')
    .add(b',')
    .add(b'/')
    .add(b':')
    .add(b';')
    .add(b'<')
    .add(b'-')
    .add(b'>')
    .add(b'?')
    .add(b'[')
    .add(b'\\')
    .add(b']')
    .add(b'{')
    .add(b'}');

/// Type of content-disposition, inline or attachment
#[derive(Clone, Debug, PartialEq)]
pub enum DispositionType {
    /// Inline implies default processing
    Inline,
    /// Attachment implies that the recipient should prompt the user to save the response locally,
    /// rather than process it normally (as per its media type).
    Attachment,
}

/// A structure to generate value of "Content-Disposition"
pub struct ContentDisposition {
    typ: DispositionType,
    encoded_filename: Option<String>,
}

impl ContentDisposition {
    /// Construct by disposition type and optional filename.
    #[inline]
    pub(crate) fn new(typ: DispositionType, filename: Option<&str>) -> Self {
        Self {
            typ,
            encoded_filename: filename
                .map(|name| utf8_percent_encode(name, HTTP_VALUE).to_string()),
        }
    }
}

impl TryFrom<ContentDisposition> for HeaderValue {
    type Error = Status;
    #[inline]
    fn try_from(value: ContentDisposition) -> Result<Self, Self::Error> {
        value
            .to_string()
            .try_into()
            .map_err(|err| bug_report(format!("{}\nNot a valid header value", err)))
    }
}

impl Display for ContentDisposition {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match &self.encoded_filename {
            None => f.write_fmt(format_args!("{}", self.typ)),
            Some(name) => f.write_fmt(format_args!(
                "{}; filename={}; filename*=UTF-8''{}",
                self.typ, name, name
            )),
        }
    }
}

impl Display for DispositionType {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            DispositionType::Inline => f.write_str("inline"),
            DispositionType::Attachment => f.write_str("attachment"),
        }
    }
}


================================================
FILE: roa/src/body/file/help.rs
================================================
use crate::http::StatusCode;
use crate::Status;

const BUG_HELP: &str =
    r"This is a bug of roa::body::file, please report it to https://github.com/Hexilee/roa.";

#[inline]
pub fn bug_report(message: impl ToString) -> Status {
    Status::new(
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("{}\n{}", message.to_string(), BUG_HELP),
        false,
    )
}


================================================
FILE: roa/src/body/file.rs
================================================
mod content_disposition;
mod help;

use std::convert::TryInto;
use std::path::Path;

use content_disposition::ContentDisposition;
pub use content_disposition::DispositionType;
use tokio::fs::File;

use crate::{http, Context, Result, State};

/// Write file to response body then set "Content-Type" and "Context-Disposition".
#[inline]
pub async fn write_file<S: State>(
    ctx: &mut Context<S>,
    path: impl AsRef<Path>,
    typ: DispositionType,
) -> Result {
    let path = path.as_ref();
    ctx.resp.write_reader(File::open(path).await?);

    if let Some(filename) = path.file_name() {
        ctx.resp.headers.insert(
            http::header::CONTENT_TYPE,
            mime_guess::from_path(&filename)
                .first_or_octet_stream()
                .as_ref()
                .parse()
                .map_err(help::bug_report)?,
        );

        let name = filename.to_string_lossy();
        let content_disposition = ContentDisposition::new(typ, Some(&name));
        ctx.resp.headers.insert(
            http::header::CONTENT_DISPOSITION,
            content_disposition.try_into()?,
        );
    }
    Ok(())
}


================================================
FILE: roa/src/body.rs
================================================
//! This module provides a context extension `PowerBody`.
//!
//! ### Read/write body in a easier way.
//!
//! The `roa_core` provides several methods to read/write body.
//!
//! ```rust
//! use roa::{Context, Result};
//! use tokio::io::AsyncReadExt;
//! use tokio::fs::File;
//!
//! async fn get(ctx: &mut Context) -> Result {
//!     let mut data = String::new();
//!     // implements futures::AsyncRead.
//!     ctx.req.reader().read_to_string(&mut data).await?;
//!     println!("data: {}", data);
//!
//!     // although body is empty now...
//!     let stream = ctx.req.stream();
//!     ctx.resp
//!         // echo
//!        .write_stream(stream)
//!        // write object implementing futures::AsyncRead
//!        .write_reader(File::open("assets/author.txt").await?)
//!        // write reader with specific chunk size
//!        .write_chunk(File::open("assets/author.txt").await?, 1024)
//!        // write text
//!        .write("I am Roa.")
//!        .write(b"I am Roa.".as_ref());
//!     Ok(())
//! }
//! ```
//!
//! These methods are useful, but they do not deal with headers and (de)serialization.
//!
//! The `PowerBody` provides more powerful methods to handle it.
//!
//! ```rust
//! use roa::{Context, Result};
//! use roa::body::{PowerBody, DispositionType::*};
//! use serde::{Serialize, Deserialize};
//! use askama::Template;
//! use tokio::fs::File;
//!
//! #[derive(Debug, Serialize, Deserialize, Template)]
//! #[template(path = "user.html")]
//! struct User {
//!     id: u64,
//!     name: String,
//! }
//!
//! async fn get(ctx: &mut Context) -> Result {
//!     // read as bytes.
//!     let data = ctx.read().await?;
//!
//!     // deserialize as json.
//!     let user: User = ctx.read_json().await?;
//!
//!     // deserialize as x-form-urlencoded.
//!     let user: User = ctx.read_form().await?;
//!
//!     // serialize object and write it to body,
//!     // set "Content-Type"
//!     ctx.write_json(&user)?;
//!
//!     // open file and write it to body,
//!     // set "Content-Type" and "Content-Disposition"
//!     ctx.write_file("assets/welcome.html", Inline).await?;
//!
//!     // write text,
//!     // set "Content-Type"
//!     ctx.write("Hello, World!");
//!
//!     // write object implementing AsyncRead,
//!     // set "Content-Type"
//!     ctx.write_reader(File::open("assets/author.txt").await?);
//!
//!     // render html template, based on [askama](https://github.com/djc/askama).
//!     // set "Content-Type"
//!     ctx.render(&user)?;
//!     Ok(())
//! }
//! ```

#[cfg(feature = "template")]
use askama::Template;
use bytes::Bytes;
use headers::{ContentLength, ContentType, HeaderMapExt};
use tokio::io::{AsyncRead, AsyncReadExt};

use crate::{async_trait, Context, Result, State};
#[cfg(feature = "file")]
mod file;
#[cfg(feature = "file")]
use file::write_file;
#[cfg(feature = "file")]
pub use file::DispositionType;
#[cfg(feature = "multipart")]
pub use multer::Multipart;
#[cfg(any(feature = "json", feature = "urlencoded"))]
use serde::de::DeserializeOwned;
#[cfg(feature = "json")]
use serde::Serialize;

/// A context extension to read/write body more simply.
#[async_trait]
pub trait PowerBody {
    /// read request body as Bytes.
    async fn read(&mut self) -> Result<Vec<u8>>;

    /// read request body as "json".
    #[cfg(feature = "json")]
    #[cfg_attr(feature = "docs", doc(cfg(feature = "json")))]
    async fn read_json<B>(&mut self) -> Result<B>
    where
        B: DeserializeOwned;

    /// read request body as "urlencoded form".
    #[cfg(feature = "urlencoded")]
    #[cfg_attr(feature = "docs", doc(cfg(feature = "urlencoded")))]
    async fn read_form<B>(&mut self) -> Result<B>
    where
        B: DeserializeOwned;

    /// read request body as "multipart form".
    #[cfg(feature = "multipart")]
    #[cfg_attr(feature = "docs", doc(cfg(feature = "multipart")))]
    async fn read_multipart(&mut self) -> Result<Multipart>;

    /// write object to response body as "application/json"
    #[cfg(feature = "json")]
    #[cfg_attr(feature = "docs", doc(cfg(feature = "json")))]
    fn write_json<B>(&mut self, data: &B) -> Result
    where
        B: Serialize;

    /// write object to response body as "text/html; charset=utf-8"
    #[cfg(feature = "template")]
    #[cfg_attr(feature = "docs", doc(cfg(feature = "template")))]
    fn render<B>(&mut self, data: &B) -> Result
    where
        B: Template;

    /// write object to response body as "text/plain"
    fn write<B>(&mut self, data: B)
    where
        B: Into<Bytes>;

    /// write object to response body as "application/octet-stream"
    fn write_reader<B>(&mut self, reader: B)
    where
        B: 'static + AsyncRead + Unpin + Sync + Send;

    /// write object to response body as extension name of file
    #[cfg(feature = "file")]
    #[cfg_attr(feature = "docs", doc(cfg(feature = "file")))]
    async fn write_file<P>(&mut self, path: P, typ: DispositionType) -> Result
    where
        P: Send + AsRef<std::path::Path>;
}

#[async_trait]
impl<S: State> PowerBody for Context<S> {
    #[inline]
    async fn read(&mut self) -> Result<Vec<u8>> {
        let mut data = match self.req.headers.typed_get::<ContentLength>() {
            Some(hint) => Vec::with_capacity(hint.0 as usize),
            None => Vec::new(),
        };
        self.req.reader().read_to_end(&mut data).await?;
        Ok(data)
    }

    #[cfg(feature = "json")]
    #[inline]
    async fn read_json<B>(&mut self) -> Result<B>
    where
        B: DeserializeOwned,
    {
        use crate::http::StatusCode;
        use crate::status;

        let data = self.read().await?;
        serde_json::from_slice(&data).map_err(|err| status!(StatusCode::BAD_REQUEST, err))
    }

    #[cfg(feature = "urlencoded")]
    #[inline]
    async fn read_form<B>(&mut self) -> Result<B>
    where
        B: DeserializeOwned,
    {
        use crate::http::StatusCode;
        use crate::status;
        let data = self.read().await?;
        serde_urlencoded::from_bytes(&data).map_err(|err| status!(StatusCode::BAD_REQUEST, err))
    }

    #[cfg(feature = "multipart")]
    async fn read_multipart(&mut self) -> Result<Multipart> {
        use headers::{ContentType, HeaderMapExt};

        use crate::http::StatusCode;

        // Verify that the request is 'Content-Type: multipart/*'.
        let typ: mime::Mime = self
            .req
            .headers
            .typed_get::<ContentType>()
            .ok_or_else(|| crate::status!(StatusCode::BAD_REQUEST, "fail to get content-type"))?
            .into();
        let boundary = typ
            .get_param(mime::BOUNDARY)
            .ok_or_else(|| crate::status!(StatusCode::BAD_REQUEST, "fail to get boundary"))?
            .as_str();
        Ok(Multipart::new(self.req.stream(), boundary))
    }

    #[cfg(feature = "json")]
    #[inline]
    fn write_json<B>(&mut self, data: &B) -> Result
    where
        B: Serialize,
    {
        self.resp.write(serde_json::to_vec(data)?);
        self.resp.headers.typed_insert(ContentType::json());
        Ok(())
    }

    #[cfg(feature = "template")]
    #[inline]
    fn render<B>(&mut self, data: &B) -> Result
    where
        B: Template,
    {
        self.resp.write(data.render()?);
        self.resp
            .headers
            .typed_insert::<ContentType>(mime::TEXT_HTML_UTF_8.into());
        Ok(())
    }

    #[inline]
    fn write<B>(&mut self, data: B)
    where
        B: Into<Bytes>,
    {
        self.resp.write(data);
        self.resp.headers.typed_insert(ContentType::text());
    }

    #[inline]
    fn write_reader<B>(&mut self, reader: B)
    where
        B: 'static + AsyncRead + Unpin + Sync + Send,
    {
        self.resp.write_reader(reader);
        self.resp.headers.typed_insert(ContentType::octet_stream());
    }

    #[cfg(feature = "file")]
    #[inline]
    async fn write_file<P>(&mut self, path: P, typ: DispositionType) -> Result
    where
        P: Send + AsRef<std::path::Path>,
    {
        write_file(self, path, typ).await
    }
}

#[cfg(all(test, feature = "tcp"))]
mod tests {
    use std::error::Error;

    use askama::Template;
    use http::header::CONTENT_TYPE;
    use http::StatusCode;
    use serde::{Deserialize, Serialize};
    use tokio::fs::File;
    use tokio::task::spawn;

    use super::PowerBody;
    use crate::tcp::Listener;
    use crate::{http, App, Context};

    #[derive(Debug, Deserialize)]
    struct UserDto {
        id: u64,
        name: String,
    }

    #[derive(Debug, Serialize, Hash, Eq, PartialEq, Clone, Template)]
    #[template(path = "user.html")]
    struct User<'a> {
        id: u64,
        name: &'a str,
    }

    impl PartialEq<UserDto> for User<'_> {
        fn eq(&self, other: &UserDto) -> bool {
            self.id == other.id && self.name == other.name
        }
    }

    #[allow(dead_code)]
    const USER: User = User {
        id: 0,
        name: "Hexilee",
    };

    #[cfg(feature = "json")]
    #[tokio::test]
    async fn read_json() -> Result<(), Box<dyn Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            let user: UserDto = ctx.read_json().await?;
            assert_eq!(USER, user);
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);

        let client = reqwest::Client::new();
        let resp = client
            .get(&format!("http://{}", addr))
            .json(&USER)
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[cfg(feature = "urlencoded")]
    #[tokio::test]
    async fn read_form() -> Result<(), Box<dyn Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            let user: UserDto = ctx.read_form().await?;
            assert_eq!(USER, user);
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);

        let client = reqwest::Client::new();
        let resp = client
            .get(&format!("http://{}", addr))
            .form(&USER)
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[cfg(feature = "template")]
    #[tokio::test]
    async fn render() -> Result<(), Box<dyn Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            ctx.render(&USER)
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert_eq!("text/html; charset=utf-8", resp.headers()[CONTENT_TYPE]);
        Ok(())
    }

    #[tokio::test]
    async fn write() -> Result<(), Box<dyn Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            ctx.write("Hello, World!");
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert_eq!("text/plain", resp.headers()[CONTENT_TYPE]);
        assert_eq!("Hello, World!", resp.text().await?);
        Ok(())
    }

    #[tokio::test]
    async fn write_octet() -> Result<(), Box<dyn Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            ctx.write_reader(File::open("../assets/author.txt").await?);
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert_eq!(
            mime::APPLICATION_OCTET_STREAM.as_ref(),
            resp.headers()[CONTENT_TYPE]
        );
        assert_eq!("Hexilee", resp.text().await?);
        Ok(())
    }

    #[cfg(feature = "multipart")]
    mod multipart {
        use std::error::Error as StdError;

        use reqwest::multipart::{Form, Part};
        use reqwest::Client;
        use tokio::fs::read;

        use crate::body::PowerBody;
        use crate::http::header::CONTENT_TYPE;
        use crate::http::StatusCode;
        use crate::router::{post, Router};
        use crate::tcp::Listener;
        use crate::{throw, App, Context};

        const FILE_PATH: &str = "../assets/author.txt";
        const FILE_NAME: &str = "author.txt";
        const FIELD_NAME: &str = "file";

        async fn post_file(ctx: &mut Context) -> crate::Result {
            let mut form = ctx.read_multipart().await?;
            while let Some(field) = form.next_field().await? {
                match (field.file_name(), field.name()) {
                    (Some(filename), Some(name)) => {
                        assert_eq!(FIELD_NAME, name);
                        assert_eq!(FILE_NAME, filename);
                        let content = field.bytes().await?;
                        let expected_content = read(FILE_PATH).await?;
                        assert_eq!(&expected_content, &content);
                    }
                    _ => throw!(
                        StatusCode::BAD_REQUEST,
                        format!("invalid field: {:?}", field)
                    ),
                }
            }
            Ok(())
        }

        #[tokio::test]
        async fn upload() -> Result<(), Box<dyn StdError>> {
            let router = Router::new().on("/file", post(post_file));
            let app = App::new().end(router.routes("/")?);
            let (addr, server) = app.run()?;
            tokio::task::spawn(server);

            // client
            let url = format!("http://{}/file", addr);
            let client = Client::new();
            let form = Form::new().part(
                FIELD_NAME,
                Part::bytes(read(FILE_PATH).await?).file_name(FILE_NAME),
            );
            let boundary = form.boundary().to_string();
            let resp = client
                .post(&url)
                .multipart(form)
                .header(
                    CONTENT_TYPE,
                    format!(r#"multipart/form-data; boundary="{}""#, boundary),
                )
                .send()
                .await?;
            assert_eq!(StatusCode::OK, resp.status());
            Ok(())
        }
    }
}


================================================
FILE: roa/src/compress.rs
================================================
//! This module provides a middleware `Compress`.
//!
//! ### Example
//!
//! ```rust
//! use roa::compress::{Compress, Level};
//! use roa::body::DispositionType::*;
//! use roa::{App, Context};
//! use roa::preload::*;
//! use std::error::Error;
//!
//! async fn end(ctx: &mut Context) -> roa::Result {
//!     ctx.write_file("../assets/welcome.html", Inline).await
//! }
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn Error>> {
//! let mut app = App::new().gate(Compress(Level::Fastest)).end(end);
//! let (addr, server) = app.run()?;
//! // server.await
//! Ok(())
//! # }
//! ```

use async_compression::tokio::bufread::{BrotliEncoder, GzipEncoder, ZlibEncoder, ZstdEncoder};
pub use async_compression::Level;
use tokio_util::io::StreamReader;

use crate::http::header::{HeaderMap, ACCEPT_ENCODING, CONTENT_ENCODING};
use crate::http::{HeaderValue, StatusCode};
use crate::{async_trait, status, Context, Middleware, Next, Result};

/// A middleware to negotiate with client and compress response body automatically,
/// supports gzip, deflate, brotli, zstd and identity.
#[derive(Debug, Copy, Clone)]
pub struct Compress(pub Level);

/// Encodings to use.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum Encoding {
    /// The Gzip encoding.
    Gzip,
    /// The Deflate encoding.
    Deflate,
    /// The Brotli encoding.
    Brotli,
    /// The Zstd encoding.
    Zstd,
    /// No encoding.
    Identity,
}

impl Encoding {
    /// Parses a given string into its corresponding encoding.
    fn parse(s: &str) -> Result<Option<Encoding>> {
        match s {
            "gzip" => Ok(Some(Encoding::Gzip)),
            "deflate" => Ok(Some(Encoding::Deflate)),
            "br" => Ok(Some(Encoding::Brotli)),
            "zstd" => Ok(Some(Encoding::Zstd)),
            "identity" => Ok(Some(Encoding::Identity)),
            "*" => Ok(None),
            _ => Err(status!(
                StatusCode::BAD_REQUEST,
                format!("unknown encoding: {}", s),
                true
            )),
        }
    }

    /// Converts the encoding into its' corresponding header value.
    fn to_header_value(self) -> HeaderValue {
        match self {
            Encoding::Gzip => HeaderValue::from_str("gzip").unwrap(),
            Encoding::Deflate => HeaderValue::from_str("deflate").unwrap(),
            Encoding::Brotli => HeaderValue::from_str("br").unwrap(),
            Encoding::Zstd => HeaderValue::from_str("zstd").unwrap(),
            Encoding::Identity => HeaderValue::from_str("identity").unwrap(),
        }
    }
}

fn select_encoding(headers: &HeaderMap) -> Result<Option<Encoding>> {
    let mut preferred_encoding = None;
    let mut max_qval = 0.0;

    for (encoding, qval) in accept_encodings(headers)? {
        if qval > max_qval {
            preferred_encoding = encoding;
            max_qval = qval;
        }
    }
    Ok(preferred_encoding)
}

/// Parse a set of HTTP headers into a vector containing tuples of options containing encodings and their corresponding q-values.
///
/// If you're looking for more fine-grained control over what encoding to choose for the client, or if you don't support every [`Encoding`] listed, this is likely what you want.
///
/// Note that a result of `None` indicates there preference is expressed on which encoding to use.
/// Either the `Accept-Encoding` header is not present, or `*` is set as the most preferred encoding.
fn accept_encodings(headers: &HeaderMap) -> Result<Vec<(Option<Encoding>, f32)>> {
    headers
        .get_all(ACCEPT_ENCODING)
        .iter()
        .map(|hval| {
            hval.to_str()
                .map_err(|err| status!(StatusCode::BAD_REQUEST, err, true))
        })
        .collect::<Result<Vec<&str>>>()?
        .iter()
        .flat_map(|s| s.split(',').map(str::trim))
        .filter_map(|v| {
            let pair: Vec<&str> = v.splitn(2, ";q=").collect();
            if pair.is_empty() {
                return None;
            }

            let encoding = match Encoding::parse(pair[0]) {
                Ok(encoding) => encoding,
                Err(_) => return None, // ignore unknown encodings
            };

            let qval = if pair.len() == 1 {
                1.0
            } else {
                match pair[1].parse::<f32>() {
                    Ok(f) => f,
                    Err(err) => return Some(Err(status!(StatusCode::BAD_REQUEST, err, true))),
                }
            };
            Some(Ok((encoding, qval)))
        })
        .collect::<Result<Vec<(Option<Encoding>, f32)>>>()
}

impl Default for Compress {
    fn default() -> Self {
        Self(Level::Default)
    }
}

#[async_trait(?Send)]
impl<'a, S> Middleware<'a, S> for Compress {
    #[allow(clippy::trivially_copy_pass_by_ref)]
    #[inline]
    async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> Result {
        next.await?;
        let level = self.0;
        let best_encoding = select_encoding(&ctx.req.headers)?;
        let body = std::mem::take(&mut ctx.resp.body);
        let content_encoding = match best_encoding {
            None | Some(Encoding::Gzip) => {
                ctx.resp
                    .write_reader(GzipEncoder::with_quality(StreamReader::new(body), level));
                Encoding::Gzip.to_header_value()
            }
            Some(Encoding::Deflate) => {
                ctx.resp
                    .write_reader(ZlibEncoder::with_quality(StreamReader::new(body), level));
                Encoding::Deflate.to_header_value()
            }
            Some(Encoding::Brotli) => {
                ctx.resp
                    .write_reader(BrotliEncoder::with_quality(StreamReader::new(body), level));
                Encoding::Brotli.to_header_value()
            }
            Some(Encoding::Zstd) => {
                ctx.resp
                    .write_reader(ZstdEncoder::with_quality(StreamReader::new(body), level));
                Encoding::Zstd.to_header_value()
            }
            Some(Encoding::Identity) => {
                ctx.resp.body = body;
                Encoding::Identity.to_header_value()
            }
        };
        ctx.resp.headers.append(CONTENT_ENCODING, content_encoding);
        Ok(())
    }
}

#[cfg(all(test, feature = "tcp", feature = "file"))]
mod tests {
    use std::io;
    use std::pin::Pin;
    use std::task::{self, Poll};

    use bytes::Bytes;
    use futures::Stream;
    use tokio::task::spawn;

    use crate::body::DispositionType::*;
    use crate::compress::{Compress, Level};
    use crate::http::header::ACCEPT_ENCODING;
    use crate::http::StatusCode;
    use crate::preload::*;
    use crate::{async_trait, App, Context, Middleware, Next};

    struct Consumer<S> {
        counter: usize,
        stream: S,
        assert_counter: usize,
    }
    impl<S> Stream for Consumer<S>
    where
        S: 'static + Send + Send + Unpin + Stream<Item = io::Result<Bytes>>,
    {
        type Item = io::Result<Bytes>;
        fn poll_next(
            mut self: Pin<&mut Self>,
            cx: &mut task::Context<'_>,
        ) -> Poll<Option<Self::Item>> {
            match Pin::new(&mut self.stream).poll_next(cx) {
                Poll::Ready(Some(Ok(bytes))) => {
                    self.counter += bytes.len();
                    Poll::Ready(Some(Ok(bytes)))
                }
                Poll::Ready(None) => {
                    assert_eq!(self.assert_counter, self.counter);
                    Poll::Ready(None)
                }
                poll => poll,
            }
        }
    }

    struct Assert(usize);

    #[async_trait(?Send)]
    impl<'a, S> Middleware<'a, S> for Assert {
        async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> crate::Result {
            next.await?;
            let body = std::mem::take(&mut ctx.resp.body);
            ctx.resp.write_stream(Consumer {
                counter: 0,
                stream: body,
                assert_counter: self.0,
            });
            Ok(())
        }
    }

    async fn end(ctx: &mut Context) -> crate::Result {
        ctx.write_file("../assets/welcome.html", Inline).await
    }

    #[tokio::test]
    async fn compress() -> Result<(), Box<dyn std::error::Error>> {
        let app = App::new()
            .gate(Assert(202)) // compressed to 202 bytes
            .gate(Compress(Level::Fastest))
            .gate(Assert(236)) // the size of assets/welcome.html is 236 bytes.
            .end(end);
        let (addr, server) = app.run()?;
        spawn(server);
        let client = reqwest::Client::builder().gzip(true).build()?;
        let resp = client
            .get(&format!("http://{}", addr))
            .header(ACCEPT_ENCODING, "gzip")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert_eq!(236, resp.text().await?.len());
        Ok(())
    }
}


================================================
FILE: roa/src/cookie.rs
================================================
//! This module provides a middleware `cookie_parser` and context extensions `CookieGetter` and `CookieSetter`.
//!
//! ### Example
//!
//! ```rust
//! use roa::cookie::cookie_parser;
//! use roa::preload::*;
//! use roa::{App, Context};
//! use std::error::Error;
//!
//! async fn end(ctx: &mut Context) -> roa::Result {
//!     assert_eq!("Hexilee", ctx.must_cookie("name")?.value());
//!     Ok(())
//! }
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn Error>> {
//! let app = App::new().gate(cookie_parser).end(end);
//! let (addr, server) = app.run()?;
//! // server.await
//! Ok(())
//! # }
//! ```

use std::sync::Arc;

pub use cookie::Cookie;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};

use crate::http::{header, StatusCode};
use crate::{throw, Context, Next, Result};

/// A scope to store and load variables in Context::storage.
struct CookieScope;

/// A context extension.
/// This extension must be used in downstream of middleware `cookier_parser`,
/// otherwise you cannot get expected cookie.
///
/// Percent-encoded cookies will be decoded.
/// ### Example
///
/// ```rust
/// use roa::cookie::cookie_parser;
/// use roa::preload::*;
/// use roa::{App, Context};
/// use std::error::Error;
///
/// async fn end(ctx: &mut Context) -> roa::Result {
///     assert_eq!("Hexilee", ctx.must_cookie("name")?.value());
///     Ok(())
/// }
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// let app = App::new().gate(cookie_parser).end(end);
/// let (addr, server) = app.run()?;
/// // server.await
/// Ok(())
/// # }
/// ```
pub trait CookieGetter {
    /// Must get a cookie, throw 401 UNAUTHORIZED if it not exists.
    fn must_cookie(&mut self, name: &str) -> Result<Arc<Cookie<'static>>>;

    /// Try to get a cookie, return `None` if it not exists.
    ///
    /// ### Example
    ///
    /// ```rust
    /// use roa::cookie::cookie_parser;
    /// use roa::preload::*;
    /// use roa::{App, Context};
    /// use std::error::Error;
    ///
    /// async fn end(ctx: &mut Context) -> roa::Result {
    ///     assert!(ctx.cookie("name").is_none());
    ///     Ok(())
    /// }
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn Error>> {
    /// let app = App::new().gate(cookie_parser).end(end);
    /// let (addr, server) = app.run()?;
    /// // server.await
    /// Ok(())
    /// # }
    /// ```
    fn cookie(&self, name: &str) -> Option<Arc<Cookie<'static>>>;
}

/// An extension to set cookie.
pub trait CookieSetter {
    /// Set a cookie in pecent encoding, should not return Err.
    /// ### Example
    ///
    /// ```rust
    /// use roa::cookie::{cookie_parser, Cookie};
    /// use roa::preload::*;
    /// use roa::{App, Context};
    /// use std::error::Error;
    ///
    /// async fn end(ctx: &mut Context) -> roa::Result {
    ///     ctx.set_cookie(Cookie::new("name", "Hexilee"));
    ///     Ok(())
    /// }
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn Error>> {
    /// let app = App::new().gate(cookie_parser).end(end);
    /// let (addr, server) = app.run()?;
    /// // server.await
    /// Ok(())
    /// # }
    /// ```
    fn set_cookie(&mut self, cookie: Cookie<'_>) -> Result;
}

/// A middleware to parse cookie.
#[inline]
pub async fn cookie_parser<S>(ctx: &mut Context<S>, next: Next<'_>) -> Result {
    if let Some(cookies) = ctx.get(header::COOKIE) {
        for cookie in cookies
            .split(';')
            .map(|cookie| cookie.trim())
            .map(Cookie::parse_encoded)
            .filter_map(|cookie| cookie.ok())
            .map(|cookie| cookie.into_owned())
            .collect::<Vec<_>>()
            .into_iter()
        {
            let name = cookie.name().to_string();
            ctx.store_scoped(CookieScope, name, cookie);
        }
    }
    next.await
}

impl<S> CookieGetter for Context<S> {
    #[inline]
    fn must_cookie(&mut self, name: &str) -> Result<Arc<Cookie<'static>>> {
        match self.cookie(name) {
            Some(value) => Ok(value),
            None => {
                self.resp.headers.insert(
                    header::WWW_AUTHENTICATE,
                    format!(
                        r#"Cookie name="{}""#,
                        utf8_percent_encode(name, NON_ALPHANUMERIC)
                    )
                    .parse()?,
                );
                throw!(StatusCode::UNAUTHORIZED)
            }
        }
    }

    #[inline]
    fn cookie(&self, name: &str) -> Option<Arc<Cookie<'static>>> {
        Some(self.load_scoped::<CookieScope, Cookie>(name)?.value())
    }
}

impl<S> CookieSetter for Context<S> {
    #[inline]
    fn set_cookie(&mut self, cookie: Cookie<'_>) -> Result {
        let cookie_value = cookie.encoded().to_string();
        self.resp
            .headers
            .append(header::SET_COOKIE, cookie_value.parse()?);
        Ok(())
    }
}

#[cfg(all(test, feature = "tcp"))]
mod tests {
    use tokio::task::spawn;

    use crate::cookie::{cookie_parser, Cookie};
    use crate::http::header::{COOKIE, WWW_AUTHENTICATE};
    use crate::http::StatusCode;
    use crate::preload::*;
    use crate::{App, Context};

    async fn must(ctx: &mut Context) -> crate::Result {
        assert_eq!("Hexi Lee", ctx.must_cookie("nick name")?.value());
        Ok(())
    }

    async fn none(ctx: &mut Context) -> crate::Result {
        assert!(ctx.cookie("nick name").is_none());
        Ok(())
    }

    #[tokio::test]
    async fn parser() -> Result<(), Box<dyn std::error::Error>> {
        // downstream of `cookie_parser`
        let (addr, server) = App::new().gate(cookie_parser).end(must).run()?;
        spawn(server);
        let client = reqwest::Client::new();
        let resp = client
            .get(&format!("http://{}", addr))
            .header(COOKIE, "nick%20name=Hexi%20Lee")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());

        // miss `cookie_parser`
        let (addr, server) = App::new().end(must).run()?;
        spawn(server);
        let resp = client
            .get(&format!("http://{}", addr))
            .header(COOKIE, "nick%20name=Hexi%20Lee")
            .send()
            .await?;
        assert_eq!(StatusCode::UNAUTHORIZED, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn cookie() -> Result<(), Box<dyn std::error::Error>> {
        // miss cookie
        let (addr, server) = App::new().end(none).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());

        let (addr, server) = App::new().gate(cookie_parser).end(must).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::UNAUTHORIZED, resp.status());
        assert_eq!(
            r#"Cookie name="nick%20name""#,
            resp.headers()
                .get(WWW_AUTHENTICATE)
                .unwrap()
                .to_str()
                .unwrap()
        );

        // string value
        let (addr, server) = App::new().gate(cookie_parser).end(must).run()?;
        spawn(server);
        let client = reqwest::Client::new();
        let resp = client
            .get(&format!("http://{}", addr))
            .header(COOKIE, "nick%20name=Hexi%20Lee")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn cookie_action() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            assert_eq!("bar baz", ctx.must_cookie("bar baz")?.value());
            assert_eq!("bar foo", ctx.must_cookie("foo baz")?.value());
            Ok(())
        }

        let (addr, server) = App::new().gate(cookie_parser).end(test).run()?;
        spawn(server);
        let client = reqwest::Client::new();
        let resp = client
            .get(&format!("http://{}", addr))
            .header(COOKIE, "bar%20baz=bar%20baz; foo%20baz=bar%20foo")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn set_cookie() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            ctx.set_cookie(Cookie::new("bar baz", "bar baz"))?;
            ctx.set_cookie(Cookie::new("bar foo", "foo baz"))?;
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        let cookies: Vec<reqwest::cookie::Cookie> = resp.cookies().collect();
        assert_eq!(2, cookies.len());
        assert_eq!(("bar%20baz"), cookies[0].name());
        assert_eq!(("bar%20baz"), cookies[0].value());
        assert_eq!(("bar%20foo"), cookies[1].name());
        assert_eq!(("foo%20baz"), cookies[1].value());
        Ok(())
    }
}


================================================
FILE: roa/src/cors.rs
================================================
//! This module provides a middleware `Cors`.

use std::collections::HashSet;
use std::convert::TryInto;
use std::fmt::Debug;
use std::iter::FromIterator;
use std::time::Duration;

use headers::{
    AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowMethods,
    AccessControlAllowOrigin, AccessControlExposeHeaders, AccessControlMaxAge,
    AccessControlRequestHeaders, AccessControlRequestMethod, Header, HeaderMapExt,
};
use roa_core::Status;

use crate::http::header::{HeaderName, HeaderValue, ORIGIN, VARY};
use crate::http::{Method, StatusCode};
use crate::{async_trait, Context, Middleware, Next, Result};

/// A middleware to deal with Cross-Origin Resource Sharing (CORS).
///
/// ### Default
///
/// The default Cors middleware will satisfy all needs of a request.
///
/// Build a default Cors middleware:
///
/// ```rust
/// use roa::cors::Cors;
///
/// let default_cors = Cors::new();
/// ```
///
/// ### Config
///
/// You can also configure it:
///
/// ```rust
/// use roa::cors::Cors;
/// use roa::http::header::{CONTENT_DISPOSITION, AUTHORIZATION, WWW_AUTHENTICATE};
/// use roa::http::Method;
///
/// let cors = Cors::builder()
///     .allow_credentials(true)
///     .max_age(86400)
///     .allow_origin("https://github.com")
///     .allow_methods(vec![Method::GET, Method::POST])
///     .allow_method(Method::PUT)
///     .expose_headers(vec![CONTENT_DISPOSITION])
///     .expose_header(WWW_AUTHENTICATE)
///     .allow_headers(vec![AUTHORIZATION])
///     .allow_header(CONTENT_DISPOSITION)
///     .build();
/// ```
#[derive(Debug, Default)]
pub struct Cors {
    allow_origin: Option<AccessControlAllowOrigin>,
    allow_methods: Option<AccessControlAllowMethods>,
    expose_headers: Option<AccessControlExposeHeaders>,
    allow_headers: Option<AccessControlAllowHeaders>,
    max_age: Option<AccessControlMaxAge>,
    credentials: Option<AccessControlAllowCredentials>,
}

/// Builder of Cors.
#[derive(Clone, Debug, Default)]
pub struct Builder {
    credentials: bool,
    allowed_headers: HashSet<HeaderName>,
    exposed_headers: HashSet<HeaderName>,
    max_age: Option<u64>,
    methods: HashSet<Method>,
    origins: Option<HeaderValue>,
}

impl Cors {
    /// Construct default Cors.
    pub fn new() -> Self {
        Self::default()
    }

    /// Get builder.
    pub fn builder() -> Builder {
        Builder::default()
    }
}

impl Builder {
    /// Sets whether to add the `Access-Control-Allow-Credentials` header.
    pub fn allow_credentials(mut self, allow: bool) -> Self {
        self.credentials = allow;
        self
    }

    /// Adds a method to the existing list of allowed request methods.
    pub fn allow_method(mut self, method: Method) -> Self {
        self.methods.insert(method);
        self
    }

    /// Adds multiple methods to the existing list of allowed request methods.
    pub fn allow_methods(mut self, methods: impl IntoIterator<Item = Method>) -> Self {
        self.methods.extend(methods);
        self
    }

    /// Adds a header to the list of allowed request headers.
    ///
    /// # Panics
    ///
    /// Panics if header is not a valid `http::header::HeaderName`.
    pub fn allow_header<H>(mut self, header: H) -> Self
    where
        H: TryInto<HeaderName>,
        H::Error: Debug,
    {
        self.allowed_headers
            .insert(header.try_into().expect("invalid header"));
        self
    }

    /// Adds multiple headers to the list of allowed request headers.
    ///
    /// # Panics
    ///
    /// Panics if any of the headers are not a valid `http::header::HeaderName`.
    pub fn allow_headers<I>(mut self, headers: I) -> Self
    where
        I: IntoIterator,
        I::Item: TryInto<HeaderName>,
        <I::Item as TryInto<HeaderName>>::Error: Debug,
    {
        let iter = headers
            .into_iter()
            .map(|h| h.try_into().expect("invalid header"));
        self.allowed_headers.extend(iter);
        self
    }

    /// Adds a header to the list of exposed headers.
    ///
    /// # Panics
    ///
    /// Panics if the provided argument is not a valid `http::header::HeaderName`.
    pub fn expose_header<H>(mut self, header: H) -> Self
    where
        H: TryInto<HeaderName>,
        H::Error: Debug,
    {
        self.exposed_headers
            .insert(header.try_into().expect("illegal Header"));
        self
    }

    /// Adds multiple headers to the list of exposed headers.
    ///
    /// # Panics
    ///
    /// Panics if any of the headers are not a valid `http::header::HeaderName`.
    pub fn expose_headers<I>(mut self, headers: I) -> Self
    where
        I: IntoIterator,
        I::Item: TryInto<HeaderName>,
        <I::Item as TryInto<HeaderName>>::Error: Debug,
    {
        let iter = headers
            .into_iter()
            .map(|h| h.try_into().expect("illegal Header"));
        self.exposed_headers.extend(iter);
        self
    }

    /// Add an origin to the existing list of allowed `Origin`s.
    ///
    /// # Panics
    ///
    /// Panics if the provided argument is not a valid `HeaderValue`.
    pub fn allow_origin<H>(mut self, origin: H) -> Self
    where
        H: TryInto<HeaderValue>,
        H::Error: Debug,
    {
        self.origins = Some(origin.try_into().expect("invalid origin"));
        self
    }

    /// Sets the `Access-Control-Max-Age` header.
    pub fn max_age(mut self, seconds: u64) -> Self {
        self.max_age = Some(seconds);
        self
    }

    /// Builds the `Cors` wrapper from the configured settings.
    ///
    /// This step isn't *required*, as the `Builder` itself can be passed
    /// to `Filter::with`. This just allows constructing once, thus not needing
    /// to pay the cost of "building" every time.
    pub fn build(self) -> Cors {
        let Builder {
            allowed_headers,
            credentials,
            exposed_headers,
            max_age,
            origins,
            methods,
        } = self;
        let mut cors = Cors::default();
        if !allowed_headers.is_empty() {
            cors.allow_headers = Some(AccessControlAllowHeaders::from_iter(allowed_headers))
        }

        if credentials {
            cors.credentials = Some(AccessControlAllowCredentials)
        }

        if !exposed_headers.is_empty() {
            cors.expose_headers = Some(AccessControlExposeHeaders::from_iter(exposed_headers))
        }

        if let Some(age) = max_age {
            cors.max_age = Some(Duration::from_secs(age).into())
        }

        if origins.is_some() {
            cors.allow_origin = Some(
                AccessControlAllowOrigin::decode(&mut origins.iter()).expect("invalid origins"),
            );
        }

        if !methods.is_empty() {
            cors.allow_methods = Some(AccessControlAllowMethods::from_iter(methods))
        }

        cors
    }
}

#[async_trait(?Send)]
impl<'a, S> Middleware<'a, S> for Cors {
    #[inline]
    async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> Result {
        // Always set Vary header
        // https://github.com/rs/cors/issues/10
        ctx.resp.headers.append(VARY, ORIGIN.into());

        let origin = match ctx.req.headers.get(ORIGIN) {
            // If there is no Origin header, skip this middleware.
            None => return next.await,
            Some(origin) => AccessControlAllowOrigin::decode(&mut Some(origin).into_iter())
                .map_err(|err| {
                    Status::new(
                        StatusCode::BAD_REQUEST,
                        format!("invalid origin: {}", err),
                        true,
                    )
                })?,
        };

        // If Options::allow_origin is None, `Access-Control-Allow-Origin` will be set to `Origin`.
        let allow_origin = self.allow_origin.clone().unwrap_or(origin);

        let credentials = self.credentials.clone();
        let insert_origin_and_credentials = move |ctx: &mut Context<S>| {
            // Set "Access-Control-Allow-Origin"
            ctx.resp.headers.typed_insert(allow_origin);

            // Try to set "Access-Control-Allow-Credentials"
            if let Some(credentials) = credentials {
                ctx.resp.headers.typed_insert(credentials);
            }
        };

        if ctx.method() != Method::OPTIONS {
            // Simple Request

            insert_origin_and_credentials(ctx);

            // Set "Access-Control-Expose-Headers"
            if let Some(ref exposed_headers) = self.expose_headers {
                ctx.resp.headers.typed_insert(exposed_headers.clone());
            }
            next.await
        } else {
            // Preflight Request

            let request_method = match ctx.req.headers.typed_get::<AccessControlRequestMethod>() {
                // If there is no Origin header or if parsing failed, skip this middleware.
                None => return next.await,
                Some(request_method) => request_method,
            };

            // If Options::allow_methods is None, `Access-Control-Allow-Methods` will be set to `Access-Control-Request-Method`.
            let allow_methods = match self.allow_methods {
                Some(ref origin) => origin.clone(),
                None => AccessControlAllowMethods::from_iter(Some(request_method.into())),
            };

            // Try to set "Access-Control-Allow-Methods"
            ctx.resp.headers.typed_insert(allow_methods);

            insert_origin_and_credentials(ctx);

            // Set "Access-Control-Max-Age"
            if let Some(ref max_age) = self.max_age {
                ctx.resp.headers.typed_insert(max_age.clone());
            }

            // If allow_headers is None, try to assign `Access-Control-Request-Headers` to `Access-Control-Allow-Headers`.
            let allow_headers = self.allow_headers.clone().or_else(|| {
                ctx.req
                    .headers
                    .typed_get::<AccessControlRequestHeaders>()
                    .map(|headers| headers.iter().collect())
            });
            if let Some(headers) = allow_headers {
                ctx.resp.headers.typed_insert(headers);
            };

            ctx.resp.status = StatusCode::NO_CONTENT;
            Ok(())
        }
    }
}

#[cfg(all(test, feature = "tcp"))]
mod tests {
    use headers::{
        AccessControlAllowCredentials, AccessControlAllowOrigin, AccessControlExposeHeaders,
        HeaderMapExt, HeaderName,
    };
    use tokio::task::spawn;

    use super::Cors;
    use crate::http::header::{
        ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
        ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE,
        ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AUTHORIZATION,
        CONTENT_DISPOSITION, CONTENT_TYPE, ORIGIN, VARY, WWW_AUTHENTICATE,
    };
    use crate::http::{HeaderValue, Method, StatusCode};
    use crate::preload::*;
    use crate::{App, Context};

    async fn end(ctx: &mut Context) -> crate::Result {
        ctx.resp.write("Hello, World");
        Ok(())
    }

    #[tokio::test]
    async fn default_cors() -> Result<(), Box<dyn std::error::Error>> {
        let (addr, server) = App::new().gate(Cors::new()).end(end).run()?;
        spawn(server);
        let client = reqwest::Client::new();

        // No origin
        let resp = client.get(&format!("http://{}", addr)).send().await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert!(resp
            .headers()
            .typed_get::<AccessControlAllowOrigin>()
            .is_none());
        assert_eq!(
            HeaderValue::from_name(ORIGIN),
            resp.headers().get(VARY).unwrap()
        );
        assert_eq!("Hello, World", resp.text().await?);

        // invalid origin
        let resp = client
            .get(&format!("http://{}", addr))
            .header(ORIGIN, "github.com")
            .send()
            .await?;
        assert_eq!(StatusCode::BAD_REQUEST, resp.status());

        // simple request
        let resp = client
            .get(&format!("http://{}", addr))
            .header(ORIGIN, "http://github.com")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());

        let allow_origin = resp
            .headers()
            .typed_get::<AccessControlAllowOrigin>()
            .unwrap();
        let origin = allow_origin.origin().unwrap();
        assert_eq!("http", origin.scheme());
        assert_eq!("github.com", origin.hostname());
        assert!(origin.port().is_none());
        assert!(resp
            .headers()
            .typed_get::<AccessControlAllowCredentials>()
            .is_none());

        assert!(resp
            .headers()
            .typed_get::<AccessControlExposeHeaders>()
            .is_none());

        assert_eq!("Hello, World", resp.text().await?);

        // options, no Access-Control-Request-Method
        let resp = client
            .request(Method::OPTIONS, &format!("http://{}", addr))
            .header(ORIGIN, "http://github.com")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert!(resp.headers().get(ACCESS_CONTROL_ALLOW_ORIGIN).is_none());
        assert_eq!(
            HeaderValue::from_name(ORIGIN),
            resp.headers().get(VARY).unwrap()
        );
        assert_eq!("Hello, World", resp.text().await?);

        // options, contains Access-Control-Request-Method
        let resp = client
            .request(Method::OPTIONS, &format!("http://{}", addr))
            .header(ORIGIN, "http://github.com")
            .header(ACCESS_CONTROL_REQUEST_METHOD, "POST")
            .header(
                ACCESS_CONTROL_REQUEST_HEADERS,
                HeaderValue::from_name(CONTENT_TYPE),
            )
            .send()
            .await?;
        assert_eq!(StatusCode::NO_CONTENT, resp.status());
        assert_eq!(
            "http://github.com",
            resp.headers()
                .get(ACCESS_CONTROL_ALLOW_ORIGIN)
                .unwrap()
                .to_str()?
        );
        assert!(resp
            .headers()
            .get(ACCESS_CONTROL_ALLOW_CREDENTIALS)
            .is_none());

        assert!(resp.headers().get(ACCESS_CONTROL_MAX_AGE).is_none());

        assert_eq!(
            "POST",
            resp.headers()
                .get(ACCESS_CONTROL_ALLOW_METHODS)
                .unwrap()
                .to_str()?
        );

        assert_eq!(
            HeaderValue::from_name(CONTENT_TYPE),
            resp.headers().get(ACCESS_CONTROL_ALLOW_HEADERS).unwrap()
        );
        assert_eq!("", resp.text().await?);
        //
        Ok(())
    }

    #[tokio::test]
    async fn configured_cors() -> Result<(), Box<dyn std::error::Error>> {
        let configured_cors = Cors::builder()
            .allow_credentials(true)
            .max_age(86400)
            .allow_origin("https://github.com")
            .allow_methods(vec![Method::GET, Method::POST])
            .allow_method(Method::PUT)
            .expose_headers(vec![CONTENT_DISPOSITION])
            .expose_header(WWW_AUTHENTICATE)
            .allow_headers(vec![AUTHORIZATION])
            .allow_header(CONTENT_TYPE)
            .build();
        let (addr, server) = App::new().gate(configured_cors).end(end).run()?;
        spawn(server);
        let client = reqwest::Client::new();

        // No origin
        let resp = client.get(&format!("http://{}", addr)).send().await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert!(resp
            .headers()
            .typed_get::<AccessControlAllowOrigin>()
            .is_none());
        assert_eq!(
            HeaderValue::from_name(ORIGIN),
            resp.headers().get(VARY).unwrap()
        );
        assert_eq!("Hello, World", resp.text().await?);

        // invalid origin
        let resp = client
            .get(&format!("http://{}", addr))
            .header(ORIGIN, "github.com")
            .send()
            .await?;
        assert_eq!(StatusCode::BAD_REQUEST, resp.status());

        // simple request
        let resp = client
            .get(&format!("http://{}", addr))
            .header(ORIGIN, "http://github.io")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());

        let allow_origin = resp
            .headers()
            .typed_get::<AccessControlAllowOrigin>()
            .unwrap();
        let origin = allow_origin.origin().unwrap();
        assert_eq!("https", origin.scheme());
        assert_eq!("github.com", origin.hostname());
        assert!(origin.port().is_none());
        assert!(resp
            .headers()
            .typed_get::<AccessControlAllowCredentials>()
            .is_some());

        let expose_headers = resp
            .headers()
            .typed_get::<AccessControlExposeHeaders>()
            .unwrap();

        let headers = expose_headers.iter().collect::<Vec<HeaderName>>();
        assert!(headers.contains(&CONTENT_DISPOSITION));
        assert!(headers.contains(&WWW_AUTHENTICATE));

        assert_eq!("Hello, World", resp.text().await?);

        // options, no Access-Control-Request-Method
        let resp = client
            .request(Method::OPTIONS, &format!("http://{}", addr))
            .header(ORIGIN, "http://github.com")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        assert!(resp.headers().get(ACCESS_CONTROL_ALLOW_ORIGIN).is_none());
        assert_eq!(
            HeaderValue::from_name(ORIGIN),
            resp.headers().get(VARY).unwrap()
        );
        assert_eq!("Hello, World", resp.text().await?);

        // options, contains Access-Control-Request-Method
        let resp = client
            .request(Method::OPTIONS, &format!("http://{}", addr))
            .header(ORIGIN, "http://github.io")
            .header(ACCESS_CONTROL_REQUEST_METHOD, "POST")
            .header(
                ACCESS_CONTROL_REQUEST_HEADERS,
                HeaderValue::from_name(CONTENT_TYPE),
            )
            .send()
            .await?;
        assert_eq!(StatusCode::NO_CONTENT, resp.status());
        assert_eq!(
            "https://github.com",
            resp.headers()
                .get(ACCESS_CONTROL_ALLOW_ORIGIN)
                .unwrap()
                .to_str()?
        );
        assert_eq!(
            "true",
            resp.headers()
                .get(ACCESS_CONTROL_ALLOW_CREDENTIALS)
                .unwrap()
                .to_str()?
        );

        assert_eq!("86400", resp.headers().get(ACCESS_CONTROL_MAX_AGE).unwrap());

        let allow_methods = resp
            .headers()
            .get(ACCESS_CONTROL_ALLOW_METHODS)
            .unwrap()
            .to_str()?;
        assert!(allow_methods.contains("POST"));
        assert!(allow_methods.contains("GET"));
        assert!(allow_methods.contains("PUT"));

        let allow_headers = resp
            .headers()
            .get(ACCESS_CONTROL_ALLOW_HEADERS)
            .unwrap()
            .to_str()?;
        assert!(allow_headers.contains(CONTENT_TYPE.as_str()));
        assert!(allow_headers.contains(AUTHORIZATION.as_str()));
        assert_eq!("", resp.text().await?);
        //
        Ok(())
    }
}


================================================
FILE: roa/src/forward.rs
================================================
//! This module provides a context extension `Forward`,
//! which is used to parse `X-Forwarded-*` headers.

use std::net::IpAddr;

use crate::http::header::HOST;
use crate::{Context, State};

/// A context extension `Forward` used to parse `X-Forwarded-*` request headers.
pub trait Forward {
    /// Get true host.
    /// - If "x-forwarded-host" is set and valid, use it.
    /// - Else if "host" is set and valid, use it.
    /// - Else throw Err(400 BAD REQUEST).
    ///
    /// ### Example
    /// ```rust
    /// use roa::{Context, Result};
    /// use roa::forward::Forward;
    ///
    /// async fn get(ctx: &mut Context) -> Result {
    ///     if let Some(host) = ctx.host() {
    ///         println!("host: {}", host);
    ///     }
    ///     Ok(())
    /// }
    /// ```
    fn host(&self) -> Option<&str>;

    /// Get true client ip.
    /// - If "x-forwarded-for" is set and valid, use the first ip.
    /// - Else use the ip of `Context::remote_addr()`.
    ///
    /// ### Example
    /// ```rust
    /// use roa::{Context, Result};
    /// use roa::forward::Forward;
    ///
    /// async fn get(ctx: &mut Context) -> Result {
    ///     println!("client ip: {}", ctx.client_ip());
    ///     Ok(())
    /// }
    /// ```
    fn client_ip(&self) -> IpAddr;

    /// Get true forwarded ips.
    /// - If "x-forwarded-for" is set and valid, use it.
    /// - Else return an empty vector.
    ///
    /// ### Example
    /// ```rust
    /// use roa::{Context, Result};
    /// use roa::forward::Forward;
    ///
    /// async fn get(ctx: &mut Context) -> Result {
    ///     println!("forwarded ips: {:?}", ctx.forwarded_ips());
    ///     Ok(())
    /// }
    /// ```
    fn forwarded_ips(&self) -> Vec<IpAddr>;

    /// Try to get forwarded proto.
    /// - If "x-forwarded-proto" is not set, return None.
    /// - If "x-forwarded-proto" is set but fails to string, return Some(Err(400 BAD REQUEST)).
    ///
    /// ### Example
    /// ```rust
    /// use roa::{Context, Result};
    /// use roa::forward::Forward;
    ///
    /// async fn get(ctx: &mut Context) -> Result {
    ///     if let Some(proto) = ctx.forwarded_proto() {
    ///         println!("forwarded proto: {}", proto);
    ///     }
    ///     Ok(())
    /// }
    /// ```
    fn forwarded_proto(&self) -> Option<&str>;
}

impl<S: State> Forward for Context<S> {
    #[inline]
    fn host(&self) -> Option<&str> {
        self.get("x-forwarded-host").or_else(|| self.get(HOST))
    }

    #[inline]
    fn client_ip(&self) -> IpAddr {
        let addrs = self.forwarded_ips();
        if addrs.is_empty() {
            self.remote_addr.ip()
        } else {
            addrs[0]
        }
    }

    #[inline]
    fn forwarded_ips(&self) -> Vec<IpAddr> {
        let mut addrs = Vec::new();
        if let Some(value) = self.get("x-forwarded-for") {
            for addr_str in value.split(',') {
                if let Ok(addr) = addr_str.trim().parse() {
                    addrs.push(addr)
                }
            }
        }
        addrs
    }

    #[inline]
    fn forwarded_proto(&self) -> Option<&str> {
        self.get("x-forwarded-proto")
    }
}

#[cfg(all(test, feature = "tcp"))]
mod tests {
    use tokio::task::spawn;

    use super::Forward;
    use crate::http::header::HOST;
    use crate::http::{HeaderValue, StatusCode};
    use crate::preload::*;
    use crate::{App, Context};

    #[tokio::test]
    async fn host() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            assert_eq!(Some("github.com"), ctx.host());
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let client = reqwest::Client::new();
        let resp = client
            .get(&format!("http://{}", addr))
            .header(HOST, HeaderValue::from_static("github.com"))
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());

        let resp = client
            .get(&format!("http://{}", addr))
            .header(HOST, "google.com")
            .header("x-forwarded-host", "github.com")
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn host_err() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            ctx.req.headers.remove(HOST);
            assert_eq!(None, ctx.host());
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn client_ip() -> Result<(), Box<dyn std::error::Error>> {
        async fn remote_addr(ctx: &mut Context) -> crate::Result {
            assert_eq!(ctx.remote_addr.ip(), ctx.client_ip());
            Ok(())
        }
        let (addr, server) = App::new().end(remote_addr).run()?;
        spawn(server);
        reqwest::get(&format!("http://{}", addr)).await?;

        async fn forward_addr(ctx: &mut Context) -> crate::Result {
            assert_eq!("192.168.0.1", ctx.client_ip().to_string());
            Ok(())
        }
        let (addr, server) = App::new().end(forward_addr).run()?;
        spawn(server);
        let client = reqwest::Client::new();
        client
            .get(&format!("http://{}", addr))
            .header("x-forwarded-for", "192.168.0.1, 8.8.8.8")
            .send()
            .await?;

        Ok(())
    }

    #[tokio::test]
    async fn forwarded_proto() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            assert_eq!(Some("https"), ctx.forwarded_proto());
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let client = reqwest::Client::new();
        client
            .get(&format!("http://{}", addr))
            .header("x-forwarded-proto", "https")
            .send()
            .await?;

        Ok(())
    }
}


================================================
FILE: roa/src/jsonrpc.rs
================================================
//!
//! ## roa::jsonrpc
//!
//! This module provides a json rpc endpoint.
//!
//! ### Example
//!
//! ```rust,no_run
//! use roa::App;
//! use roa::jsonrpc::{RpcEndpoint, Data, Error, Params, Server};
//! use roa::tcp::Listener;
//! use tracing::info;
//!
//! #[derive(serde::Deserialize)]
//! struct TwoNums {
//!     a: usize,
//!     b: usize,
//! }
//!
//! async fn add(Params(params): Params<TwoNums>) -> Result<usize, Error> {
//!     Ok(params.a + params.b)
//! }
//!
//! async fn sub(Params(params): Params<(usize, usize)>) -> Result<usize, Error> {
//!     Ok(params.0 - params.1)
//! }
//!
//! async fn message(data: Data<String>) -> Result<String, Error> {
//!     Ok(String::from(&*data))
//! }
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let rpc = Server::new()
//!         .with_data(Data::new(String::from("Hello!")))
//!         .with_method("sub", sub)
//!         .with_method("message", message)
//!         .finish_unwrapped();
//!
//!     let app = App::new().end(RpcEndpoint(rpc));
//!     app.listen("127.0.0.1:8000", |addr| {
//!         info!("Server is listening on {}", addr)
//!     })?
//!     .await?;
//!     Ok(())
//! }
//! ```

use bytes::Bytes;
#[doc(no_inline)]
pub use jsonrpc_v2::*;

use crate::body::PowerBody;
use crate::{async_trait, Context, Endpoint, Result, State};

/// A wrapper for [`jsonrpc_v2::Server`], implemented [`roa::Endpoint`].
///
/// [`jsonrpc_v2::Server`]: https://docs.rs/jsonrpc-v2/0.10.1/jsonrpc_v2/struct.Server.html
/// [`roa::Endpoint`]: https://docs.rs/roa/0.6.0/roa/trait.Endpoint.html
pub struct RpcEndpoint<R>(pub Server<R>);

#[async_trait(? Send)]
impl<'a, S, R> Endpoint<'a, S> for RpcEndpoint<R>
where
    S: State,
    R: Router + Sync + Send + 'static,
{
    #[inline]
    async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
        let data = ctx.read().await?;
        let resp = self.0.handle(Bytes::from(data)).await;
        ctx.write_json(&resp)
    }
}


================================================
FILE: roa/src/jwt.rs
================================================
//! This module provides middleware `JwtGuard` and a context extension `JwtVerifier`.
//!
//! ### Example
//!
//! ```rust
//! use roa::jwt::{guard, DecodingKey};
//! use roa::{App, Context};
//! use roa::http::header::AUTHORIZATION;
//! use roa::http::StatusCode;
//! use roa::preload::*;
//! use tokio::task::spawn;
//! use jsonwebtoken::{encode, Header, EncodingKey};
//! use serde::{Deserialize, Serialize};
//! use std::time::{Duration, SystemTime, UNIX_EPOCH};
//!
//! #[derive(Debug, Serialize, Deserialize)]
//! struct User {
//!     sub: String,
//!     company: String,
//!     exp: u64,
//!     id: u64,
//!     name: String,
//! }
//!
//! const SECRET: &[u8] = b"123456";
//!
//! async fn test(ctx: &mut Context) -> roa::Result {
//!     let user: User = ctx.claims()?;
//!     assert_eq!(0, user.id);
//!     assert_eq!("Hexilee", &user.name);
//!     Ok(())
//! }
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let (addr, server) = App::new()
//!         .gate(guard(DecodingKey::from_secret(SECRET)))
//!         .end(test).run()?;
//!     spawn(server);
//!     let mut user = User {
//!         sub: "user".to_string(),
//!         company: "None".to_string(),
//!         exp: (SystemTime::now() + Duration::from_secs(86400))
//!             .duration_since(UNIX_EPOCH)?
//!             .as_secs(),
//!         id: 0,
//!         name: "Hexilee".to_string(),
//!     };
//!
//!     let client = reqwest::Client::new();
//!     let resp = client
//!         .get(&format!("http://{}", addr))
//!         .header(
//!             AUTHORIZATION,
//!             format!(
//!                 "Bearer {}",
//!                 encode(
//!                     &Header::default(),
//!                     &user,
//!                     &EncodingKey::from_secret(SECRET)
//!                 )?
//!             ),
//!         )
//!         .send()
//!         .await?;
//!     assert_eq!(StatusCode::OK, resp.status());
//!     Ok(())
//! }
//! ```

use headers::authorization::Bearer;
use headers::{Authorization, HeaderMapExt};
use jsonwebtoken::decode;
pub use jsonwebtoken::{DecodingKey, Validation};
use serde::de::DeserializeOwned;
use serde_json::Value;

use crate::http::header::{HeaderValue, WWW_AUTHENTICATE};
use crate::http::StatusCode;
use crate::{async_trait, throw, Context, Middleware, Next, Result, Status};

/// A private scope.
struct JwtScope;

static INVALID_TOKEN: HeaderValue =
    HeaderValue::from_static(r#"Bearer realm="<jwt>", error="invalid_token""#);

/// A function to set value of WWW_AUTHENTICATE.
#[inline]
fn set_www_authenticate<S>(ctx: &mut Context<S>) {
    ctx.resp
        .headers
        .insert(WWW_AUTHENTICATE, INVALID_TOKEN.clone());
}

/// Throw a internal server error.
#[inline]
fn guard_not_set() -> Status {
    Status::new(
        StatusCode::INTERNAL_SERVER_ERROR,
        "middleware `JwtGuard` is not set correctly",
        false,
    )
}

/// A context extension.
/// This extension must be used in downstream of middleware `guard` or `guard_by`,
/// otherwise you cannot get expected claims.
///
/// ### Example
///
/// ```rust
/// use roa::{Context, Result};
/// use roa::jwt::JwtVerifier;
/// use serde_json::Value;
///
/// async fn get(ctx: &mut Context) -> Result {
///     let claims: Value = ctx.claims()?;
///     Ok(())
/// }
/// ```
pub trait JwtVerifier<S> {
    /// Deserialize claims from token.
    fn claims<C>(&self) -> Result<C>
    where
        C: 'static + DeserializeOwned;

    /// Verify token and deserialize claims with a validation.
    /// Use this method if this validation is different from that one of `JwtGuard`.
    fn verify<C>(&mut self, validation: &Validation) -> Result<C>
    where
        C: 'static + DeserializeOwned;
}

/// Guard by default validation.
pub fn guard(secret: DecodingKey) -> JwtGuard {
    JwtGuard::new(secret, Validation::default())
}

/// A middleware to deny unauthorized requests.
///
/// The json web token should be deliver by request header "authorization",
/// in format of `Authorization: Bearer <token>`.
///
/// If request fails to pass verification, return 401 UNAUTHORIZED and set response header "WWW-Authenticate".
#[derive(Debug, Clone, PartialEq)]
pub struct JwtGuard {
    secret: DecodingKey<'static>,
    validation: Validation,
}

impl JwtGuard {
    /// Construct guard.
    pub fn new(secret: DecodingKey, validation: Validation) -> Self {
        Self {
            secret: secret.into_static(),
            validation,
        }
    }

    /// Verify token.
    #[inline]
    fn verify<S>(&self, ctx: &Context<S>) -> Option<(Bearer, Value)> {
        let bearer = ctx.req.headers.typed_get::<Authorization<Bearer>>()?.0;
        let value = decode::<Value>(bearer.token(), &self.secret, &self.validation)
            .ok()?
            .claims;
        Some((bearer, value))
    }
}

#[async_trait(? Send)]
impl<'a, S> Middleware<'a, S> for JwtGuard {
    #[inline]
    async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> Result {
        match self.verify(ctx) {
            None => {
                set_www_authenticate(ctx);
                throw!(StatusCode::UNAUTHORIZED)
            }
            Some((bearer, value)) => {
                ctx.store_scoped(JwtScope, "secret", self.secret.clone());
                ctx.store_scoped(JwtScope, "token", bearer);
                ctx.store_scoped(JwtScope, "value", value);
                next.await
            }
        }
    }
}

impl<S> JwtVerifier<S> for Context<S> {
    #[inline]
    fn claims<C>(&self) -> Result<C>
    where
        C: 'static + DeserializeOwned,
    {
        let value = self.load_scoped::<JwtScope, Value>("value");
        match value {
            Some(claims) => Ok(serde_json::from_value((*claims).clone())?),
            None => Err(guard_not_set()),
        }
    }

    #[inline]
    fn verify<C>(&mut self, validation: &Validation) -> Result<C>
    where
        C: 'static + DeserializeOwned,
    {
        let secret = self.load_scoped::<JwtScope, DecodingKey<'static>>("secret");
        let token = self.load_scoped::<JwtScope, Bearer>("token");
        match (secret, token) {
            (Some(secret), Some(token)) => match decode(token.token(), &secret, validation) {
                Ok(data) => Ok(data.claims),
                Err(_) => {
                    set_www_authenticate(self);
                    throw!(StatusCode::UNAUTHORIZED)
                }
            },
            _ => Err(guard_not_set()),
        }
    }
}

#[cfg(all(test, feature = "tcp"))]
mod tests {
    use std::time::{Duration, SystemTime, UNIX_EPOCH};

    use jsonwebtoken::{encode, EncodingKey, Header};
    use serde::{Deserialize, Serialize};
    use tokio::task::spawn;

    use super::{guard, DecodingKey, INVALID_TOKEN};
    use crate::http::header::{AUTHORIZATION, WWW_AUTHENTICATE};
    use crate::http::StatusCode;
    use crate::preload::*;
    use crate::{App, Context};

    #[derive(Debug, Serialize, Deserialize)]
    struct User {
        sub: String,
        company: String,
        exp: u64,
        id: u64,
        name: String,
    }

    const SECRET: &[u8] = b"123456";

    #[tokio::test]
    async fn claims() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            let user: User = ctx.claims()?;
            assert_eq!(0, user.id);
            assert_eq!("Hexilee", &user.name);
            Ok(())
        }
        let (addr, server) = App::new()
            .gate(guard(DecodingKey::from_secret(SECRET)))
            .end(test)
            .run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::UNAUTHORIZED, resp.status());
        assert_eq!(&INVALID_TOKEN, &resp.headers()[WWW_AUTHENTICATE]);

        // non-string header value
        let client = reqwest::Client::new();
        let resp = client
            .get(&format!("http://{}", addr))
            .header(AUTHORIZATION, [255].as_ref())
            .send()
            .await?;
        assert_eq!(StatusCode::UNAUTHORIZED, resp.status());
        assert_eq!(&INVALID_TOKEN, &resp.headers()[WWW_AUTHENTICATE]);

        // non-Bearer header value
        let resp = client
            .get(&format!("http://{}", addr))
            .header(AUTHORIZATION, "Basic hahaha")
            .send()
            .await?;
        assert_eq!(StatusCode::UNAUTHORIZED, resp.status());
        assert_eq!(&INVALID_TOKEN, &resp.headers()[WWW_AUTHENTICATE]);

        // invalid token
        let resp = client
            .get(&format!("http://{}", addr))
            .header(AUTHORIZATION, "Bearer hahaha")
            .send()
            .await?;
        assert_eq!(StatusCode::UNAUTHORIZED, resp.status());
        assert_eq!(&INVALID_TOKEN, &resp.headers()[WWW_AUTHENTICATE]);

        // expired token
        let mut user = User {
            sub: "user".to_string(),
            company: "None".to_string(),
            exp: (SystemTime::now() - Duration::from_secs(1))
                .duration_since(UNIX_EPOCH)?
                .as_secs(), // one second ago
            id: 0,
            name: "Hexilee".to_string(),
        };
        let resp = client
            .get(&format!("http://{}", addr))
            .header(
                AUTHORIZATION,
                format!(
                    "Bearer {}",
                    encode(&Header::default(), &user, &EncodingKey::from_secret(SECRET),)?
                ),
            )
            .send()
            .await?;
        assert_eq!(StatusCode::UNAUTHORIZED, resp.status());
        assert_eq!(&INVALID_TOKEN, &resp.headers()[WWW_AUTHENTICATE]);

        user.exp = (SystemTime::now() + Duration::from_millis(60))
            .duration_since(UNIX_EPOCH)?
            .as_secs(); // one hour later
        let resp = client
            .get(&format!("http://{}", addr))
            .header(
                AUTHORIZATION,
                format!(
                    "Bearer {}",
                    encode(&Header::default(), &user, &EncodingKey::from_secret(SECRET),)?
                ),
            )
            .send()
            .await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn jwt_verify_not_set() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            let _: User = ctx.claims()?;
            Ok(())
        }
        let (addr, server) = App::new().end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}", addr)).await?;
        assert_eq!(StatusCode::INTERNAL_SERVER_ERROR, resp.status());
        Ok(())
    }
}


================================================
FILE: roa/src/lib.rs
================================================
#![cfg_attr(feature = "docs", feature(doc_cfg))]
#![cfg_attr(feature = "docs", doc = include_str!("../README.md"))]
#![cfg_attr(feature = "docs", warn(missing_docs))]

pub use roa_core::*;

#[cfg(feature = "router")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "router")))]
pub mod router;

#[cfg(feature = "tcp")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "tcp")))]
pub mod tcp;

#[cfg(feature = "tls")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "tls")))]
pub mod tls;

#[cfg(feature = "websocket")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "websocket")))]
pub mod websocket;

#[cfg(feature = "cookies")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "cookies")))]
pub mod cookie;

#[cfg(feature = "jwt")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "jwt")))]
pub mod jwt;

#[cfg(feature = "compress")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "compress")))]
pub mod compress;

#[cfg(feature = "jsonrpc")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "jsonrpc")))]
pub mod jsonrpc;

pub mod body;
pub mod cors;
pub mod forward;
pub mod logger;
pub mod query;
pub mod stream;

/// Reexport all extension traits.
pub mod preload {
    pub use crate::body::PowerBody;
    #[cfg(feature = "cookies")]
    pub use crate::cookie::{CookieGetter, CookieSetter};
    pub use crate::forward::Forward;
    #[cfg(feature = "jwt")]
    pub use crate::jwt::JwtVerifier;
    pub use crate::query::Query;
    #[cfg(feature = "router")]
    pub use crate::router::RouterParam;
    #[cfg(feature = "tcp")]
    #[doc(no_inline)]
    pub use crate::tcp::Listener;
    #[cfg(all(feature = "tcp", feature = "tls"))]
    #[doc(no_inline)]
    pub use crate::tls::TlsListener;
}


================================================
FILE: roa/src/logger.rs
================================================
//! This module provides a middleware `logger`.
//!
//! ### Example
//!
//! ```rust
//! use roa::logger::logger;
//! use roa::preload::*;
//! use roa::App;
//! use roa::http::StatusCode;
//! use tokio::task::spawn;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     pretty_env_logger::init();
//!     let app = App::new()
//!         .gate(logger)
//!         .end("Hello, World");
//!     let (addr, server) = app.run()?;
//!     spawn(server);
//!     let resp = reqwest::get(&format!("http://{}", addr)).await?;
//!     assert_eq!(StatusCode::OK, resp.status());
//!     Ok(())
//! }
//! ```

use std::pin::Pin;
use std::time::Instant;
use std::{io, mem};

use bytes::Bytes;
use bytesize::ByteSize;
use futures::task::{self, Poll};
use futures::{Future, Stream};
use roa_core::http::{Method, StatusCode};
use tracing::{error, info};

use crate::http::Uri;
use crate::{Context, Executor, JoinHandle, Next, Result};

/// A finite-state machine to log success information in each successful response.
enum StreamLogger<S> {
    /// Polling state, as a body stream.
    Polling { stream: S, task: LogTask },

    /// Logging state, as a logger future.
    Logging(JoinHandle<()>),

    /// Complete, as a empty stream.
    Complete,
}

/// A task structure to log when polling is complete.
#[derive(Clone)]
struct LogTask {
    counter: u64,
    method: Method,
    status_code: StatusCode,
    uri: Uri,
    start: Instant,
    exec: Executor,
}

impl LogTask {
    #[inline]
    fn log(&self) -> JoinHandle<()> {
        let LogTask {
            counter,
            method,
            status_code,
            uri,
            start,
            exec,
        } = self.clone();
        exec.spawn_blocking(move || {
            info!(
                "<-- {} {} {}ms {} {}",
                method,
                uri,
                start.elapsed().as_millis(),
                ByteSize(counter),
                status_code,
            )
        })
    }
}

impl<S> Stream for StreamLogger<S>
where
    S: 'static + Send + Send + Unpin + Stream<Item = io::Result<Bytes>>,
{
    type Item = io::Result<Bytes>;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
        match &mut *self {
            StreamLogger::Polling { stream, task } => {
                match futures::ready!(Pin::new(stream).poll_next(cx)) {
                    Some(Ok(bytes)) => {
                        task.counter += bytes.len() as u64;
                        Poll::Ready(Some(Ok(bytes)))
                    }
                    None => {
                        let handler = task.log();
                        *self = StreamLogger::Logging(handler);
                        self.poll_next(cx)
                    }
                    err => Poll::Ready(err),
                }
            }

            StreamLogger::Logging(handler) => {
                futures::ready!(Pin::new(handler).poll(cx));
                *self = StreamLogger::Complete;
                self.poll_next(cx)
            }

            StreamLogger::Complete => Poll::Ready(None),
        }
    }
}

/// A middleware to log information about request and response.
///
/// Based on crate `log`, the log level must be greater than `INFO` to log all information,
/// and should be greater than `ERROR` when you need error information only.
pub async fn logger<S>(ctx: &mut Context<S>, next: Next<'_>) -> Result {
    info!("--> {} {}", ctx.method(), ctx.uri().path());
    let start = Instant::now();
    let mut result = next.await;

    let method = ctx.method().clone();
    let uri = ctx.uri().clone();
    let exec = ctx.exec.clone();

    match &mut result {
        Err(status) => {
            let status_code = status.status_code;
            let message = if status.expose {
                status.message.clone()
            } else {
                // set expose to true; then root status_handler won't log this status.
                status.expose = true;

                // take unexposed message
                mem::take(&mut status.message)
            };
            ctx.exec
                .spawn_blocking(move || {
                    error!("<-- {} {} {}\n{}", method, uri, status_code, message,);
                })
                .await
        }
        Ok(_) => {
            let status_code = ctx.status();
            // logging when body polling complete.
            let logger = StreamLogger::Polling {
                stream: mem::take(&mut ctx.resp.body),
                task: LogTask {
                    counter: 0,
                    method,
                    uri,
                    status_code,
                    start,
                    exec,
                },
            };
            ctx.resp.write_stream(logger);
        }
    }
    result
}


================================================
FILE: roa/src/query.rs
================================================
//! This module provides a middleware `query_parser` and a context extension `Query`.
//!
//! ### Example
//!
//! ```rust
//! use roa::query::query_parser;
//! use roa::{App, Context};
//! use roa::http::StatusCode;
//! use roa::preload::*;
//! use tokio::task::spawn;
//!
//! async fn must(ctx: &mut Context) -> roa::Result {
//!     assert_eq!("Hexilee", &*ctx.must_query("name")?);
//!     Ok(())
//! }
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let app = App::new()
//!         .gate(query_parser)
//!         .end(must);
//!     let (addr, server) = app.run()?;
//!     spawn(server);
//!     let resp = reqwest::get(&format!("http://{}?name=Hexilee", addr)).await?;
//!     assert_eq!(StatusCode::OK, resp.status());
//!     Ok(())
//! }
//! ```

use url::form_urlencoded::parse;

use crate::http::StatusCode;
use crate::{Context, Next, Result, Status, Variable};

/// A scope to store and load variables in Context::storage.
struct QueryScope;

/// A context extension.
/// This extension must be used in downstream of middleware `query_parser`,
/// otherwise you cannot get expected query variable.
///
/// ### Example
///
/// ```rust
/// use roa::query::query_parser;
/// use roa::{App, Context};
/// use roa::http::StatusCode;
/// use roa::preload::*;
/// use tokio::task::spawn;
///
/// async fn must(ctx: &mut Context) -> roa::Result {
///     assert_eq!("Hexilee", &*ctx.must_query("name")?);
///     Ok(())
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     // downstream of `query_parser`
///     let app = App::new()
///         .gate(query_parser)
///         .end(must);
///     let (addr, server) = app.run()?;
///     spawn(server);
///     let resp = reqwest::get(&format!("http://{}?name=Hexilee", addr)).await?;
///     assert_eq!(StatusCode::OK, resp.status());
///
///     // miss `query_parser`
///     let app = App::new().end(must);
///     let (addr, server) = app.run()?;
///     spawn(server);
///     let resp = reqwest::get(&format!("http://{}?name=Hexilee", addr)).await?;
///     assert_eq!(StatusCode::BAD_REQUEST, resp.status());
///     Ok(())
/// }
/// ```
pub trait Query {
    /// Must get a variable, throw 400 BAD_REQUEST if it not exists.
    /// ### Example
    ///
    /// ```rust
    /// use roa::query::query_parser;
    /// use roa::{App, Context};
    /// use roa::http::StatusCode;
    /// use roa::preload::*;
    /// use tokio::task::spawn;
    ///
    /// async fn must(ctx: &mut Context) -> roa::Result {
    ///     assert_eq!("Hexilee", &*ctx.must_query("name")?);
    ///     Ok(())
    /// }
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     // downstream of `query_parser`
    ///     let app = App::new()
    ///         .gate(query_parser)
    ///         .end(must);
    ///     let (addr, server) = app.run()?;
    ///     spawn(server);
    ///     let resp = reqwest::get(&format!("http://{}", addr)).await?;
    ///     assert_eq!(StatusCode::BAD_REQUEST, resp.status());
    ///     Ok(())
    /// }
    /// ```
    fn must_query<'a>(&self, name: &'a str) -> Result<Variable<'a, String>>;

    /// Query a variable, return `None` if it not exists.
    /// ### Example
    ///
    /// ```rust
    /// use roa::query::query_parser;
    /// use roa::{App, Context};
    /// use roa::http::StatusCode;
    /// use roa::preload::*;
    /// use tokio::task::spawn;
    ///
    /// async fn test(ctx: &mut Context) -> roa::Result {
    ///     assert!(ctx.query("name").is_none());
    ///     Ok(())
    /// }
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     // downstream of `query_parser`
    ///     let app = App::new()
    ///         .gate(query_parser)
    ///         .end(test);
    ///     let (addr, server) = app.run()?;
    ///     spawn(server);
    ///     let resp = reqwest::get(&format!("http://{}", addr)).await?;
    ///     assert_eq!(StatusCode::OK, resp.status());
    ///     Ok(())
    /// }
    /// ```
    fn query<'a>(&self, name: &'a str) -> Option<Variable<'a, String>>;
}

/// A middleware to parse query.
#[inline]
pub async fn query_parser<S>(ctx: &mut Context<S>, next: Next<'_>) -> Result {
    let query_string = ctx.uri().query().unwrap_or("");
    let pairs: Vec<(String, String)> = parse(query_string.as_bytes()).into_owned().collect();
    for (key, value) in pairs {
        ctx.store_scoped(QueryScope, key, value);
    }
    next.await
}

impl<S> Query for Context<S> {
    #[inline]
    fn must_query<'a>(&self, name: &'a str) -> Result<Variable<'a, String>> {
        self.query(name).ok_or_else(|| {
            Status::new(
                StatusCode::BAD_REQUEST,
                format!("query `{}` is required", name),
                true,
            )
        })
    }
    #[inline]
    fn query<'a>(&self, name: &'a str) -> Option<Variable<'a, String>> {
        self.load_scoped::<QueryScope, String>(name)
    }
}

#[cfg(all(test, feature = "tcp"))]
mod tests {
    use tokio::task::spawn;

    use crate::http::StatusCode;
    use crate::preload::*;
    use crate::query::query_parser;
    use crate::{App, Context};

    #[tokio::test]
    async fn query() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            assert_eq!("Hexilee", &*ctx.must_query("name")?);
            Ok(())
        }

        // miss key
        let (addr, server) = App::new().gate(query_parser).end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}/", addr)).await?;
        assert_eq!(StatusCode::BAD_REQUEST, resp.status());

        // string value
        let (addr, server) = App::new().gate(query_parser).end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}?name=Hexilee", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn query_parse() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            assert_eq!(120, ctx.must_query("age")?.parse::<u64>()?);
            Ok(())
        }
        // invalid int value
        let (addr, server) = App::new().gate(query_parser).end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}?age=Hexilee", addr)).await?;
        assert_eq!(StatusCode::BAD_REQUEST, resp.status());

        let (addr, server) = App::new().gate(query_parser).end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}?age=120", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }

    #[tokio::test]
    async fn query_action() -> Result<(), Box<dyn std::error::Error>> {
        async fn test(ctx: &mut Context) -> crate::Result {
            assert_eq!("Hexilee", &*ctx.must_query("name")?);
            assert_eq!("rust", &*ctx.must_query("lang")?);
            Ok(())
        }
        let (addr, server) = App::new().gate(query_parser).end(test).run()?;
        spawn(server);
        let resp = reqwest::get(&format!("http://{}?name=Hexilee&lang=rust", addr)).await?;
        assert_eq!(StatusCode::OK, resp.status());
        Ok(())
    }
}


================================================
FILE: roa/src/router/endpoints/dispatcher.rs
================================================
use std::collections::HashMap;

use doc_comment::doc_comment;

use super::method_not_allowed;
use crate::http::Method;
use crate::{async_trait, Context, Endpoint, Result};

macro_rules! impl_http_methods {
    ($end:ident, $method:expr) => {
        doc_comment! {
        concat!("Method to add or override endpoint on ", stringify!($method), ".

You can use it as follow:

```rust
use roa::{App, Context, Result};
use roa::router::get;

async fn foo(ctx: &mut Context) -> Result {
    Ok(())
}

async fn bar(ctx: &mut Context) -> Result {
    Ok(())
}

let app = App::new().end(get(foo).", stringify!($end), "(bar));
```"),
            pub fn $end(mut self, endpoint: impl for<'a> Endpoint<'a, S>) -> Self {
                self.0.insert($method, Box::new(endpoint));
                self
            }
        }
    };
}

macro_rules! impl_http_functions {
    ($end:ident, $method:expr) => {
        doc_comment! {
        concat!("Function to construct dispatcher with ", stringify!($method), " and an endpoint.

You can use it as follow:

```rust
use roa::{App, Context, Result};
use roa::router::", stringify!($end), ";

async fn end(ctx: &mut Context) -> Result {
    Ok(())
}

let app = App::new().end(", stringify!($end), "(end));
```"),
            pub fn $end<S>(endpoint: impl for<'a> Endpoint<'a, S>) -> Dispatcher<S> {
                    Dispatcher::<S>::default().$end(endpoint)
            }
        }
    };
}

/// An endpoint wrapper to dispatch requests by http method.
pub struct Dispatcher<S>(HashMap<Method, Box<dyn for<'a> Endpoint<'a, S>>>);

impl_http_functions!(get, Method::GET);
impl_http_functions!(post, Method::POST);
impl_http_functions!(put, Method::PUT);
impl_http_functions!(patch, Method::PATCH);
impl_http_functions!(options, Method::OPTIONS);
impl_http_functions!(delete, Method::DELETE);
impl_http_functions!(head, Method::HEAD);
impl_http_functions!(trace, Method::TRACE);
impl_http_functions!(connect, Method::CONNECT);

impl<S> Dispatcher<S> {
    impl_http_methods!(get, Method::GET);
    impl_http_methods!(post, Method::POST);
    impl_http_methods!(put, Method::PUT);
    impl_http_methods!(patch, Method::PATCH);
    impl_http_methods!(options, Method::OPTIONS);
    impl_http_methods!(delete, Method::DELETE);
    impl_http_methods!(head, Method::HEAD);
    impl_http_methods!(trace, Method::TRACE);
    impl_http_methods!(connect, Method::CONNECT);
}

/// Empty dispatcher.
impl<S> Default for Dispatcher<S> {
    fn default() -> Self {
        Self(HashMap::new())
    }
}

#[async_trait(?Send)]
impl<'a, S> Endpoint<'a, S> for Dispatcher<S>
where
    S: 'static,
{
    #[inline]
    async fn call(&'a self, ctx: &'a mut Context<S>) -> Result<()> {
        match self.0.get(ctx.method()) {
            Some(endpoint) => endpoint.call(ctx).await,
            None => method_not_allowed(ctx.method()),
        }
    }
}


================================================
FILE: roa/src/router/endpoints/guard.rs
================================================
use std::collections::HashSet;
use std::iter::FromIterator;

use super::method_not_allowed;
use crate::http::Method;
use crate::{async_trait, Context, Endpoint, Result};

/// Methods allowed in `Guard`.
const ALL_METHODS: [Method; 9] = [
    Method::GET,
    Method::POST,
    Method::PUT,
    Method::PATCH,
    Method::OPTIONS,
    Method::DELETE,
    Method::HEAD,
    Method::TRACE,
    Method::CONNECT,
];

/// An endpoint wrapper to guard endpoint by http method.
pub struct Guard<E> {
    white_list: HashSet<Method>,
    endpoint: E,
}

/// Initialize hash set.
fn hash_set(methods: impl AsRef<[Method]>) -> HashSet<Method> {
    HashSet::from_iter(methods.as_ref().to_vec())
}

/// A function to construct guard by white list.
///
/// Only requests with http method in list can access this endpoint, otherwise will get a 405 METHOD NOT ALLOWED.
///
/// ```
/// use roa::{App, Context, Result};
/// use roa::http::Method;
/// use roa::router::allow;
///
/// async fn foo(ctx: &mut Context) -> Result {
///     Ok(())
/// }
///
/// let app = App::new().end(allow([Method::GET, Method::POST], foo));
/// ```
pub fn allow<E>(methods: impl AsRef<[Method]>, endpoint: E) -> Guard<E> {
    Guard {
        endpoint,
        white_list: hash_set(methods),
    }
}

/// A function to construct guard by black list.
///
/// Only requests with http method not in list can access this endpoint, otherwise will get a 405 METHOD NOT ALLOWED.
///
/// ```
/// use roa::{App, Context, Result};
/// use roa::http::Method;
/// use roa::router::deny;
///
/// async fn foo(ctx: &mut Context) -> Result {
///     Ok(())
/// }
///
/// let app = App::new().end(deny([Method::PUT, Method::DELETE], foo));
/// ```
pub fn deny<E>(methods: impl AsRef<[Method]>, endpoint: E) -> Guard<E> {
    let white_list = hash_set(ALL_METHODS);
    let black_list = &white_list & &hash_set(methods);
    Guard {
        endpoint,
        white_list: &white_list ^ &black_list,
    }
}

#[async_trait(?Send)]
impl<'a, S, E> Endpoint<'a, S> for Guard<E>
where
    E: Endpoint<'a, S>,
{
    #[inline]
    async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
        if self.white_list.contains(ctx.method()) {
            self.endpoint.call(ctx).await
        } else {
            method_not_allowed(ctx.method())
        }
    }
}


================================================
FILE: roa/src/router/endpoints.rs
================================================
mod dispatcher;
mod guard;

use crate::http::{Method, StatusCode};
use crate::{throw, Result};

#[inline]
fn method_not_allowed(method: &Method) -> Result {
    throw!(
        StatusCode::METHOD_NOT_ALLOWED,
        format!("Method {} not allowed", method)
    )
}

pub use dispatcher::{connect, delete, get, head, options, patch, post, put, trace, Dispatcher};
pub use guard::{allow, deny, Guard};


================================================
FILE: roa/src/router/err.rs
================================================
use std::fmt::{self, Display, Formatter};

use roa_core::http;

/// Error occurring in building route table.
#[derive(Debug)]
pub enum RouterError {
    /// Dynamic paths miss variable.
    MissingVariable(String),

    /// Variables, methods or paths conflict.
    Conflict(Conflict),
}

/// Router conflict.
#[derive(Debug, Eq, PartialEq)]
pub enum Conflict {
    Path(String),
    Method(String, http::Method),
    Variable {
        paths: (String, String),
        var_name: String,
    },
}

impl Display for Conflict {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
        match self {
            Conflict::Path(path) => f.write_str(&format!("conflict path: `{}`", path)),
            Conflict::Method(path, method) => f.write_str(&format!(
                "conflict method: `{}` on `{}` is already set",
                method, path
            )),
            Conflict::Variable { paths, var_name } => f.write_str(&format!(
                "conflict variable `{}`: between `{}` and `{}`",
                var_name, paths.0, paths.1
            )),
        }
    }
}

impl Display for RouterError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
        match self {
            RouterError::Conflict(conflict) => f.write_str(&format!("Conflict! {}", conflict)),
            RouterError::MissingVariable(path) => {
                f.write_str(&format!("missing variable on path {}", path))
            }
        }
    }
}

impl From<Conflict> for RouterError {
    fn from(conflict: Conflict) -> Self {
        RouterError::Conflict(conflict)
    }
}

impl std::error::Error for Conflict {}
impl std::error::Error for RouterError {}

#[cfg(test)]
mod tests {
    use roa_core::http;

    use super::{Conflict, RouterError};

    #[test]
    fn conflict_to_string() {
        assert_eq!(
            "conflict path: `/`",
            Conflict::Path("/".to_string()).to_string()
        );
        assert_eq!(
            "conflict method: `GET` on `/` is already set",
            Conflict::Method("/".to_string(), http::Method::GET).to_string()
        );
        assert_eq!(
            "conflict variable `id`: between `/:id` and `/user/:id`",
            Conflict::Variable {
                paths: ("/:id".to_string(), "/user/:id".to_string()),
                var_name: "id".to_string()
            }
            .to_string()
        );
    }

    #[test]
    fn err_to_string() {
        assert_eq!(
            "Conflict! conflict path: `/`",
            RouterError::Conflict(Conflict::Path("/".to_string())).to_string()
        );
        assert_eq!(
            "missing variable on path /:",
            RouterError::MissingVariable("/:".to_string()).to_string()
        );
    }
}


================================================
FILE: roa/src/router/path.rs
================================================
use std::collections::HashSet;
use std::convert::AsRef;
use std::str::FromStr;

use regex::{escape, Captures, Regex};

use super::{Conflict, RouterError};

/// Match pattern *{variable}
const WILDCARD: &str = r"\*\{(?P<var>\w*)\}";

/// Match pattern /:variable/
const VARIABLE: &str = r"/:(?P<var>\w*)/";

/// {/path path/ /path/} => /path/
pub fn standardize_path(raw_path: &str) -> String {
    format!("/{}/", raw_path.trim_matches('/'))
}

/// Join multiple segments.
pub fn join_path<'a>(paths: impl 'a + AsRef<[&'a str]>) -> String {
    paths
        .as_ref()
        .iter()
        .map(|path| path.trim_matches('/'))
        .filter(|path| !path.is_empty())
        .collect::<Vec<&str>>()
        .join("/")
}

/// Build pattern.
fn must_build(pattern: &str) -> Regex {
    Regex::new(pattern).unwrap_or_else(|err| {
        panic!(
            r#"{}
                regex pattern {} is invalid, this is a bug of roa::router::path.
                please report it to https://github.com/Hexilee/roa"#,
            err, pattern
        )
    })
}

/// Parsed path.
#[derive(Clone)]
pub enum Path {
    Static(String),
    Dynamic(RegexPath),
}

/// Dynamic path.
#[derive(Clone)]
pub struct RegexPath {
    pub raw: String,
    pub vars: HashSet<String>,
    pub re: Regex,
}

impl FromStr for Path {
    type Err = RouterError;
    fn from_str(raw_path: &str) -> Result<Self, Self::Err> {
        let path = standardize_path(raw_path);
        Ok(match path_to_regexp(&path)? {
            None => Path::Static(path),
            Some((pattern, vars)) => Path::Dynamic(RegexPath {
                raw: path,
                vars,
                re: must_build(&format!(r"^{}$", pattern)),
            }),
        })
    }
}

fn path_to_regexp(path: &str) -> Result<Option<(String, HashSet<String>)>, RouterError> {
    let mut pattern = escape(path);
    let mut vars = HashSet::new();
    let wildcard_re = must_build(WILDCARD);
    let variable_re = must_build(VARIABLE);
    let wildcards: Vec<Captures> = wildcard_re.captures_iter(path).collect();
    let variable_template = path.replace('/', "//"); // to match continuous variables like /:year/:month/:day/
    let variables: Vec<Captures> = variable_re.captures_iter(&variable_template).collect();
    if wildcards.is_empty() && variables.is_empty() {
        Ok(None)
    } else {
        // detect variable conflicts.
        let try_add_variable = |set: &mut HashSet<String>, variable: String| {
            if set.insert(variable.clone()) {
                Ok(())
            } else {
                Err(Conflict::Variable {
                    paths: (path.to_string(), path.to_string()),
                    var_name: variable,
                })
            }
        };

        // match wildcard patterns
        for cap in wildcards {
            let variable = &cap["var"];
            if variable.is_empty() {
                return Err(RouterError::MissingVariable(path.to_string()));
            }
            let var = escape(variable);
            pattern = pattern.replace(
                &escape(&format!(r"*{{{}}}", variable)),
                &format!(r"(?P<{}>\S+)", &var),
            );
            try_add_variable(&mut vars, var)?;
        }

        // match segment variable patterns
        for cap in variables {
            let variable = &cap["var"];
            if variable.is_empty() {
                return Err(RouterError::MissingVariable(path.to_string()));
            }
            let var = escape(variable);
            pattern = pattern.replace(
                &escape(&format!(r":{}", variable)),
                &format!(r"(?P<{}>[^\s/]+)", &var),
            );
            try_add_variable(&mut vars, var)?;
        }
        Ok(Some((pattern, vars)))
    }
}

#[cfg(test)]
mod tests {
    use test_case::test_case;

    use super::{must_build, path_to_regexp, Path, VARIABLE, WILDCARD};

    #[test_case("/:id/"; "pure dynamic")]
    #[test_case("/user/:id/"; "static prefix")]
    #[test_case("/user/:id/name"; "static prefix and suffix")]
    fn var_regex_match(path: &str) {
        let re = must_build(VARIABLE);
        let cap = re.captures(path);
        assert!(cap.is_some());
        assert_eq!("id", &cap.unwrap()["var"]);
    }

    #[test_case("/-:id/"; "invalid prefix")]
    #[test_case("/:i-d/"; "invalid variable name")]
    #[test_case("/:id-/"; "invalid suffix")]
    fn var_regex_mismatch(path: &str) {
        let re = must_build(VARIABLE);
        let cap = re.captures(path);
        assert!(cap.is_none());
    }

    #[test_case("*{id}"; "pure dynamic")]
    #[test_case("user-*{id}"; "static prefix")]
    #[test_case("user-*{id}-name"; "static prefix and suffix")]
    fn wildcard_regex_match(path: &str) {
        let re = must_build(WILDCARD);
        let cap = re.captures(path);
        assert!(cap.is_some());
        assert_eq!("id", &cap.unwrap()["var"]);
    }

    #[test_case("*"; "no variable")]
    #[test_case("*{-id}"; "invalid variable name")]
    fn wildcard_regex_mismatch(path: &str) {
        let re = must_build(WILDCARD);
        let cap = re.captures(path);
        assert!(cap.is_none());
    }

    #[test_case(r"/:id/" => r"/(?P<id>[^\s/]+)/"; "single variable")]
    #[test_case(r"/:year/:month/:day/" => r"/(?P<year>[^\s/]+)/(?P<month>[^\s/]+)/(?P<day>[^\s/]+)/"; "multiple variable")]
    #[test_case(r"*{id}" => r"(?P<id>\S+)"; "single wildcard")]
    #[test_case(r"*{year}_*{month}_*{day}" => r"(?P<year>\S+)_(?P<month>\S+)_(?P<day>\S+)"; "multiple wildcard")]
    fn path_to_regexp_dynamic_pattern(path: &str) -> String {
        path_to_regexp(path).unwrap().unwrap().0
    }

    #[test_case(r"/id/")]
    #[test_case(r"/user/post/")]
    fn path_to_regexp_static(path: &str) {
        assert!(path_to_regexp(path).unwrap().is_none())
    }

    #[test_case(r"/:/"; "missing variable name")]
    #[test_case(r"*{}"; "wildcard missing variable name")]
    #[test_case(r"/:id/:id/"; "conflict variable")]
    #[test_case(r"*{id}-*{id}"; "wildcard conflict variable")]
    #[test_case(r"/:id/*{id}"; "mix conflict variable")]
    fn path_to_regexp_err(path: &str) {
        assert!(path_to_regexp(path).is_err())
    }

    fn path_match(pattern: &str, path: &str) {
        let pattern: Path = pattern.parse().unwrap();
        match pattern {
            Path::Static(pattern) => panic!("`{}` should be dynamic", pattern),
            Path::Dynamic(re) => assert!(re.re.is_match(path)),
        }
    }

    fn path_not_match(pattern: &str, path: &str) {
        let pattern: Path = pattern.parse().unwrap();
        match pattern {
            Path::Static(pattern) => panic!("`{}` should be dynamic", pattern),
            Path::Dynamic(re) => {
                println!("regex: {}", re.re.to_string());
                assert!(!re.re.is_match(path))
            }
        }
    }

    #[test_case(r"/user/1/")]
    #[test_case(r"/user/65535/")]
    fn single_variable_path_match(path: &str) {
        path_match(r"/user/:id", path)
    }

    #[test_case(r"/2000/01/01/")]
    #[test_case(r"/2020/02/20/")]
    fn multiple_variable_path_match(path: &str) {
        path_match(r"/:year/:month/:day", path)
    }

    #[test_case(r"/usr/include/boost/boost.h/")]
    #[test_case(r"/usr/include/uv/uv.h/")]
    fn segment_wildcard_path_match(path: &str) {
        path_match(r"/usr/include/*{dir}/*{file}.h", path)
    }

    #[test_case(r"/srv/static/app/index.html/")]
    #[test_case(r"/srv/static/../../index.html/")]
    fn full_wildcard_path_match(path: &str) {
        path_match(r"/srv/static/*{path}/", path)
    }

    #[test_case(r"/srv/app/index.html/")]
    #[test_case(r"/srv/../../index.html/")]
    fn variable_path_not_match(path: &str) {
        path_not_match(r"/srv/:path/", path)
    }

    #[should_panic]
    #[test]
    fn must_build_fails() {
        must_build(r"{");
    }
}


================================================
FILE: roa/src/router.rs
================================================
//! This module provides a context extension `RouterParam` and
//! many endpoint wrappers like `Router`, `Dispatcher` and `Guard`.
//!
//! ### Example
//!
//! ```rust
//! use roa::router::{Router, RouterParam, get, allow};
//! use roa::{App, Context, Status, MiddlewareExt, Next};
//! use roa::http::{StatusCode, Method};
//! use roa::tcp::Listener;
//! use tokio::task::spawn;
//!
//!
//! async fn gate(_ctx: &mut Context, next: Next<'_>) -> Result<(), Status> {
//!     next.await
//! }
//!
//! async fn query(ctx: &mut Context) -> Result<(), Status> {
//!     Ok(())
//! }
//!
//! async fn create(ctx: &mut Context) -> Result<(), Status> {
//!     Ok(())
//! }
//!
//! async fn graphql(ctx: &mut Context) -> Result<(), Status> {
//!     Ok(())
//! }
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let router = Router::new()
//!         .gate(gate)
//!         .on("/restful", get(query).post(create))
//!         .on("/graphql", allow([Method::GET, Method::POST], graphql));
//!     let app = App::new()
//!         .end(router.routes("/api")?);
//!     let (addr, server) = app.run()?;
//!     spawn(server);
//!     let resp = reqwest::get(&format!("http://{}/api/restful", addr)).await?;
//!     assert_eq!(StatusCode::OK, resp.status());
//!
//!     let resp = reqwest::get(&format!("http://{}/restful", addr)).await?;
//!     assert_eq!(StatusCode::NOT_FOUND, resp.status());
//!     Ok(())
//! }
//! ```
//!

mod endpoints;
mod err;
mod p
Download .txt
gitextract_am2piau4/

├── .github/
│   └── workflows/
│       ├── clippy.yml
│       ├── code-coverage.yml
│       ├── nightly-test.yml
│       ├── release.yml
│       ├── security-audit.yml
│       └── stable-test.yml
├── .gitignore
├── .vscode/
│   └── launch.json
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── assets/
│   ├── author.txt
│   ├── cert.pem
│   ├── css/
│   │   └── table.css
│   ├── key.pem
│   └── welcome.html
├── examples/
│   ├── echo.rs
│   ├── hello-world.rs
│   ├── https.rs
│   ├── restful-api.rs
│   ├── serve-file.rs
│   ├── websocket-echo.rs
│   └── welcome.rs
├── integration/
│   ├── diesel-example/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── src/
│   │   │   ├── bin/
│   │   │   │   └── api.rs
│   │   │   ├── data_object.rs
│   │   │   ├── endpoints.rs
│   │   │   ├── lib.rs
│   │   │   ├── models.rs
│   │   │   └── schema.rs
│   │   └── tests/
│   │       └── restful.rs
│   ├── juniper-example/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   └── src/
│   │       ├── main.rs
│   │       ├── models.rs
│   │       └── schema.rs
│   ├── multipart-example/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── assets/
│   │   │   └── index.html
│   │   └── src/
│   │       └── main.rs
│   └── websocket-example/
│       ├── Cargo.toml
│       ├── README.md
│       └── src/
│           └── main.rs
├── roa/
│   ├── Cargo.toml
│   ├── README.md
│   ├── src/
│   │   ├── body/
│   │   │   ├── file/
│   │   │   │   ├── content_disposition.rs
│   │   │   │   └── help.rs
│   │   │   └── file.rs
│   │   ├── body.rs
│   │   ├── compress.rs
│   │   ├── cookie.rs
│   │   ├── cors.rs
│   │   ├── forward.rs
│   │   ├── jsonrpc.rs
│   │   ├── jwt.rs
│   │   ├── lib.rs
│   │   ├── logger.rs
│   │   ├── query.rs
│   │   ├── router/
│   │   │   ├── endpoints/
│   │   │   │   ├── dispatcher.rs
│   │   │   │   └── guard.rs
│   │   │   ├── endpoints.rs
│   │   │   ├── err.rs
│   │   │   └── path.rs
│   │   ├── router.rs
│   │   ├── stream.rs
│   │   ├── tcp/
│   │   │   ├── incoming.rs
│   │   │   └── listener.rs
│   │   ├── tcp.rs
│   │   ├── tls/
│   │   │   ├── incoming.rs
│   │   │   └── listener.rs
│   │   ├── tls.rs
│   │   └── websocket.rs
│   └── templates/
│       └── user.html
├── roa-async-std/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       ├── lib.rs
│       ├── listener.rs
│       ├── net.rs
│       └── runtime.rs
├── roa-core/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       ├── app/
│       │   ├── future.rs
│       │   ├── runtime.rs
│       │   └── stream.rs
│       ├── app.rs
│       ├── body.rs
│       ├── context/
│       │   └── storage.rs
│       ├── context.rs
│       ├── err.rs
│       ├── executor.rs
│       ├── group.rs
│       ├── lib.rs
│       ├── middleware.rs
│       ├── request.rs
│       ├── response.rs
│       └── state.rs
├── roa-diesel/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       ├── async_ext.rs
│       ├── lib.rs
│       └── pool.rs
├── roa-juniper/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       └── lib.rs
├── rustfmt.toml
├── src/
│   └── lib.rs
├── templates/
│   └── directory.html
└── tests/
    ├── logger.rs
    ├── restful.rs
    └── serve-file.rs
Download .txt
SYMBOL INDEX (611 symbols across 64 files)

FILE: examples/echo.rs
  function echo (line 12) | async fn echo(ctx: &mut Context) -> roa::Result {
  function main (line 19) | async fn main() -> Result<(), Box<dyn StdError>> {

FILE: examples/hello-world.rs
  function main (line 11) | async fn main() -> anyhow::Result<()> {

FILE: examples/https.rs
  function serve_file (line 17) | async fn serve_file(ctx: &mut Context) -> roa::Result {
  function main (line 23) | async fn main() -> Result<(), Box<dyn StdError>> {

FILE: examples/restful-api.rs
  type User (line 25) | struct User {
  type Database (line 31) | struct Database {
    method new (line 36) | fn new() -> Self {
    method create (line 42) | async fn create(&self, user: User) -> usize {
    method retrieve (line 46) | async fn retrieve(&self, id: usize) -> Result<User> {
    method update (line 53) | async fn update(&self, id: usize, new_user: &mut User) -> Result {
    method delete (line 63) | async fn delete(&self, id: usize) -> Result<User> {
  function create_user (line 71) | async fn create_user(ctx: &mut Context<Database>) -> Result {
  function get_user (line 79) | async fn get_user(ctx: &mut Context<Database>) -> Result {
  function update_user (line 85) | async fn update_user(ctx: &mut Context<Database>) -> Result {
  function delete_user (line 92) | async fn delete_user(ctx: &mut Context<Database>) -> Result {
  function main (line 99) | async fn main() -> StdResult<(), Box<dyn std::error::Error>> {

FILE: examples/serve-file.rs
  type Dir (line 26) | struct Dir<'a> {
  type DirInfo (line 33) | struct DirInfo {
  type FileInfo (line 39) | struct FileInfo {
  function new (line 47) | fn new(title: &'a str, root: &'a str) -> Self {
  function path_checker (line 57) | async fn path_checker(ctx: &mut Context, next: Next<'_>) -> Result {
  function serve_path (line 65) | async fn serve_path(ctx: &mut Context) -> Result {
  function serve_root (line 79) | async fn serve_root(ctx: &mut Context) -> Result {
  function serve_dir (line 83) | async fn serve_dir(ctx: &mut Context, path: &str) -> Result {
  function format_time (line 119) | fn format_time(time: SystemTime) -> String {
  function main (line 125) | async fn main() -> StdResult<(), Box<dyn std::error::Error>> {

FILE: examples/websocket-echo.rs
  function main (line 18) | async fn main() -> Result<(), Box<dyn StdError>> {

FILE: examples/welcome.rs
  function main (line 13) | async fn main() -> Result<(), Box<dyn StdError>> {

FILE: integration/diesel-example/src/bin/api.rs
  function main (line 9) | async fn main() -> anyhow::Result<()> {

FILE: integration/diesel-example/src/data_object.rs
  type NewPost (line 8) | pub struct NewPost {

FILE: integration/diesel-example/src/endpoints.rs
  function post_router (line 14) | pub fn post_router() -> Router<State> {
  function create_post (line 20) | async fn create_post(ctx: &mut Context<State>) -> Result {
  function get_post (line 38) | async fn get_post(ctx: &mut Context<State>) -> Result {
  function update_post (line 49) | async fn update_post(ctx: &mut Context<State>) -> Result {
  function delete_post (line 71) | async fn delete_post(ctx: &mut Context<State>) -> Result {

FILE: integration/diesel-example/src/lib.rs
  type State (line 14) | pub struct State(pub Pool<SqliteConnection>);
    method as_ref (line 17) | fn as_ref(&self) -> &Pool<SqliteConnection> {
  function create_pool (line 22) | pub fn create_pool() -> anyhow::Result<State> {

FILE: integration/diesel-example/src/models.rs
  type Post (line 5) | pub struct Post {

FILE: integration/diesel-example/tests/restful.rs
  type NewPost (line 11) | pub struct NewPost<'a> {
  function eq (line 18) | fn eq(&self, other: &Post) -> bool {
  function test (line 24) | async fn test() -> anyhow::Result<()> {

FILE: integration/juniper-example/src/main.rs
  type NewPost (line 32) | struct NewPost {
  type Query (line 38) | struct Query;
    method post (line 44) | async fn post(
  type Mutation (line 64) | struct Mutation;
    method create_post (line 70) | async fn create_post(
    method update_post (line 91) | async fn update_post(
    method delete_post (line 120) | async fn delete_post(&self, ctx: &JuniperContext<State>, id: i32) -> F...
  function main (line 136) | async fn main() -> Result<(), Box<dyn StdError>> {

FILE: integration/juniper-example/src/models.rs
  type Post (line 7) | pub struct Post {

FILE: integration/multipart-example/src/main.rs
  function get_form (line 14) | async fn get_form(ctx: &mut Context) -> roa::Result {
  function post_file (line 19) | async fn post_file(ctx: &mut Context) -> roa::Result {
  function main (line 38) | async fn main() -> Result<(), Box<dyn StdError>> {

FILE: integration/websocket-example/src/main.rs
  type Sender (line 21) | type Sender = SplitSink<SocketStream, Message>;
  type Channel (line 22) | type Channel = Slab<Mutex<Sender>>;
  type SyncChannel (line 24) | struct SyncChannel(Arc<RwLock<Channel>>);
    method new (line 27) | fn new() -> Self {
    method broadcast (line 31) | async fn broadcast(&self, message: Message) {
    method send (line 40) | async fn send(&self, index: usize, message: Message) {
    method register (line 46) | async fn register(&self, sender: Sender) -> usize {
    method deregister (line 50) | async fn deregister(&self, index: usize) -> Sender {
  function handle_message (line 55) | async fn handle_message(
  function route (line 75) | fn route(prefix: &'static str) -> Result<RouteTable<SyncChannel>, Router...
  function main (line 104) | async fn main() -> Result<(), Box<dyn StdError>> {
  function echo (line 128) | async fn echo() -> Result<(), Box<dyn StdError>> {
  function broadcast (line 154) | async fn broadcast() -> Result<(), Box<dyn StdError>> {

FILE: roa-async-std/src/listener.rs
  type Listener (line 9) | pub trait Listener {
    method bind (line 14) | fn bind(self, addr: impl ToSocketAddrs) -> std::io::Result<(SocketAddr...
    method listen (line 17) | fn listen(
    method run (line 43) | fn run(self) -> std::io::Result<(SocketAddr, Self::Server)>;
    type Server (line 51) | type Server = Server<TcpIncoming, Self, Executor>;
    method bind (line 52) | fn bind(self, addr: impl ToSocketAddrs) -> std::io::Result<(SocketAddr...
    method listen (line 58) | fn listen(
    method run (line 68) | fn run(self) -> std::io::Result<(SocketAddr, Self::Server)> {
  function incoming (line 84) | async fn incoming() -> Result<(), Box<dyn Error>> {

FILE: roa-async-std/src/net.rs
  type TcpIncoming (line 18) | pub struct TcpIncoming {
    method bind (line 32) | pub fn bind(addr: impl ToSocketAddrs) -> io::Result<Self> {
    method from_std (line 38) | pub fn from_std(listener: StdListener) -> io::Result<Self> {
    method local_addr (line 51) | pub fn local_addr(&self) -> SocketAddr {
    method set_nodelay (line 56) | pub fn set_nodelay(&mut self, enabled: bool) -> &mut Self {
    method set_sleep_on_errors (line 76) | pub fn set_sleep_on_errors(&mut self, val: bool) {
    method poll_stream (line 81) | fn poll_stream(
    method fmt (line 177) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  type BoxedAccept (line 27) | type BoxedAccept<'a> =
  type Conn (line 141) | type Conn = AddrStream<AsyncStream<TcpStream>>;
  type Error (line 142) | type Error = io::Error;
  method poll_accept (line 145) | fn poll_accept(
  method drop (line 155) | fn drop(&mut self) {
  function is_connection_error (line 167) | fn is_connection_error(e: &io::Error) -> bool {
  function incoming (line 198) | async fn incoming() -> Result<(), Box<dyn Error>> {

FILE: roa-async-std/src/runtime.rs
  type FutureObj (line 7) | pub type FutureObj = Pin<Box<dyn 'static + Send + Future<Output = ()>>>;
  type BlockingObj (line 10) | pub type BlockingObj = Box<dyn 'static + Send + FnOnce()>;
  type Exec (line 20) | pub struct Exec;
  method spawn (line 24) | fn spawn(&self, fut: FutureObj) {
  method spawn_blocking (line 29) | fn spawn_blocking(&self, task: BlockingObj) {
  function exec (line 45) | async fn exec() -> Result<(), Box<dyn Error>> {

FILE: roa-core/src/app.rs
  type App (line 85) | pub struct App<S, T> {
  type HttpService (line 92) | pub struct HttpService<S, E> {
  function map_service (line 101) | fn map_service<U>(self, mapper: impl FnOnce(T) -> U) -> App<S, U> {
  function with_exec (line 117) | pub fn with_exec(state: S, exec: impl 'static + Send + Sync + Spawn) -> ...
  function gate (line 131) | pub fn gate<M>(self, middleware: M) -> App<S, Chain<T, M>>
  function end (line 139) | pub fn end<E>(self, endpoint: E) -> App<S, Arc<Chain<T, E>>>
  function accept (line 152) | pub fn accept<I, IO>(self, incoming: I) -> Server<I, Self, Executor>
  function http_service (line 166) | pub fn http_service(&self) -> HttpService<S, E>
  type AppFuture (line 190) | type AppFuture<S, E> =
  type Response (line 199) | type Response = HttpService<S, E>;
  type Error (line 200) | type Error = std::io::Error;
  type Future (line 201) | type Future = AppFuture<S, E>;
  function call (line 205) | fn call(&mut self, stream: &AddrStream<IO>) -> Self::Future {
  type HttpFuture (line 214) | type HttpFuture =
  type Response (line 222) | type Response = HttpResponse<HyperBody>;
  type Error (line 223) | type Error = Infallible;
  type Future (line 224) | type Future = HttpFuture;
  function call (line 228) | fn call(&mut self, req: HttpRequest<HyperBody>) -> Self::Future {
  function new (line 238) | pub fn new(endpoint: Arc<E>, remote_addr: SocketAddr, exec: Executor, st...
  function serve (line 249) | pub async fn serve(self, req: Request) -> Response
  method clone (line 276) | fn clone(&self) -> Self {
  function gate_simple (line 293) | async fn gate_simple() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa-core/src/app/future.rs
  type SendFuture (line 13) | pub struct SendFuture<F>(pub F);
  type Output (line 19) | type Output = F::Output;
  method poll (line 21) | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Ou...

FILE: roa-core/src/app/runtime.rs
  function state (line 8) | pub fn state(state: S) -> Self {
  function new (line 17) | pub fn new() -> Self {
  method default (line 24) | fn default() -> Self {
  type Exec (line 29) | pub struct Exec;
  method spawn (line 33) | fn spawn(&self, fut: FutureObj) {
  method spawn_blocking (line 38) | fn spawn_blocking(&self, task: BlockingObj) {

FILE: roa-core/src/app/stream.rs
  type AddrStream (line 12) | pub struct AddrStream<IO> {
  function new (line 23) | pub fn new(remote_addr: SocketAddr, stream: IO) -> AddrStream<IO> {
  method poll_read (line 37) | fn poll_read(
  method poll_write (line 56) | fn poll_write(
  method poll_flush (line 67) | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> P...
  method poll_shutdown (line 72) | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -...
  method fmt (line 78) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

FILE: roa-core/src/body.rs
  constant DEFAULT_CHUNK_SIZE (line 10) | const DEFAULT_CHUNK_SIZE: usize = 4096;
  type Body (line 36) | pub enum Body {
    method empty (line 54) | pub fn empty() -> Self {
    method once (line 60) | pub fn once(bytes: impl Into<Bytes>) -> Self {
    method stream (line 66) | pub fn stream<S>(stream: S) -> Self
    method write_stream (line 75) | pub fn write_stream(
    method write_reader (line 96) | pub fn write_reader(
    method write_chunk (line 105) | pub fn write_chunk(
    method write (line 115) | pub fn write(&mut self, data: impl Into<Bytes>) -> &mut Self {
  type Segment (line 49) | pub struct Segment(Option<Pin<Box<dyn Stream<Item = io::Result<Bytes>> +...
    method new (line 128) | fn new(stream: impl Stream<Item = io::Result<Bytes>> + Sync + Send + '...
  function from (line 135) | fn from(body: Body) -> Self {
  method default (line 146) | fn default() -> Self {
  type ReaderStream (line 151) | pub struct ReaderStream<R> {
  function new (line 158) | fn new(reader: R, chunk_size: usize) -> Self {
  type Item (line 167) | type Item = io::Result<Bytes>;
  method poll_next (line 169) | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Opt...
  type Item (line 185) | type Item = io::Result<Bytes>;
  method poll_next (line 187) | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Opt...
  type Item (line 201) | type Item = io::Result<Bytes>;
  method poll_next (line 203) | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Opt...
  function read_body (line 220) | async fn read_body(body: Body) -> io::Result<String> {
  function body_empty (line 227) | async fn body_empty() -> std::io::Result<()> {
  function body_single (line 234) | async fn body_single() -> std::io::Result<()> {
  function body_multiple (line 242) | async fn body_multiple() -> std::io::Result<()> {
  function body_composed (line 250) | async fn body_composed() -> std::io::Result<()> {

FILE: roa-core/src/context.rs
  type Context (line 36) | pub struct Context<S = ()> {
  function new (line 56) | pub(crate) fn new(request: Request, state: S, exec: Executor, remote_add...
  function uri (line 81) | pub fn uri(&self) -> &Uri {
  function method (line 100) | pub fn method(&self) -> &Method {
  function get (line 122) | pub fn get(&self, name: impl AsHeaderName) -> Option<&str> {
  function must_get (line 149) | pub fn must_get(&self, name: impl AsHeaderName) -> crate::Result<&str> {
  function status (line 174) | pub fn status(&self) -> StatusCode {
  function version (line 193) | pub fn version(&self) -> Version {
  function store_scoped (line 220) | pub fn store_scoped<SC, K, V>(&mut self, scope: SC, key: K, value: V) ->...
  function store (line 248) | pub fn store<K, V>(&mut self, key: K, value: V) -> Option<Arc<V>>
  function load_scoped (line 278) | pub fn load_scoped<'a, SC, V>(&self, key: &'a str) -> Option<Variable<'a...
  function load (line 305) | pub fn load<'a, V>(&self, key: &'a str) -> Option<Variable<'a, V>>
  type PublicScope (line 314) | struct PublicScope;
  type Target (line 317) | type Target = S;
  method deref (line 319) | fn deref(&self) -> &Self::Target {
  method deref_mut (line 326) | fn deref_mut(&mut self) -> &mut Self::Target {
  method clone (line 333) | fn clone(&self) -> Self {
  function status_and_version (line 354) | async fn status_and_version() -> Result<(), Box<dyn Error>> {
  type State (line 366) | struct State {
  function state (line 371) | async fn state() -> Result<(), Box<dyn Error>> {
  function must_get (line 390) | async fn must_get() -> Result<(), Box<dyn Error>> {

FILE: roa-core/src/context/storage.rs
  type Value (line 13) | pub trait Value: Any + Send + Sync {}
  type Storage (line 19) | pub struct Storage(HashMap<TypeId, HashMap<Cow<'static, str>, Arc<dyn An...
    method new (line 100) | pub fn new() -> Self {
    method insert (line 110) | pub fn insert<S, K, V>(&mut self, scope: S, key: K, value: V) -> Optio...
    method get (line 132) | pub fn get<'a, S, V>(&self, key: &'a str) -> Option<Variable<'a, V>>
  type Variable (line 44) | pub struct Variable<'a, V> {
  type Target (line 50) | type Target = V;
  method deref (line 52) | fn deref(&self) -> &Self::Target {
  function new (line 60) | fn new(key: &'a str, value: Arc<V>) -> Self {
  function value (line 66) | pub fn value(self) -> Arc<V> {
  function parse (line 77) | pub fn parse<T>(&self) -> Result<T, Status>
  method default (line 144) | fn default() -> Self {
  function storage (line 158) | fn storage() {
  function variable (line 181) | fn variable() {

FILE: roa-core/src/err.rs
  type Result (line 7) | pub type Result<R = ()> = StdResult<R, Status>;
  type Status (line 82) | pub struct Status {
    method new (line 127) | pub fn new(status_code: StatusCode, message: impl ToString, expose: bo...
    method from (line 141) | fn from(err: E) -> Self {
  method fmt (line 148) | fn fmt(&self, f: &mut Formatter<'_>) -> StdResult<(), std::fmt::Error> {

FILE: roa-core/src/executor.rs
  type FutureObj (line 10) | pub type FutureObj = Pin<Box<dyn 'static + Send + Future<Output = ()>>>;
  type BlockingObj (line 13) | pub type BlockingObj = Box<dyn 'static + Send + FnOnce()>;
  type Spawn (line 16) | pub trait Spawn {
    method spawn (line 18) | fn spawn(&self, fut: FutureObj);
    method spawn_blocking (line 21) | fn spawn_blocking(&self, task: BlockingObj);
    method spawn (line 96) | fn spawn(&self, fut: FutureObj) {
    method spawn_blocking (line 100) | fn spawn_blocking(&self, task: BlockingObj) {
  type Executor (line 26) | pub struct Executor(pub(crate) Arc<dyn 'static + Send + Sync + Spawn>);
    method spawn (line 34) | pub fn spawn<Fut>(&self, fut: Fut) -> JoinHandle<Fut::Output>
    method spawn_blocking (line 50) | pub fn spawn_blocking<T, R>(&self, task: T) -> JoinHandle<R>
    method execute (line 80) | fn execute(&self, fut: F) {
  type JoinHandle (line 29) | pub struct JoinHandle<T>(Receiver<T>);
  type Output (line 66) | type Output = T;
  method poll (line 68) | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Ou...
  type Exec (line 93) | pub struct Exec;
  function spawn (line 106) | async fn spawn() {
  function spawn_blocking (line 112) | async fn spawn_blocking() {

FILE: roa-core/src/group.rs
  type MiddlewareExt (line 7) | pub trait MiddlewareExt<S>: Sized + for<'a> Middleware<'a, S> {
    method chain (line 9) | fn chain<M>(self, next: M) -> Chain<Self, M>
    method end (line 17) | fn end<E>(self, next: E) -> Chain<Self, E>
    method shared (line 25) | fn shared(self) -> Shared<S>
  type EndpointExt (line 34) | pub trait EndpointExt<S>: Sized + for<'a> Endpoint<'a, S> {
    method boxed (line 36) | fn boxed(self) -> Boxed<S>
  type Chain (line 48) | pub struct Chain<T, U>(T, U);
  type Shared (line 51) | pub struct Shared<S>(Arc<dyn for<'a> Middleware<'a, S>>);
  type Boxed (line 54) | pub struct Boxed<S>(Box<dyn for<'a> Endpoint<'a, S>>);
  function handle (line 63) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> Re...
  function handle (line 76) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> Re...
  method clone (line 83) | fn clone(&self) -> Self {
  function call (line 94) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
  function call (line 106) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
  type Pusher (line 122) | struct Pusher {
    method new (line 128) | fn new(data: usize, vector: Arc<Mutex<Vec<usize>>>) -> Self {
    method handle (line 135) | async fn handle(&'a self, _ctx: &'a mut Context, next: Next<'a>) -> Re...
  function middleware_order (line 144) | async fn middleware_order() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa-core/src/middleware.rs
  type Middleware (line 60) | pub trait Middleware<'a, S = ()>: 'static + Sync + Send {
    method handle (line 62) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> ...
  method handle (line 73) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> Re...
  type Endpoint (line 162) | pub trait Endpoint<'a, S = ()>: 'static + Sync + Send {
    method call (line 164) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result;
  method call (line 175) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
  function handle (line 185) | async fn handle(&'a self, _ctx: &'a mut Context<S>, next: Next<'a>) -> R...
  function call (line 195) | async fn call(&'a self, _ctx: &'a mut Context<S>) -> Result {
  method call (line 204) | async fn call(&'a self, _ctx: &'a mut Context<S>) -> Result {
  method call (line 214) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
  function call (line 224) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
  method call (line 234) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
  type Next (line 311) | pub type Next<'a> = &'a mut (dyn Unpin + Future<Output = Result>);
  constant HELLO (line 321) | const HELLO: &str = "Hello, world";
  function status_endpoint (line 324) | async fn status_endpoint() {
  function string_endpoint (line 332) | async fn string_endpoint() {
  function static_slice_endpoint (line 347) | async fn static_slice_endpoint() {
  function redirect_endpoint (line 362) | async fn redirect_endpoint() {

FILE: roa-core/src/request.rs
  type Request (line 10) | pub struct Request {
    method take_raw (line 32) | pub fn take_raw(&mut self) -> http::Request<Body> {
    method raw_body (line 47) | pub fn raw_body(&mut self) -> Body {
    method stream (line 53) | pub fn stream(
    method reader (line 63) | pub fn reader(&mut self) -> impl AsyncRead + Sync + Send + Unpin + 'st...
    method from (line 70) | fn from(req: http::Request<Body>) -> Self {
  method default (line 85) | fn default() -> Self {
  function test (line 98) | async fn test(ctx: &mut Context) -> Result<(), Status> {
  function body_read (line 106) | async fn body_read() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa-core/src/response.rs
  type Response (line 9) | pub struct Response {
    method new (line 25) | pub(crate) fn new() -> Self {
    method into_resp (line 35) | fn into_resp(self) -> http::Response<hyper::Body> {
  type Target (line 51) | type Target = Body;
  method deref (line 53) | fn deref(&self) -> &Self::Target {
  method deref_mut (line 60) | fn deref_mut(&mut self) -> &mut Self::Target {
  function from (line 67) | fn from(value: Response) -> Self {
  method default (line 74) | fn default() -> Self {

FILE: roa-core/src/state.rs
  type State (line 28) | pub trait State: 'static + Clone + Send + Sync + Sized {}

FILE: roa-diesel/src/async_ext.rs
  type SqlQuery (line 12) | pub trait SqlQuery<Conn: 'static + Connection> {
    method execute (line 21) | async fn execute<E>(&self, exec: E) -> Result<usize>
    method load_data (line 42) | async fn load_data<U, Q>(&self, query: Q) -> Result<Vec<U>>
    method get_result (line 56) | async fn get_result<U, Q>(&self, query: Q) -> Result<Option<U>>
    method get_results (line 67) | async fn get_results<U, Q>(&self, query: Q) -> Result<Vec<U>>
    method first (line 80) | async fn first<U, Q>(&self, query: Q) -> Result<Option<U>>
  function execute (line 94) | async fn execute<E>(&self, exec: E) -> Result<usize>
  function load_data (line 124) | async fn load_data<U, Q>(&self, query: Q) -> Result<Vec<U>>
  function get_result (line 147) | async fn get_result<U, Q>(&self, query: Q) -> Result<Option<U>>
  function get_results (line 167) | async fn get_results<U, Q>(&self, query: Q) -> Result<Vec<U>>
  function first (line 184) | async fn first<U, Q>(&self, query: Q) -> Result<Option<U>>

FILE: roa-diesel/src/pool.rs
  type Pool (line 9) | pub type Pool<Conn> = r2d2::Pool<ConnectionManager<Conn>>;
  type WrapConnection (line 12) | pub type WrapConnection<Conn> = PooledConnection<ConnectionManager<Conn>>;
  function make_pool (line 28) | pub fn make_pool<Conn>(url: impl Into<String>) -> Result<Pool<Conn>, Poo...
  function builder (line 36) | pub fn builder<Conn>() -> Builder<ConnectionManager<Conn>>
  type AsyncPool (line 45) | pub trait AsyncPool<Conn>
    method get_conn (line 76) | async fn get_conn(&self) -> Result<WrapConnection<Conn>, Status>;
    method get_timeout (line 82) | async fn get_timeout(&self, timeout: Duration) -> Result<WrapConnectio...
    method pool_state (line 85) | async fn pool_state(&self) -> r2d2::State;
  function get_conn (line 95) | async fn get_conn(&self) -> Result<WrapConnection<Conn>, Status> {
  function get_timeout (line 101) | async fn get_timeout(&self, timeout: Duration) -> Result<WrapConnection<...
  function pool_state (line 110) | async fn pool_state(&self) -> r2d2::State {

FILE: roa-juniper/src/lib.rs
  type JuniperContext (line 18) | pub struct JuniperContext<S>(Context<S>);
  type Target (line 23) | type Target = Context<S>;
  method deref (line 25) | fn deref(&self) -> &Self::Target {
  method deref_mut (line 31) | fn deref_mut(&mut self) -> &mut Self::Target {
  type GraphQL (line 37) | pub struct GraphQL<QueryT, MutationT, SubscriptionT, Sca>(
  function call (line 60) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {

FILE: roa/src/body.rs
  type PowerBody (line 107) | pub trait PowerBody {
    method read (line 109) | async fn read(&mut self) -> Result<Vec<u8>>;
    method read_json (line 114) | async fn read_json<B>(&mut self) -> Result<B>
    method read_form (line 121) | async fn read_form<B>(&mut self) -> Result<B>
    method read_multipart (line 128) | async fn read_multipart(&mut self) -> Result<Multipart>;
    method write_json (line 133) | fn write_json<B>(&mut self, data: &B) -> Result
    method render (line 140) | fn render<B>(&mut self, data: &B) -> Result
    method write (line 145) | fn write<B>(&mut self, data: B)
    method write_reader (line 150) | fn write_reader<B>(&mut self, reader: B)
    method write_file (line 157) | async fn write_file<P>(&mut self, path: P, typ: DispositionType) -> Re...
    method read (line 165) | async fn read(&mut self) -> Result<Vec<u8>> {
    method read_json (line 176) | async fn read_json<B>(&mut self) -> Result<B>
    method read_form (line 189) | async fn read_form<B>(&mut self) -> Result<B>
    method read_multipart (line 200) | async fn read_multipart(&mut self) -> Result<Multipart> {
    method write_json (line 221) | fn write_json<B>(&mut self, data: &B) -> Result
    method render (line 232) | fn render<B>(&mut self, data: &B) -> Result
    method write (line 244) | fn write<B>(&mut self, data: B)
    method write_reader (line 253) | fn write_reader<B>(&mut self, reader: B)
    method write_file (line 263) | async fn write_file<P>(&mut self, path: P, typ: DispositionType) -> Re...
  type UserDto (line 287) | struct UserDto {
  type User (line 294) | struct User<'a> {
  function eq (line 300) | fn eq(&self, other: &UserDto) -> bool {
  constant USER (line 306) | const USER: User = User {
  function read_json (line 313) | async fn read_json() -> Result<(), Box<dyn Error>> {
  function read_form (line 334) | async fn read_form() -> Result<(), Box<dyn Error>> {
  function render (line 355) | async fn render() -> Result<(), Box<dyn Error>> {
  function write (line 368) | async fn write() -> Result<(), Box<dyn Error>> {
  function write_octet (line 383) | async fn write_octet() -> Result<(), Box<dyn Error>> {
  constant FILE_PATH (line 415) | const FILE_PATH: &str = "../assets/author.txt";
  constant FILE_NAME (line 416) | const FILE_NAME: &str = "author.txt";
  constant FIELD_NAME (line 417) | const FIELD_NAME: &str = "file";
  function post_file (line 419) | async fn post_file(ctx: &mut Context) -> crate::Result {
  function upload (line 440) | async fn upload() -> Result<(), Box<dyn StdError>> {

FILE: roa/src/body/file.rs
  function write_file (line 15) | pub async fn write_file<S: State>(

FILE: roa/src/body/file/content_disposition.rs
  constant HTTP_VALUE (line 12) | const HTTP_VALUE: &AsciiSet = &CONTROLS
  type DispositionType (line 36) | pub enum DispositionType {
  type ContentDisposition (line 45) | pub struct ContentDisposition {
    method new (line 53) | pub(crate) fn new(typ: DispositionType, filename: Option<&str>) -> Self {
  type Error (line 63) | type Error = Status;
  method try_from (line 65) | fn try_from(value: ContentDisposition) -> Result<Self, Self::Error> {
  method fmt (line 75) | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
  method fmt (line 88) | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {

FILE: roa/src/body/file/help.rs
  constant BUG_HELP (line 4) | const BUG_HELP: &str =
  function bug_report (line 8) | pub fn bug_report(message: impl ToString) -> Status {

FILE: roa/src/compress.rs
  type Compress (line 35) | pub struct Compress(pub Level);
    method handle (line 146) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> ...
  type Encoding (line 39) | enum Encoding {
    method parse (line 54) | fn parse(s: &str) -> Result<Option<Encoding>> {
    method to_header_value (line 71) | fn to_header_value(self) -> HeaderValue {
  function select_encoding (line 82) | fn select_encoding(headers: &HeaderMap) -> Result<Option<Encoding>> {
  function accept_encodings (line 101) | fn accept_encodings(headers: &HeaderMap) -> Result<Vec<(Option<Encoding>...
  method default (line 137) | fn default() -> Self {
  type Consumer (line 199) | struct Consumer<S> {
  type Item (line 208) | type Item = io::Result<Bytes>;
  method poll_next (line 209) | fn poll_next(
  type Assert (line 227) | struct Assert(usize);
    method handle (line 231) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> ...
  function end (line 243) | async fn end(ctx: &mut Context) -> crate::Result {
  function compress (line 248) | async fn compress() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/cookie.rs
  type CookieScope (line 33) | struct CookieScope;
  type CookieGetter (line 60) | pub trait CookieGetter {
    method must_cookie (line 62) | fn must_cookie(&mut self, name: &str) -> Result<Arc<Cookie<'static>>>;
    method cookie (line 86) | fn cookie(&self, name: &str) -> Option<Arc<Cookie<'static>>>;
    method must_cookie (line 137) | fn must_cookie(&mut self, name: &str) -> Result<Arc<Cookie<'static>>> {
    method cookie (line 155) | fn cookie(&self, name: &str) -> Option<Arc<Cookie<'static>>> {
  type CookieSetter (line 90) | pub trait CookieSetter {
    method set_cookie (line 112) | fn set_cookie(&mut self, cookie: Cookie<'_>) -> Result;
    method set_cookie (line 162) | fn set_cookie(&mut self, cookie: Cookie<'_>) -> Result {
  function cookie_parser (line 117) | pub async fn cookie_parser<S>(ctx: &mut Context<S>, next: Next<'_>) -> R...
  function must (line 181) | async fn must(ctx: &mut Context) -> crate::Result {
  function none (line 186) | async fn none(ctx: &mut Context) -> crate::Result {
  function parser (line 192) | async fn parser() -> Result<(), Box<dyn std::error::Error>> {
  function cookie (line 217) | async fn cookie() -> Result<(), Box<dyn std::error::Error>> {
  function cookie_action (line 251) | async fn cookie_action() -> Result<(), Box<dyn std::error::Error>> {
  function set_cookie (line 271) | async fn set_cookie() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/cors.rs
  type Cors (line 56) | pub struct Cors {
    method new (line 78) | pub fn new() -> Self {
    method builder (line 83) | pub fn builder() -> Builder {
    method handle (line 241) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> ...
  type Builder (line 67) | pub struct Builder {
    method allow_credentials (line 90) | pub fn allow_credentials(mut self, allow: bool) -> Self {
    method allow_method (line 96) | pub fn allow_method(mut self, method: Method) -> Self {
    method allow_methods (line 102) | pub fn allow_methods(mut self, methods: impl IntoIterator<Item = Metho...
    method allow_header (line 112) | pub fn allow_header<H>(mut self, header: H) -> Self
    method allow_headers (line 127) | pub fn allow_headers<I>(mut self, headers: I) -> Self
    method expose_header (line 145) | pub fn expose_header<H>(mut self, header: H) -> Self
    method expose_headers (line 160) | pub fn expose_headers<I>(mut self, headers: I) -> Self
    method allow_origin (line 178) | pub fn allow_origin<H>(mut self, origin: H) -> Self
    method max_age (line 188) | pub fn max_age(mut self, seconds: u64) -> Self {
    method build (line 198) | pub fn build(self) -> Cors {
  function end (line 344) | async fn end(ctx: &mut Context) -> crate::Result {
  function default_cors (line 350) | async fn default_cors() -> Result<(), Box<dyn std::error::Error>> {
  function configured_cors (line 462) | async fn configured_cors() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/forward.rs
  type Forward (line 10) | pub trait Forward {
    method host (line 28) | fn host(&self) -> Option<&str>;
    method client_ip (line 44) | fn client_ip(&self) -> IpAddr;
    method forwarded_ips (line 60) | fn forwarded_ips(&self) -> Vec<IpAddr>;
    method forwarded_proto (line 78) | fn forwarded_proto(&self) -> Option<&str>;
    method host (line 83) | fn host(&self) -> Option<&str> {
    method client_ip (line 88) | fn client_ip(&self) -> IpAddr {
    method forwarded_ips (line 98) | fn forwarded_ips(&self) -> Vec<IpAddr> {
    method forwarded_proto (line 111) | fn forwarded_proto(&self) -> Option<&str> {
  function host (line 127) | async fn host() -> Result<(), Box<dyn std::error::Error>> {
  function host_err (line 153) | async fn host_err() -> Result<(), Box<dyn std::error::Error>> {
  function client_ip (line 167) | async fn client_ip() -> Result<(), Box<dyn std::error::Error>> {
  function forwarded_proto (line 193) | async fn forwarded_proto() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/jsonrpc.rs
  type RpcEndpoint (line 60) | pub struct RpcEndpoint<R>(pub Server<R>);
  function call (line 69) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {

FILE: roa/src/jwt.rs
  type JwtScope (line 83) | struct JwtScope;
  function set_www_authenticate (line 90) | fn set_www_authenticate<S>(ctx: &mut Context<S>) {
  function guard_not_set (line 98) | fn guard_not_set() -> Status {
  type JwtVerifier (line 122) | pub trait JwtVerifier<S> {
    method claims (line 124) | fn claims<C>(&self) -> Result<C>
    method verify (line 130) | fn verify<C>(&mut self, validation: &Validation) -> Result<C>
  function guard (line 136) | pub fn guard(secret: DecodingKey) -> JwtGuard {
  type JwtGuard (line 147) | pub struct JwtGuard {
    method new (line 154) | pub fn new(secret: DecodingKey, validation: Validation) -> Self {
    method verify (line 163) | fn verify<S>(&self, ctx: &Context<S>) -> Option<(Bearer, Value)> {
    method handle (line 175) | async fn handle(&'a self, ctx: &'a mut Context<S>, next: Next<'a>) -> ...
  function claims (line 193) | fn claims<C>(&self) -> Result<C>
  function verify (line 205) | fn verify<C>(&mut self, validation: &Validation) -> Result<C>
  type User (line 239) | struct User {
  constant SECRET (line 247) | const SECRET: &[u8] = b"123456";
  function claims (line 250) | async fn claims() -> Result<(), Box<dyn std::error::Error>> {
  function jwt_verify_not_set (line 337) | async fn jwt_verify_not_set() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/logger.rs
  type StreamLogger (line 41) | enum StreamLogger<S> {
  type LogTask (line 54) | struct LogTask {
    method log (line 65) | fn log(&self) -> JoinHandle<()> {
  type Item (line 91) | type Item = io::Result<Bytes>;
  method poll_next (line 93) | fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Po...
  function logger (line 125) | pub async fn logger<S>(ctx: &mut Context<S>, next: Next<'_>) -> Result {

FILE: roa/src/query.rs
  type QueryScope (line 36) | struct QueryScope;
  type Query (line 76) | pub trait Query {
    method must_query (line 105) | fn must_query<'a>(&self, name: &'a str) -> Result<Variable<'a, String>>;
    method query (line 135) | fn query<'a>(&self, name: &'a str) -> Option<Variable<'a, String>>;
    method must_query (line 151) | fn must_query<'a>(&self, name: &'a str) -> Result<Variable<'a, String>> {
    method query (line 161) | fn query<'a>(&self, name: &'a str) -> Option<Variable<'a, String>> {
  function query_parser (line 140) | pub async fn query_parser<S>(ctx: &mut Context<S>, next: Next<'_>) -> Re...
  function query (line 176) | async fn query() -> Result<(), Box<dyn std::error::Error>> {
  function query_parse (line 197) | async fn query_parse() -> Result<(), Box<dyn std::error::Error>> {
  function query_action (line 216) | async fn query_action() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/router.rs
  type RouterScope (line 73) | struct RouterScope;
  type RouterParam (line 107) | pub trait RouterParam {
    method must_param (line 109) | fn must_param<'a>(&self, name: &'a str) -> Result<Variable<'a, String>>;
    method param (line 139) | fn param<'a>(&self, name: &'a str) -> Option<Variable<'a, String>>;
    method must_param (line 297) | fn must_param<'a>(&self, name: &'a str) -> Result<Variable<'a, String>> {
    method param (line 307) | fn param<'a>(&self, name: &'a str) -> Option<Variable<'a, String>> {
  type Router (line 143) | pub struct Router<S> {
  type RouteTable (line 149) | pub struct RouteTable<S> {
  function new (line 159) | pub fn new() -> Self {
  function on (line 167) | pub fn on(mut self, path: &'static str, endpoint: impl for<'a> Endpoint<...
  function register (line 174) | fn register(&self, endpoint: impl for<'a> Endpoint<'a, S>) -> Boxed<S> {
  function include (line 179) | pub fn include(mut self, prefix: &'static str, router: Router<S>) -> Self {
  function gate (line 188) | pub fn gate(self, next: impl for<'a> Middleware<'a, S>) -> Router<S> {
  function routes (line 200) | pub fn routes(self, prefix: &'static str) -> StdResult<RouteTable<S>, Ro...
  function new (line 213) | fn new() -> Self {
  function insert (line 221) | fn insert(
  method default (line 242) | fn default() -> Self {
  method default (line 251) | fn default() -> Self {
  function call (line 262) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {
  function gate (line 323) | async fn gate(ctx: &mut Context, next: Next<'_>) -> Result<(), Status> {
  function test (line 328) | async fn test(ctx: &mut Context) -> Result<(), Status> {
  function gate_test (line 335) | async fn gate_test() -> Result<(), Box<dyn std::error::Error>> {
  function route (line 346) | async fn route() -> Result<(), Box<dyn std::error::Error>> {
  function conflict_path (line 358) | fn conflict_path() -> Result<(), Box<dyn std::error::Error>> {
  function route_not_found (line 369) | async fn route_not_found() -> Result<(), Box<dyn std::error::Error>> {
  function non_utf8_uri (line 379) | async fn non_utf8_uri() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/router/endpoints.rs
  function method_not_allowed (line 8) | fn method_not_allowed(method: &Method) -> Result {

FILE: roa/src/router/endpoints/dispatcher.rs
  type Dispatcher (line 63) | pub struct Dispatcher<S>(HashMap<Method, Box<dyn for<'a> Endpoint<'a, S>...
  method default (line 89) | fn default() -> Self {
  function call (line 100) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result<()> {

FILE: roa/src/router/endpoints/guard.rs
  constant ALL_METHODS (line 9) | const ALL_METHODS: [Method; 9] = [
  type Guard (line 22) | pub struct Guard<E> {
  function hash_set (line 28) | fn hash_set(methods: impl AsRef<[Method]>) -> HashSet<Method> {
  function allow (line 47) | pub fn allow<E>(methods: impl AsRef<[Method]>, endpoint: E) -> Guard<E> {
  function deny (line 69) | pub fn deny<E>(methods: impl AsRef<[Method]>, endpoint: E) -> Guard<E> {
  function call (line 84) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result {

FILE: roa/src/router/err.rs
  type RouterError (line 7) | pub enum RouterError {
    method from (line 54) | fn from(conflict: Conflict) -> Self {
  type Conflict (line 17) | pub enum Conflict {
  method fmt (line 27) | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
  method fmt (line 43) | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
  function conflict_to_string (line 69) | fn conflict_to_string() {
  function err_to_string (line 89) | fn err_to_string() {

FILE: roa/src/router/path.rs
  constant WILDCARD (line 10) | const WILDCARD: &str = r"\*\{(?P<var>\w*)\}";
  constant VARIABLE (line 13) | const VARIABLE: &str = r"/:(?P<var>\w*)/";
  function standardize_path (line 16) | pub fn standardize_path(raw_path: &str) -> String {
  function join_path (line 21) | pub fn join_path<'a>(paths: impl 'a + AsRef<[&'a str]>) -> String {
  function must_build (line 32) | fn must_build(pattern: &str) -> Regex {
  type Path (line 45) | pub enum Path {
  type RegexPath (line 52) | pub struct RegexPath {
  type Err (line 59) | type Err = RouterError;
  method from_str (line 60) | fn from_str(raw_path: &str) -> Result<Self, Self::Err> {
  function path_to_regexp (line 73) | fn path_to_regexp(path: &str) -> Result<Option<(String, HashSet<String>)...
  function var_regex_match (line 136) | fn var_regex_match(path: &str) {
  function var_regex_mismatch (line 146) | fn var_regex_mismatch(path: &str) {
  function wildcard_regex_match (line 155) | fn wildcard_regex_match(path: &str) {
  function wildcard_regex_mismatch (line 164) | fn wildcard_regex_mismatch(path: &str) {
  function path_to_regexp_dynamic_pattern (line 174) | fn path_to_regexp_dynamic_pattern(path: &str) -> String {
  function path_to_regexp_static (line 180) | fn path_to_regexp_static(path: &str) {
  function path_to_regexp_err (line 189) | fn path_to_regexp_err(path: &str) {
  function path_match (line 193) | fn path_match(pattern: &str, path: &str) {
  function path_not_match (line 201) | fn path_not_match(pattern: &str, path: &str) {
  function single_variable_path_match (line 214) | fn single_variable_path_match(path: &str) {
  function multiple_variable_path_match (line 220) | fn multiple_variable_path_match(path: &str) {
  function segment_wildcard_path_match (line 226) | fn segment_wildcard_path_match(path: &str) {
  function full_wildcard_path_match (line 232) | fn full_wildcard_path_match(path: &str) {
  function variable_path_not_match (line 238) | fn variable_path_not_match(path: &str) {
  function must_build_fails (line 244) | fn must_build_fails() {

FILE: roa/src/stream.rs
  type AsyncStream (line 13) | pub struct AsyncStream<IO>(pub IO);
  method poll_read (line 20) | fn poll_read(
  method poll_write (line 36) | fn poll_write(
  method poll_flush (line 45) | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io...
  method poll_shutdown (line 50) | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll...
  method poll_read (line 61) | fn poll_read(
  method poll_write (line 79) | fn poll_write(
  method poll_flush (line 90) | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io...
  method poll_close (line 95) | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io...

FILE: roa/src/tcp/incoming.rs
  type TcpIncoming (line 18) | pub struct TcpIncoming {
    method bind (line 32) | pub fn bind(addr: impl ToSocketAddrs) -> io::Result<Self> {
    method from_std (line 38) | pub fn from_std(listener: StdListener) -> io::Result<Self> {
    method local_addr (line 52) | pub fn local_addr(&self) -> SocketAddr {
    method set_nodelay (line 57) | pub fn set_nodelay(&mut self, enabled: bool) -> &mut Self {
    method set_sleep_on_errors (line 77) | pub fn set_sleep_on_errors(&mut self, val: bool) {
    method poll_stream (line 82) | fn poll_stream(
    method fmt (line 178) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  type BoxedAccept (line 27) | type BoxedAccept<'a> =
  type Conn (line 142) | type Conn = AddrStream<TcpStream>;
  type Error (line 143) | type Error = io::Error;
  method poll_accept (line 146) | fn poll_accept(
  method drop (line 156) | fn drop(&mut self) {
  function is_connection_error (line 168) | fn is_connection_error(e: &io::Error) -> bool {

FILE: roa/src/tcp/listener.rs
  type Listener (line 9) | pub trait Listener {
    method bind (line 14) | fn bind(self, addr: impl ToSocketAddrs) -> std::io::Result<(SocketAddr...
    method listen (line 17) | fn listen(
    method run (line 45) | fn run(self) -> std::io::Result<(SocketAddr, Self::Server)>;
    type Server (line 53) | type Server = Server<TcpIncoming, Self, Executor>;
    method bind (line 54) | fn bind(self, addr: impl ToSocketAddrs) -> std::io::Result<(SocketAddr...
    method listen (line 60) | fn listen(
    method run (line 70) | fn run(self) -> std::io::Result<(SocketAddr, Self::Server)> {

FILE: roa/src/tls/incoming.rs
  type TlsIncoming (line 17) | pub struct TlsIncoming<I> {
  type AcceptFuture (line 22) | type AcceptFuture<IO> =
  type WrapTlsStream (line 26) | pub enum WrapTlsStream<IO> {
  function poll_handshake (line 37) | fn poll_handshake(
  method poll_read (line 50) | fn poll_read(
  method poll_write (line 69) | fn poll_write(
  method poll_write_vectored (line 83) | fn poll_write_vectored(
  method poll_flush (line 97) | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io...
  method poll_shutdown (line 107) | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll...
  function new (line 120) | pub fn new(incoming: I, config: ServerConfig) -> Self {
  type Target (line 129) | type Target = I;
  method deref (line 130) | fn deref(&self) -> &Self::Target {
  method deref_mut (line 136) | fn deref_mut(&mut self) -> &mut Self::Target {
  type Conn (line 146) | type Conn = AddrStream<WrapTlsStream<IO>>;
  type Error (line 147) | type Error = I::Error;
  method poll_accept (line 150) | fn poll_accept(

FILE: roa/src/tls/listener.rs
  function bind (line 12) | pub fn bind(addr: impl ToSocketAddrs, config: ServerConfig) -> io::Resul...
  type TlsListener (line 19) | pub trait TlsListener {
    method bind_tls (line 24) | fn bind_tls(
    method listen_tls (line 31) | fn listen_tls(
    method run_tls (line 71) | fn run_tls(self, config: ServerConfig) -> std::io::Result<(SocketAddr,...
    type Server (line 79) | type Server = Server<TlsIncoming<TcpIncoming>, Self, Executor>;
    method bind_tls (line 80) | fn bind_tls(
    method listen_tls (line 90) | fn listen_tls(
    method run_tls (line 101) | fn run_tls(self, config: ServerConfig) -> std::io::Result<(SocketAddr,...
  function end (line 123) | async fn end(ctx: &mut Context) -> Result<(), Status> {
  function run_tls (line 129) | async fn run_tls() -> Result<(), Box<dyn std::error::Error>> {

FILE: roa/src/websocket.rs
  type SocketStream (line 41) | pub type SocketStream = WebSocketStream<Upgraded>;
  type Websocket (line 80) | pub struct Websocket<F, S, Fut>
  function config (line 103) | fn config(config: Option<WebSocketConfig>, task: F) -> Self {
  function new (line 113) | pub fn new(task: F) -> Self {
  function with_config (line 141) | pub fn with_config(config: WebSocketConfig, task: F) -> Self {
  function call (line 154) | async fn call(&'a self, ctx: &'a mut Context<S>) -> Result<(), Status> {

FILE: tests/logger.rs
  type TestLogger (line 12) | struct TestLogger {
    method enabled (line 16) | fn enabled(&self, metadata: &Metadata) -> bool {
    method log (line 19) | fn log(&self, record: &Record) {
    method flush (line 25) | fn flush(&self) {}
  function init (line 32) | fn init() -> anyhow::Result<()> {
  function log (line 39) | async fn log() -> anyhow::Result<()> {

FILE: tests/restful.rs
  type User (line 17) | struct User {
  type DB (line 23) | struct DB {
    method new (line 29) | fn new() -> Self {
    method add (line 36) | fn add(&mut self, user: User) -> usize {
    method delete_index (line 43) | fn delete_index(&mut self, name: &str, id: usize) {
    method delete (line 54) | fn delete(&mut self, id: usize) -> Option<User> {
    method get (line 64) | fn get(&self, id: usize) -> Option<&User> {
    method get_by_name (line 68) | fn get_by_name(&self, name: &str) -> Vec<(usize, &User)> {
    method update (line 78) | fn update(&mut self, id: usize, new_user: &mut User) -> bool {
  type State (line 94) | struct State(Arc<RwLock<DB>>);
    method new (line 97) | fn new(db: DB) -> Self {
    method add (line 101) | async fn add(&mut self, user: User) -> usize {
    method delete (line 105) | async fn delete(&mut self, id: usize) -> Option<User> {
    method get_user (line 109) | async fn get_user(&self, id: usize) -> Option<User> {
    method get_by_name (line 113) | async fn get_by_name(&self, name: &str) -> Vec<(usize, User)> {
    method get_all (line 123) | async fn get_all(&self) -> Vec<(usize, User)> {
    method update (line 133) | async fn update(&mut self, id: usize, new_user: &mut User) -> bool {
  function create_user (line 138) | async fn create_user(ctx: &mut Context<State>) -> roa::Result {
  function query_user (line 145) | async fn query_user(ctx: &mut Context<State>) -> roa::Result {
  function update_user (line 153) | async fn update_user(ctx: &mut Context<State>) -> roa::Result {
  function delete_user (line 163) | async fn delete_user(ctx: &mut Context<State>) -> roa::Result {
  function crud_router (line 171) | fn crud_router() -> Router<State> {
  function restful_crud (line 178) | async fn restful_crud() -> Result<(), Box<dyn std::error::Error>> {
  function create_batch (line 255) | async fn create_batch(ctx: &mut Context<State>) -> roa::Result {
  function query_batch (line 265) | async fn query_batch(ctx: &mut Context<State>) -> roa::Result {
  function batch_router (line 273) | fn batch_router() -> Router<State> {
  function batch (line 278) | async fn batch() -> Result<(), Box<dyn std::error::Error>> {

FILE: tests/serve-file.rs
  function serve_static_file (line 11) | async fn serve_static_file() -> Result<(), Box<dyn std::error::Error>> {
  function serve_router_variable (line 25) | async fn serve_router_variable() -> Result<(), Box<dyn std::error::Error...
  function serve_router_wildcard (line 41) | async fn serve_router_wildcard() -> Result<(), Box<dyn std::error::Error...
  function serve_gzip (line 57) | async fn serve_gzip() -> Result<(), Box<dyn std::error::Error>> {
Condensed preview — 112 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (370K chars).
[
  {
    "path": ".github/workflows/clippy.yml",
    "chars": 443,
    "preview": "on: [push, pull_request]\nname: Clippy\njobs:\n  clippy_check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/"
  },
  {
    "path": ".github/workflows/code-coverage.yml",
    "chars": 1192,
    "preview": "name: Code Coverage\non:\n  push:\n    branches:\n    - master\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - "
  },
  {
    "path": ".github/workflows/nightly-test.yml",
    "chars": 935,
    "preview": "name: Nightly Test\non:\n  push:\n  pull_request:\n  schedule:\n    - cron: '0 0 * * *'\n\njobs:\n  check:\n    runs-on: ubuntu-l"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 3725,
    "preview": "name: Release\non:\n  push:\n    branches:\n      - release\n    paths:\n      - '**/Cargo.toml'\n      - '.github/workflows/re"
  },
  {
    "path": ".github/workflows/security-audit.yml",
    "chars": 246,
    "preview": "name: Security Audit\non:\n  schedule:\n    - cron: '0 0 * * *'\njobs:\n  audit:\n    runs-on: ubuntu-latest\n    steps:\n      "
  },
  {
    "path": ".github/workflows/stable-test.yml",
    "chars": 1098,
    "preview": "on: [push, pull_request]\nname: Stable Test\njobs:\n  check:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n       "
  },
  {
    "path": ".gitignore",
    "chars": 59,
    "preview": "/target\n**/*.rs.bk\nCargo.lock\n**/upload/*\n.env\nnode_modules"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 19389,
    "preview": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 1093,
    "preview": "[package]\nname = \"roa-root\"\nversion = \"0.6.0\"\nauthors = [\"Hexilee <hexileee@gmail.com>\"]\nedition = \"2018\"\nlicense = \"MIT"
  },
  {
    "path": "LICENSE",
    "chars": 1050,
    "preview": "Copyright (c) 2020 Hexilee\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this softwar"
  },
  {
    "path": "Makefile",
    "chars": 324,
    "preview": "check:\n\tcargo check --all --features \"roa/full\"\nbuild: \n\tcargo build --all --features \"roa/full\"\ntest: \n\tcargo test --al"
  },
  {
    "path": "README.md",
    "chars": 2816,
    "preview": "<div align=\"center\">\n  <h1>Roa</h1>\n  <p><strong>Roa is an async web framework inspired by koajs, lightweight but powerf"
  },
  {
    "path": "assets/author.txt",
    "chars": 7,
    "preview": "Hexilee"
  },
  {
    "path": "assets/cert.pem",
    "chars": 1878,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV\nUzELMAkGA1UECAwCQ0ExCzAJBgN"
  },
  {
    "path": "assets/css/table.css",
    "chars": 291,
    "preview": "/* spacing */\n\ntable {\n    table-layout: fixed;\n    width: 80%;\n    border-collapse: collapse;\n}\n\nthead th {\n    text-al"
  },
  {
    "path": "assets/key.pem",
    "chars": 3242,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP\nn8k1sQEcer5BfAP986Sck3H"
  },
  {
    "path": "assets/welcome.html",
    "chars": 236,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Roa Framework</title>\n</head>\n<body>\n<h1>W"
  },
  {
    "path": "examples/echo.rs",
    "chars": 832,
    "preview": "//! RUST_LOG=info Cargo run --example echo,\n//! then request http://127.0.0.1:8000 with some payload.\n\nuse std::error::E"
  },
  {
    "path": "examples/hello-world.rs",
    "chars": 635,
    "preview": "//! RUST_LOG=info Cargo run --example hello-world,\n//! then request http://127.0.0.1:8000.\n\nuse log::info;\nuse roa::logg"
  },
  {
    "path": "examples/https.rs",
    "chars": 1543,
    "preview": "//! RUST_LOG=info Cargo run --example https,\n//! then request https://127.0.0.1:8000.\n\nuse std::error::Error as StdError"
  },
  {
    "path": "examples/restful-api.rs",
    "chars": 3181,
    "preview": "//! RUST_LOG=info Cargo run --example restful-api,\n//! then:\n//! - `curl 127.0.0.1:8000/user/0`\n//!     query user where"
  },
  {
    "path": "examples/serve-file.rs",
    "chars": 4086,
    "preview": "//! RUST_LOG=info cargo run --example serve-file,\n//! then request http://127.0.0.1:8000.\n\nuse std::borrow::Cow;\nuse std"
  },
  {
    "path": "examples/websocket-echo.rs",
    "chars": 1229,
    "preview": "//! RUST_LOG=info cargo run --example websocket-echo,\n//! then request ws://127.0.0.1:8000/chat.\n\nuse std::error::Error "
  },
  {
    "path": "examples/welcome.rs",
    "chars": 738,
    "preview": "//! RUST_LOG=info Cargo run --example welcome,\n//! then request http://127.0.0.1:8000 with some payload.\n\nuse std::error"
  },
  {
    "path": "integration/diesel-example/Cargo.toml",
    "chars": 703,
    "preview": "[package]\nname = \"diesel-example\"\nversion = \"0.1.0\"\nauthors = [\"Hexilee <hexileee@gmail.com>\"]\nedition = \"2018\"\n\n# See m"
  },
  {
    "path": "integration/diesel-example/README.md",
    "chars": 536,
    "preview": "```bash\nRUST_LOG=info cargo run --bin api\n```\n\n- `curl 127.0.0.1:8000/post/1`\n    query post where id=0 and published\n- "
  },
  {
    "path": "integration/diesel-example/src/bin/api.rs",
    "chars": 644,
    "preview": "use diesel_example::{create_pool, post_router};\nuse roa::logger::logger;\nuse roa::preload::*;\nuse roa::App;\nuse tracing:"
  },
  {
    "path": "integration/diesel-example/src/data_object.rs",
    "chars": 243,
    "preview": "use serde::Deserialize;\n\nuse crate::schema::posts;\n\n// for both transfer and access\n#[derive(Debug, Insertable, Deserial"
  },
  {
    "path": "integration/diesel-example/src/endpoints.rs",
    "chars": 2452,
    "preview": "use diesel::prelude::*;\nuse diesel::result::Error;\nuse roa::http::StatusCode;\nuse roa::preload::*;\nuse roa::router::{get"
  },
  {
    "path": "integration/diesel-example/src/lib.rs",
    "chars": 789,
    "preview": "#[macro_use]\nextern crate diesel;\n\nmod data_object;\nmod endpoints;\npub mod models;\npub mod schema;\n\nuse diesel::prelude:"
  },
  {
    "path": "integration/diesel-example/src/models.rs",
    "chars": 227,
    "preview": "use diesel::Queryable;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Queryable, Serialize, Deserialize)]\n"
  },
  {
    "path": "integration/diesel-example/src/schema.rs",
    "chars": 129,
    "preview": "table! {\n    posts (id) {\n        id -> Integer,\n        title -> Text,\n        body -> Text,\n        published -> Bool,"
  },
  {
    "path": "integration/diesel-example/tests/restful.rs",
    "chars": 2902,
    "preview": "use diesel_example::models::Post;\nuse diesel_example::{create_pool, post_router};\nuse roa::http::StatusCode;\nuse roa::pr"
  },
  {
    "path": "integration/juniper-example/Cargo.toml",
    "chars": 724,
    "preview": "[package]\nname = \"juniper-example\"\nversion = \"0.1.0\"\nauthors = [\"Hexilee <hexileee@gmail.com>\"]\nedition = \"2018\"\n\n# See "
  },
  {
    "path": "integration/juniper-example/README.md",
    "chars": 107,
    "preview": "```bash\nRUST_LOG=info cargo run\n```\n\nThen request http://127.0.0.1:8000, play with the GraphQL playground! "
  },
  {
    "path": "integration/juniper-example/src/main.rs",
    "chars": 4416,
    "preview": "#[macro_use]\nextern crate diesel;\n\nmod models;\nmod schema;\nuse std::error::Error as StdError;\n\nuse diesel::prelude::*;\nu"
  },
  {
    "path": "integration/juniper-example/src/models.rs",
    "chars": 281,
    "preview": "use diesel::Queryable;\nuse juniper::GraphQLObject;\nuse serde::Deserialize;\n\n#[derive(Debug, Clone, Queryable, Deserializ"
  },
  {
    "path": "integration/juniper-example/src/schema.rs",
    "chars": 129,
    "preview": "table! {\n    posts (id) {\n        id -> Integer,\n        title -> Text,\n        body -> Text,\n        published -> Bool,"
  },
  {
    "path": "integration/multipart-example/Cargo.toml",
    "chars": 491,
    "preview": "[package]\nname = \"multipart-example\"\nversion = \"0.1.0\"\nauthors = [\"Hexilee <hexileee@gmail.com>\"]\nedition = \"2018\"\n\n# Se"
  },
  {
    "path": "integration/multipart-example/README.md",
    "chars": 108,
    "preview": "```bash\nRUST_LOG=info cargo run\n```\n\nThen visit `http://127.0.0.1:8000`, files will be stored in `./upload`."
  },
  {
    "path": "integration/multipart-example/assets/index.html",
    "chars": 240,
    "preview": "<html lang=\"en\">\n<head><title>Upload Test</title></head>\n<body>\n<form action=\"/file\" method=\"post\" enctype=\"multipart/fo"
  },
  {
    "path": "integration/multipart-example/src/main.rs",
    "chars": 1625,
    "preview": "use std::error::Error as StdError;\nuse std::path::Path;\n\nuse roa::body::{DispositionType, PowerBody};\nuse roa::logger::l"
  },
  {
    "path": "integration/websocket-example/Cargo.toml",
    "chars": 602,
    "preview": "[package]\nname = \"websocket-example\"\nversion = \"0.1.0\"\nauthors = [\"Hexilee <hexileee@gmail.com>\"]\nedition = \"2018\"\n\n# Se"
  },
  {
    "path": "integration/websocket-example/README.md",
    "chars": 6,
    "preview": "WIP..."
  },
  {
    "path": "integration/websocket-example/src/main.rs",
    "chars": 6762,
    "preview": "use std::borrow::Cow;\nuse std::error::Error as StdError;\nuse std::sync::Arc;\n\nuse futures::stream::{SplitSink, SplitStre"
  },
  {
    "path": "roa/Cargo.toml",
    "chars": 3474,
    "preview": "[package]\nname = \"roa\"\nversion = \"0.6.1\"\nauthors = [\"Hexilee <i@hexilee.me>\"]\nedition = \"2018\"\nlicense = \"MIT\"\nreadme = "
  },
  {
    "path": "roa/README.md",
    "chars": 6965,
    "preview": "[![Build status](https://img.shields.io/travis/Hexilee/roa/master.svg)](https://travis-ci.org/Hexilee/roa)\n[![codecov](h"
  },
  {
    "path": "roa/src/body/file/content_disposition.rs",
    "chars": 2548,
    "preview": "use std::convert::{TryFrom, TryInto};\nuse std::fmt::{self, Display, Formatter};\n\nuse percent_encoding::{utf8_percent_enc"
  },
  {
    "path": "roa/src/body/file/help.rs",
    "chars": 372,
    "preview": "use crate::http::StatusCode;\nuse crate::Status;\n\nconst BUG_HELP: &str =\n    r\"This is a bug of roa::body::file, please r"
  },
  {
    "path": "roa/src/body/file.rs",
    "chars": 1140,
    "preview": "mod content_disposition;\nmod help;\n\nuse std::convert::TryInto;\nuse std::path::Path;\n\nuse content_disposition::ContentDis"
  },
  {
    "path": "roa/src/body.rs",
    "chars": 14353,
    "preview": "//! This module provides a context extension `PowerBody`.\n//!\n//! ### Read/write body in a easier way.\n//!\n//! The `roa_"
  },
  {
    "path": "roa/src/compress.rs",
    "chars": 8909,
    "preview": "//! This module provides a middleware `Compress`.\n//!\n//! ### Example\n//!\n//! ```rust\n//! use roa::compress::{Compress, "
  },
  {
    "path": "roa/src/cookie.rs",
    "chars": 9094,
    "preview": "//! This module provides a middleware `cookie_parser` and context extensions `CookieGetter` and `CookieSetter`.\n//!\n//! "
  },
  {
    "path": "roa/src/cors.rs",
    "chars": 19394,
    "preview": "//! This module provides a middleware `Cors`.\n\nuse std::collections::HashSet;\nuse std::convert::TryInto;\nuse std::fmt::D"
  },
  {
    "path": "roa/src/forward.rs",
    "chars": 6182,
    "preview": "//! This module provides a context extension `Forward`,\n//! which is used to parse `X-Forwarded-*` headers.\n\nuse std::ne"
  },
  {
    "path": "roa/src/jsonrpc.rs",
    "chars": 1975,
    "preview": "//!\n//! ## roa::jsonrpc\n//!\n//! This module provides a json rpc endpoint.\n//!\n//! ### Example\n//!\n//! ```rust,no_run\n//!"
  },
  {
    "path": "roa/src/jwt.rs",
    "chars": 10769,
    "preview": "//! This module provides middleware `JwtGuard` and a context extension `JwtVerifier`.\n//!\n//! ### Example\n//!\n//! ```rus"
  },
  {
    "path": "roa/src/lib.rs",
    "chars": 1687,
    "preview": "#![cfg_attr(feature = \"docs\", feature(doc_cfg))]\n#![cfg_attr(feature = \"docs\", doc = include_str!(\"../README.md\"))]\n#![c"
  },
  {
    "path": "roa/src/logger.rs",
    "chars": 4863,
    "preview": "//! This module provides a middleware `logger`.\n//!\n//! ### Example\n//!\n//! ```rust\n//! use roa::logger::logger;\n//! use"
  },
  {
    "path": "roa/src/query.rs",
    "chars": 7364,
    "preview": "//! This module provides a middleware `query_parser` and a context extension `Query`.\n//!\n//! ### Example\n//!\n//! ```rus"
  },
  {
    "path": "roa/src/router/endpoints/dispatcher.rs",
    "chars": 2870,
    "preview": "use std::collections::HashMap;\n\nuse doc_comment::doc_comment;\n\nuse super::method_not_allowed;\nuse crate::http::Method;\nu"
  },
  {
    "path": "roa/src/router/endpoints/guard.rs",
    "chars": 2306,
    "preview": "use std::collections::HashSet;\nuse std::iter::FromIterator;\n\nuse super::method_not_allowed;\nuse crate::http::Method;\nuse"
  },
  {
    "path": "roa/src/router/endpoints.rs",
    "chars": 400,
    "preview": "mod dispatcher;\nmod guard;\n\nuse crate::http::{Method, StatusCode};\nuse crate::{throw, Result};\n\n#[inline]\nfn method_not_"
  },
  {
    "path": "roa/src/router/err.rs",
    "chars": 2747,
    "preview": "use std::fmt::{self, Display, Formatter};\n\nuse roa_core::http;\n\n/// Error occurring in building route table.\n#[derive(De"
  },
  {
    "path": "roa/src/router/path.rs",
    "chars": 7882,
    "preview": "use std::collections::HashSet;\nuse std::convert::AsRef;\nuse std::str::FromStr;\n\nuse regex::{escape, Captures, Regex};\n\nu"
  },
  {
    "path": "roa/src/router.rs",
    "chars": 11822,
    "preview": "//! This module provides a context extension `RouterParam` and\n//! many endpoint wrappers like `Router`, `Dispatcher` an"
  },
  {
    "path": "roa/src/stream.rs",
    "chars": 2640,
    "preview": "//! this module provides a stream adaptor `AsyncStream`\n\nuse std::io;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};"
  },
  {
    "path": "roa/src/tcp/incoming.rs",
    "chars": 6601,
    "preview": "use std::convert::TryInto;\nuse std::future::Future;\nuse std::mem::transmute;\nuse std::net::{SocketAddr, TcpListener as S"
  },
  {
    "path": "roa/src/tcp/listener.rs",
    "chars": 2191,
    "preview": "use std::net::{SocketAddr, ToSocketAddrs};\nuse std::sync::Arc;\n\nuse roa_core::{App, Endpoint, Executor, Server, State};\n"
  },
  {
    "path": "roa/src/tcp.rs",
    "chars": 1028,
    "preview": "//! This module provides an acceptor implementing `roa_core::Accept` and an app extension.\n//!\n//! ### TcpIncoming\n//!\n/"
  },
  {
    "path": "roa/src/tls/incoming.rs",
    "chars": 4887,
    "preview": "use std::io;\nuse std::ops::{Deref, DerefMut};\nuse std::pin::Pin;\nuse std::sync::Arc;\nuse std::task::{self, Context, Poll"
  },
  {
    "path": "roa/src/tls/listener.rs",
    "chars": 5868,
    "preview": "use std::io;\nuse std::net::{SocketAddr, ToSocketAddrs};\nuse std::sync::Arc;\n\nuse super::{ServerConfig, TlsIncoming};\nuse"
  },
  {
    "path": "roa/src/tls.rs",
    "chars": 2377,
    "preview": "//! This module provides an acceptor implementing `roa_core::Accept` and an app extension.\n//!\n//! ### TlsIncoming\n//!\n/"
  },
  {
    "path": "roa/src/websocket.rs",
    "chars": 6607,
    "preview": "//! This module provides a websocket endpoint.\n//!\n//! ### Example\n//! ```\n//! use futures::StreamExt;\n//! use roa::rout"
  },
  {
    "path": "roa/templates/user.html",
    "chars": 162,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>User Homepage</title>\n</head>\n<body>\n<h4>{"
  },
  {
    "path": "roa-async-std/Cargo.toml",
    "chars": 1159,
    "preview": "[package]\nauthors = [\"Hexilee <i@hexilee.me>\"]\ncategories = [\n  \"network-programming\",\n  \"asynchronous\",\n  \"web-programm"
  },
  {
    "path": "roa-async-std/README.md",
    "chars": 1140,
    "preview": "[![Stable Test](https://github.com/Hexilee/roa/workflows/Stable%20Test/badge.svg)](https://github.com/Hexilee/roa/action"
  },
  {
    "path": "roa-async-std/src/lib.rs",
    "chars": 278,
    "preview": "#![cfg_attr(feature = \"docs\", doc = include_str!(\"../README.md\"))]\n#![cfg_attr(feature = \"docs\", warn(missing_docs))]\n\nm"
  },
  {
    "path": "roa-async-std/src/listener.rs",
    "chars": 2572,
    "preview": "use std::net::{SocketAddr, ToSocketAddrs};\nuse std::sync::Arc;\n\nuse roa::{App, Endpoint, Executor, Server, State};\n\nuse "
  },
  {
    "path": "roa-async-std/src/net.rs",
    "chars": 7270,
    "preview": "use std::future::Future;\nuse std::mem::transmute;\nuse std::net::{TcpListener as StdListener, ToSocketAddrs};\nuse std::pi"
  },
  {
    "path": "roa-async-std/src/runtime.rs",
    "chars": 1136,
    "preview": "use std::future::Future;\nuse std::pin::Pin;\n\nuse roa::Spawn;\n\n/// Future Object\npub type FutureObj = Pin<Box<dyn 'static"
  },
  {
    "path": "roa-core/Cargo.toml",
    "chars": 1155,
    "preview": "[package]\nname = \"roa-core\"\nversion = \"0.6.1\"\nauthors = [\"Hexilee <i@hexilee.me>\"]\nedition = \"2018\"\nlicense = \"MIT\"\nread"
  },
  {
    "path": "roa-core/README.md",
    "chars": 4334,
    "preview": "[![Stable Test](https://github.com/Hexilee/roa/workflows/Stable%20Test/badge.svg)](https://github.com/Hexilee/roa/action"
  },
  {
    "path": "roa-core/src/app/future.rs",
    "chars": 882,
    "preview": "use std::future::Future;\nuse std::pin::Pin;\n\nuse futures::task::{Context, Poll};\n\n/// A wrapper to make future `Send`. I"
  },
  {
    "path": "roa-core/src/app/runtime.rs",
    "chars": 880,
    "preview": "use crate::executor::{BlockingObj, FutureObj};\nuse crate::{App, Spawn};\n\nimpl<S> App<S, ()> {\n    /// Construct app with"
  },
  {
    "path": "roa-core/src/app/stream.rs",
    "chars": 2186,
    "preview": "use std::fmt::Debug;\nuse std::io;\nuse std::net::SocketAddr;\nuse std::pin::Pin;\nuse std::task::{self, Poll};\n\nuse futures"
  },
  {
    "path": "roa-core/src/app.rs",
    "chars": 7793,
    "preview": "mod future;\n#[cfg(feature = \"runtime\")]\nmod runtime;\nmod stream;\n\nuse std::convert::Infallible;\nuse std::error::Error;\nu"
  },
  {
    "path": "roa-core/src/body.rs",
    "chars": 6768,
    "preview": "use std::mem;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\n\nuse bytes::{Bytes, BytesMut};\nuse futures::future::ok;"
  },
  {
    "path": "roa-core/src/context/storage.rs",
    "chars": 4676,
    "preview": "use std::any::{Any, TypeId};\nuse std::borrow::Cow;\nuse std::collections::HashMap;\nuse std::fmt::Display;\nuse std::ops::D"
  },
  {
    "path": "roa-core/src/context.rs",
    "chars": 10721,
    "preview": "mod storage;\n\nuse std::any::Any;\nuse std::borrow::Cow;\nuse std::net::SocketAddr;\nuse std::ops::{Deref, DerefMut};\nuse st"
  },
  {
    "path": "roa-core/src/err.rs",
    "chars": 4577,
    "preview": "use std::fmt::{Display, Formatter};\nuse std::result::Result as StdResult;\n\npub use http::StatusCode;\n\n/// Type alias for"
  },
  {
    "path": "roa-core/src/executor.rs",
    "chars": 2925,
    "preview": "use std::future::Future;\nuse std::pin::Pin;\nuse std::sync::Arc;\n\nuse futures::channel::oneshot::{channel, Receiver};\nuse"
  },
  {
    "path": "roa-core/src/group.rs",
    "chars": 4476,
    "preview": "use std::sync::Arc;\n\nuse crate::{async_trait, Context, Endpoint, Middleware, Next, Result};\n\n/// A set of method to chai"
  },
  {
    "path": "roa-core/src/lib.rs",
    "chars": 920,
    "preview": "#![cfg_attr(feature = \"docs\", feature(doc_cfg))]\n#![cfg_attr(feature = \"docs\", doc = include_str!(\"../README.md\"))]\n#![c"
  },
  {
    "path": "roa-core/src/middleware.rs",
    "chars": 9618,
    "preview": "use std::future::Future;\n\nuse http::header::LOCATION;\nuse http::{StatusCode, Uri};\n\nuse crate::{async_trait, throw, Cont"
  },
  {
    "path": "roa-core/src/request.rs",
    "chars": 3146,
    "preview": "use std::io;\n\nuse bytes::Bytes;\nuse futures::stream::{Stream, TryStreamExt};\nuse http::{Extensions, HeaderMap, HeaderVal"
  },
  {
    "path": "roa-core/src/response.rs",
    "chars": 1602,
    "preview": "//! A module for Response and its body\nuse std::ops::{Deref, DerefMut};\n\nuse http::{HeaderMap, HeaderValue, StatusCode, "
  },
  {
    "path": "roa-core/src/state.rs",
    "chars": 822,
    "preview": "/// The `State` trait, should be replace with trait alias.\n/// The `App::state` will be cloned when a request inbounds.\n"
  },
  {
    "path": "roa-diesel/Cargo.toml",
    "chars": 864,
    "preview": "[package]\nname = \"roa-diesel\"\nversion = \"0.6.0\"\nauthors = [\"Hexilee <i@hexilee.me>\"]\nedition = \"2018\"\nlicense = \"MIT\"\nre"
  },
  {
    "path": "roa-diesel/README.md",
    "chars": 1432,
    "preview": "[![Stable Test](https://github.com/Hexilee/roa/workflows/Stable%20Test/badge.svg)](https://github.com/Hexilee/roa/action"
  },
  {
    "path": "roa-diesel/src/async_ext.rs",
    "chars": 7130,
    "preview": "use diesel::connection::Connection;\nuse diesel::helper_types::Limit;\nuse diesel::query_dsl::methods::{ExecuteDsl, LimitD"
  },
  {
    "path": "roa-diesel/src/lib.rs",
    "chars": 433,
    "preview": "#![cfg_attr(feature = \"docs\", doc = include_str!(\"../README.md\"))]\n#![cfg_attr(feature = \"docs\", warn(missing_docs))]\n\nm"
  },
  {
    "path": "roa-diesel/src/pool.rs",
    "chars": 3289,
    "preview": "use std::time::Duration;\n\nuse diesel::r2d2::{ConnectionManager, PoolError};\nuse diesel::Connection;\nuse r2d2::{Builder, "
  },
  {
    "path": "roa-juniper/Cargo.toml",
    "chars": 738,
    "preview": "[package]\nname = \"roa-juniper\"\nversion = \"0.6.0\"\nauthors = [\"Hexilee <i@hexilee.me>\"]\nedition = \"2018\"\nreadme = \"./READM"
  },
  {
    "path": "roa-juniper/README.md",
    "chars": 845,
    "preview": "[![Stable Test](https://github.com/Hexilee/roa/workflows/Stable%20Test/badge.svg)](https://github.com/Hexilee/roa/action"
  },
  {
    "path": "roa-juniper/src/lib.rs",
    "chars": 2081,
    "preview": "//! This crate provides a juniper context and a graphql endpoint.\n//!\n//! ### Example\n//!\n//! Refer to [integration-exam"
  },
  {
    "path": "rustfmt.toml",
    "chars": 114,
    "preview": "group_imports = \"StdExternalCrate\"\nimports_granularity = \"Module\"\nreorder_imports = true\nunstable_features = true\n"
  },
  {
    "path": "src/lib.rs",
    "chars": 55,
    "preview": "#[cfg(doctest)]\ndoc_comment::doctest!(\"../README.md\");\n"
  },
  {
    "path": "templates/directory.html",
    "chars": 735,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>{{title}}</title>\n    <link href=\"/assets/"
  },
  {
    "path": "tests/logger.rs",
    "chars": 3446,
    "preview": "use std::sync::RwLock;\n\nuse log::{Level, LevelFilter, Metadata, Record};\nuse once_cell::sync::Lazy;\nuse roa::http::Statu"
  },
  {
    "path": "tests/restful.rs",
    "chars": 9719,
    "preview": "use std::collections::HashMap;\nuse std::sync::Arc;\n\nuse http::StatusCode;\nuse multimap::MultiMap;\nuse roa::preload::*;\nu"
  },
  {
    "path": "tests/serve-file.rs",
    "chars": 2570,
    "preview": "use http::header::ACCEPT_ENCODING;\nuse roa::body::DispositionType;\nuse roa::compress::Compress;\nuse roa::preload::*;\nuse"
  }
]

About this extraction

This page contains the full source code of the Hexilee/roa GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 112 files (340.2 KB), approximately 93.0k tokens, and a symbol index with 611 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!